diff --git a/library/Zend/Authentication/Adapter/AbstractAdapter.php b/library/Zend/Authentication/Adapter/AbstractAdapter.php
new file mode 100755
index 0000000000..2f394f9e90
--- /dev/null
+++ b/library/Zend/Authentication/Adapter/AbstractAdapter.php
@@ -0,0 +1,71 @@
+credential;
+ }
+
+ /**
+ * Sets the credential for binding
+ *
+ * @param mixed $credential
+ * @return AbstractAdapter
+ */
+ public function setCredential($credential)
+ {
+ $this->credential = $credential;
+
+ return $this;
+ }
+
+ /**
+ * Returns the identity of the account being authenticated, or
+ * NULL if none is set.
+ *
+ * @return mixed
+ */
+ public function getIdentity()
+ {
+ return $this->identity;
+ }
+
+ /**
+ * Sets the identity for binding
+ *
+ * @param mixed $identity
+ * @return AbstractAdapter
+ */
+ public function setIdentity($identity)
+ {
+ $this->identity = $identity;
+
+ return $this;
+ }
+}
diff --git a/library/Zend/Authentication/Adapter/AdapterInterface.php b/library/Zend/Authentication/Adapter/AdapterInterface.php
new file mode 100755
index 0000000000..d23d079366
--- /dev/null
+++ b/library/Zend/Authentication/Adapter/AdapterInterface.php
@@ -0,0 +1,21 @@
+zendDb = $zendDb;
+
+ if (null !== $tableName) {
+ $this->setTableName($tableName);
+ }
+
+ if (null !== $identityColumn) {
+ $this->setIdentityColumn($identityColumn);
+ }
+
+ if (null !== $credentialColumn) {
+ $this->setCredentialColumn($credentialColumn);
+ }
+ }
+
+ /**
+ * setTableName() - set the table name to be used in the select query
+ *
+ * @param string $tableName
+ * @return self Provides a fluent interface
+ */
+ public function setTableName($tableName)
+ {
+ $this->tableName = $tableName;
+ return $this;
+ }
+
+ /**
+ * setIdentityColumn() - set the column name to be used as the identity column
+ *
+ * @param string $identityColumn
+ * @return self Provides a fluent interface
+ */
+ public function setIdentityColumn($identityColumn)
+ {
+ $this->identityColumn = $identityColumn;
+ return $this;
+ }
+
+ /**
+ * setCredentialColumn() - set the column name to be used as the credential column
+ *
+ * @param string $credentialColumn
+ * @return self Provides a fluent interface
+ */
+ public function setCredentialColumn($credentialColumn)
+ {
+ $this->credentialColumn = $credentialColumn;
+ return $this;
+ }
+
+ /**
+ * setAmbiguityIdentity() - sets a flag for usage of identical identities
+ * with unique credentials. It accepts integers (0, 1) or boolean (true,
+ * false) parameters. Default is false.
+ *
+ * @param int|bool $flag
+ * @return self Provides a fluent interface
+ */
+ public function setAmbiguityIdentity($flag)
+ {
+ if (is_int($flag)) {
+ $this->ambiguityIdentity = (1 === $flag ? true : false);
+ } elseif (is_bool($flag)) {
+ $this->ambiguityIdentity = $flag;
+ }
+ return $this;
+ }
+
+ /**
+ * getAmbiguityIdentity() - returns TRUE for usage of multiple identical
+ * identities with different credentials, FALSE if not used.
+ *
+ * @return bool
+ */
+ public function getAmbiguityIdentity()
+ {
+ return $this->ambiguityIdentity;
+ }
+
+ /**
+ * getDbSelect() - Return the preauthentication Db Select object for userland select query modification
+ *
+ * @return Sql\Select
+ */
+ public function getDbSelect()
+ {
+ if ($this->dbSelect == null) {
+ $this->dbSelect = new Sql\Select();
+ }
+ return $this->dbSelect;
+ }
+
+ /**
+ * getResultRowObject() - Returns the result row as a stdClass object
+ *
+ * @param string|array $returnColumns
+ * @param string|array $omitColumns
+ * @return stdClass|bool
+ */
+ public function getResultRowObject($returnColumns = null, $omitColumns = null)
+ {
+ if (!$this->resultRow) {
+ return false;
+ }
+
+ $returnObject = new stdClass();
+
+ if (null !== $returnColumns) {
+ $availableColumns = array_keys($this->resultRow);
+ foreach ((array) $returnColumns as $returnColumn) {
+ if (in_array($returnColumn, $availableColumns)) {
+ $returnObject->{$returnColumn} = $this->resultRow[$returnColumn];
+ }
+ }
+ return $returnObject;
+ } elseif (null !== $omitColumns) {
+ $omitColumns = (array) $omitColumns;
+ foreach ($this->resultRow as $resultColumn => $resultValue) {
+ if (!in_array($resultColumn, $omitColumns)) {
+ $returnObject->{$resultColumn} = $resultValue;
+ }
+ }
+ return $returnObject;
+ }
+
+ foreach ($this->resultRow as $resultColumn => $resultValue) {
+ $returnObject->{$resultColumn} = $resultValue;
+ }
+ return $returnObject;
+ }
+
+ /**
+ * This method is called to attempt an authentication. Previous to this
+ * call, this adapter would have already been configured with all
+ * necessary information to successfully connect to a database table and
+ * attempt to find a record matching the provided identity.
+ *
+ * @throws Exception\RuntimeException if answering the authentication query is impossible
+ * @return AuthenticationResult
+ */
+ public function authenticate()
+ {
+ $this->authenticateSetup();
+ $dbSelect = $this->authenticateCreateSelect();
+ $resultIdentities = $this->authenticateQuerySelect($dbSelect);
+
+ if (($authResult = $this->authenticateValidateResultSet($resultIdentities)) instanceof AuthenticationResult) {
+ return $authResult;
+ }
+
+ // At this point, ambiguity is already done. Loop, check and break on success.
+ foreach ($resultIdentities as $identity) {
+ $authResult = $this->authenticateValidateResult($identity);
+ if ($authResult->isValid()) {
+ break;
+ }
+ }
+
+ return $authResult;
+ }
+
+ /**
+ * _authenticateValidateResult() - This method attempts to validate that
+ * the record in the resultset is indeed a record that matched the
+ * identity provided to this adapter.
+ *
+ * @param array $resultIdentity
+ * @return AuthenticationResult
+ */
+ abstract protected function authenticateValidateResult($resultIdentity);
+
+ /**
+ * _authenticateCreateSelect() - This method creates a Zend\Db\Sql\Select object that
+ * is completely configured to be queried against the database.
+ *
+ * @return Sql\Select
+ */
+ abstract protected function authenticateCreateSelect();
+
+ /**
+ * _authenticateSetup() - This method abstracts the steps involved with
+ * making sure that this adapter was indeed setup properly with all
+ * required pieces of information.
+ *
+ * @throws Exception\RuntimeException in the event that setup was not done properly
+ * @return bool
+ */
+ protected function authenticateSetup()
+ {
+ $exception = null;
+
+ if ($this->tableName == '') {
+ $exception = 'A table must be supplied for the DbTable authentication adapter.';
+ } elseif ($this->identityColumn == '') {
+ $exception = 'An identity column must be supplied for the DbTable authentication adapter.';
+ } elseif ($this->credentialColumn == '') {
+ $exception = 'A credential column must be supplied for the DbTable authentication adapter.';
+ } elseif ($this->identity == '') {
+ $exception = 'A value for the identity was not provided prior to authentication with DbTable.';
+ } elseif ($this->credential === null) {
+ $exception = 'A credential value was not provided prior to authentication with DbTable.';
+ }
+
+ if (null !== $exception) {
+ throw new Exception\RuntimeException($exception);
+ }
+
+ $this->authenticateResultInfo = array(
+ 'code' => AuthenticationResult::FAILURE,
+ 'identity' => $this->identity,
+ 'messages' => array()
+ );
+
+ return true;
+ }
+
+ /**
+ * _authenticateQuerySelect() - This method accepts a Zend\Db\Sql\Select object and
+ * performs a query against the database with that object.
+ *
+ * @param Sql\Select $dbSelect
+ * @throws Exception\RuntimeException when an invalid select object is encountered
+ * @return array
+ */
+ protected function authenticateQuerySelect(Sql\Select $dbSelect)
+ {
+ $sql = new Sql\Sql($this->zendDb);
+ $statement = $sql->prepareStatementForSqlObject($dbSelect);
+ try {
+ $result = $statement->execute();
+ $resultIdentities = array();
+ // iterate result, most cross platform way
+ foreach ($result as $row) {
+ // ZF-6428 - account for db engines that by default return uppercase column names
+ if (isset($row['ZEND_AUTH_CREDENTIAL_MATCH'])) {
+ $row['zend_auth_credential_match'] = $row['ZEND_AUTH_CREDENTIAL_MATCH'];
+ unset($row['ZEND_AUTH_CREDENTIAL_MATCH']);
+ }
+ $resultIdentities[] = $row;
+ }
+ } catch (\Exception $e) {
+ throw new Exception\RuntimeException(
+ 'The supplied parameters to DbTable failed to '
+ . 'produce a valid sql statement, please check table and column names '
+ . 'for validity.',
+ 0,
+ $e
+ );
+ }
+ return $resultIdentities;
+ }
+
+ /**
+ * _authenticateValidateResultSet() - This method attempts to make
+ * certain that only one record was returned in the resultset
+ *
+ * @param array $resultIdentities
+ * @return bool|\Zend\Authentication\Result
+ */
+ protected function authenticateValidateResultSet(array $resultIdentities)
+ {
+ if (count($resultIdentities) < 1) {
+ $this->authenticateResultInfo['code'] = AuthenticationResult::FAILURE_IDENTITY_NOT_FOUND;
+ $this->authenticateResultInfo['messages'][] = 'A record with the supplied identity could not be found.';
+ return $this->authenticateCreateAuthResult();
+ } elseif (count($resultIdentities) > 1 && false === $this->getAmbiguityIdentity()) {
+ $this->authenticateResultInfo['code'] = AuthenticationResult::FAILURE_IDENTITY_AMBIGUOUS;
+ $this->authenticateResultInfo['messages'][] = 'More than one record matches the supplied identity.';
+ return $this->authenticateCreateAuthResult();
+ }
+
+ return true;
+ }
+
+ /**
+ * Creates a Zend\Authentication\Result object from the information that
+ * has been collected during the authenticate() attempt.
+ *
+ * @return AuthenticationResult
+ */
+ protected function authenticateCreateAuthResult()
+ {
+ return new AuthenticationResult(
+ $this->authenticateResultInfo['code'],
+ $this->authenticateResultInfo['identity'],
+ $this->authenticateResultInfo['messages']
+ );
+ }
+}
diff --git a/library/Zend/Authentication/Adapter/DbTable/CallbackCheckAdapter.php b/library/Zend/Authentication/Adapter/DbTable/CallbackCheckAdapter.php
new file mode 100755
index 0000000000..d585e353eb
--- /dev/null
+++ b/library/Zend/Authentication/Adapter/DbTable/CallbackCheckAdapter.php
@@ -0,0 +1,117 @@
+setCredentialValidationCallback($credentialValidationCallback);
+ } else {
+ $this->setCredentialValidationCallback(function ($a, $b) {
+ return $a === $b;
+ });
+ }
+ }
+
+ /**
+ * setCredentialValidationCallback() - allows the developer to use a callback as a way of checking the
+ * credential.
+ *
+ * @param callable $validationCallback
+ * @return self
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setCredentialValidationCallback($validationCallback)
+ {
+ if (!is_callable($validationCallback)) {
+ throw new Exception\InvalidArgumentException('Invalid callback provided');
+ }
+ $this->credentialValidationCallback = $validationCallback;
+ return $this;
+ }
+
+ /**
+ * _authenticateCreateSelect() - This method creates a Zend\Db\Sql\Select object that
+ * is completely configured to be queried against the database.
+ *
+ * @return Sql\Select
+ */
+ protected function authenticateCreateSelect()
+ {
+ // get select
+ $dbSelect = clone $this->getDbSelect();
+ $dbSelect->from($this->tableName)
+ ->columns(array(Sql\Select::SQL_STAR))
+ ->where(new SqlOp($this->identityColumn, '=', $this->identity));
+
+ return $dbSelect;
+ }
+
+ /**
+ * _authenticateValidateResult() - This method attempts to validate that
+ * the record in the resultset is indeed a record that matched the
+ * identity provided to this adapter.
+ *
+ * @param array $resultIdentity
+ * @return AuthenticationResult
+ */
+ protected function authenticateValidateResult($resultIdentity)
+ {
+ try {
+ $callbackResult = call_user_func($this->credentialValidationCallback, $resultIdentity[$this->credentialColumn], $this->credential);
+ } catch (\Exception $e) {
+ $this->authenticateResultInfo['code'] = AuthenticationResult::FAILURE_UNCATEGORIZED;
+ $this->authenticateResultInfo['messages'][] = $e->getMessage();
+ return $this->authenticateCreateAuthResult();
+ }
+ if ($callbackResult !== true) {
+ $this->authenticateResultInfo['code'] = AuthenticationResult::FAILURE_CREDENTIAL_INVALID;
+ $this->authenticateResultInfo['messages'][] = 'Supplied credential is invalid.';
+ return $this->authenticateCreateAuthResult();
+ }
+
+ $this->resultRow = $resultIdentity;
+
+ $this->authenticateResultInfo['code'] = AuthenticationResult::SUCCESS;
+ $this->authenticateResultInfo['messages'][] = 'Authentication successful.';
+ return $this->authenticateCreateAuthResult();
+ }
+}
diff --git a/library/Zend/Authentication/Adapter/DbTable/CredentialTreatmentAdapter.php b/library/Zend/Authentication/Adapter/DbTable/CredentialTreatmentAdapter.php
new file mode 100755
index 0000000000..b31cc5f74f
--- /dev/null
+++ b/library/Zend/Authentication/Adapter/DbTable/CredentialTreatmentAdapter.php
@@ -0,0 +1,124 @@
+setCredentialTreatment($credentialTreatment);
+ }
+ }
+
+ /**
+ * setCredentialTreatment() - allows the developer to pass a parametrized string that is
+ * used to transform or treat the input credential data.
+ *
+ * In many cases, passwords and other sensitive data are encrypted, hashed, encoded,
+ * obscured, or otherwise treated through some function or algorithm. By specifying a
+ * parametrized treatment string with this method, a developer may apply arbitrary SQL
+ * upon input credential data.
+ *
+ * Examples:
+ *
+ * 'PASSWORD(?)'
+ * 'MD5(?)'
+ *
+ * @param string $treatment
+ * @return self Provides a fluent interface
+ */
+ public function setCredentialTreatment($treatment)
+ {
+ $this->credentialTreatment = $treatment;
+ return $this;
+ }
+
+ /**
+ * _authenticateCreateSelect() - This method creates a Zend\Db\Sql\Select object that
+ * is completely configured to be queried against the database.
+ *
+ * @return Sql\Select
+ */
+ protected function authenticateCreateSelect()
+ {
+ // build credential expression
+ if (empty($this->credentialTreatment) || (strpos($this->credentialTreatment, '?') === false)) {
+ $this->credentialTreatment = '?';
+ }
+
+ $credentialExpression = new SqlExpr(
+ '(CASE WHEN ?' . ' = ' . $this->credentialTreatment . ' THEN 1 ELSE 0 END) AS ?',
+ array($this->credentialColumn, $this->credential, 'zend_auth_credential_match'),
+ array(SqlExpr::TYPE_IDENTIFIER, SqlExpr::TYPE_VALUE, SqlExpr::TYPE_IDENTIFIER)
+ );
+
+ // get select
+ $dbSelect = clone $this->getDbSelect();
+ $dbSelect->from($this->tableName)
+ ->columns(array('*', $credentialExpression))
+ ->where(new SqlOp($this->identityColumn, '=', $this->identity));
+
+ return $dbSelect;
+ }
+
+ /**
+ * _authenticateValidateResult() - This method attempts to validate that
+ * the record in the resultset is indeed a record that matched the
+ * identity provided to this adapter.
+ *
+ * @param array $resultIdentity
+ * @return AuthenticationResult
+ */
+ protected function authenticateValidateResult($resultIdentity)
+ {
+ if ($resultIdentity['zend_auth_credential_match'] != '1') {
+ $this->authenticateResultInfo['code'] = AuthenticationResult::FAILURE_CREDENTIAL_INVALID;
+ $this->authenticateResultInfo['messages'][] = 'Supplied credential is invalid.';
+ return $this->authenticateCreateAuthResult();
+ }
+
+ unset($resultIdentity['zend_auth_credential_match']);
+ $this->resultRow = $resultIdentity;
+
+ $this->authenticateResultInfo['code'] = AuthenticationResult::SUCCESS;
+ $this->authenticateResultInfo['messages'][] = 'Authentication successful.';
+ return $this->authenticateCreateAuthResult();
+ }
+}
diff --git a/library/Zend/Authentication/Adapter/DbTable/Exception/ExceptionInterface.php b/library/Zend/Authentication/Adapter/DbTable/Exception/ExceptionInterface.php
new file mode 100755
index 0000000000..5365808fcc
--- /dev/null
+++ b/library/Zend/Authentication/Adapter/DbTable/Exception/ExceptionInterface.php
@@ -0,0 +1,16 @@
+setFilename($filename);
+ }
+ if ($realm !== null) {
+ $this->setRealm($realm);
+ }
+ if ($identity !== null) {
+ $this->setIdentity($identity);
+ }
+ if ($credential !== null) {
+ $this->setCredential($credential);
+ }
+ }
+
+ /**
+ * Returns the filename option value or null if it has not yet been set
+ *
+ * @return string|null
+ */
+ public function getFilename()
+ {
+ return $this->filename;
+ }
+
+ /**
+ * Sets the filename option value
+ *
+ * @param mixed $filename
+ * @return Digest Provides a fluent interface
+ */
+ public function setFilename($filename)
+ {
+ $this->filename = (string) $filename;
+ return $this;
+ }
+
+ /**
+ * Returns the realm option value or null if it has not yet been set
+ *
+ * @return string|null
+ */
+ public function getRealm()
+ {
+ return $this->realm;
+ }
+
+ /**
+ * Sets the realm option value
+ *
+ * @param mixed $realm
+ * @return Digest Provides a fluent interface
+ */
+ public function setRealm($realm)
+ {
+ $this->realm = (string) $realm;
+ return $this;
+ }
+
+ /**
+ * Returns the username option value or null if it has not yet been set
+ *
+ * @return string|null
+ */
+ public function getUsername()
+ {
+ return $this->getIdentity();
+ }
+
+ /**
+ * Sets the username option value
+ *
+ * @param mixed $username
+ * @return Digest Provides a fluent interface
+ */
+ public function setUsername($username)
+ {
+ return $this->setIdentity($username);
+ }
+
+ /**
+ * Returns the password option value or null if it has not yet been set
+ *
+ * @return string|null
+ */
+ public function getPassword()
+ {
+ return $this->getCredential();
+ }
+
+ /**
+ * Sets the password option value
+ *
+ * @param mixed $password
+ * @return Digest Provides a fluent interface
+ */
+ public function setPassword($password)
+ {
+ return $this->setCredential($password);
+ }
+
+ /**
+ * Defined by Zend\Authentication\Adapter\AdapterInterface
+ *
+ * @throws Exception\ExceptionInterface
+ * @return AuthenticationResult
+ */
+ public function authenticate()
+ {
+ $optionsRequired = array('filename', 'realm', 'identity', 'credential');
+ foreach ($optionsRequired as $optionRequired) {
+ if (null === $this->$optionRequired) {
+ throw new Exception\RuntimeException("Option '$optionRequired' must be set before authentication");
+ }
+ }
+
+ ErrorHandler::start(E_WARNING);
+ $fileHandle = fopen($this->filename, 'r');
+ $error = ErrorHandler::stop();
+ if (false === $fileHandle) {
+ throw new Exception\UnexpectedValueException("Cannot open '$this->filename' for reading", 0, $error);
+ }
+
+ $id = "$this->identity:$this->realm";
+ $idLength = strlen($id);
+
+ $result = array(
+ 'code' => AuthenticationResult::FAILURE,
+ 'identity' => array(
+ 'realm' => $this->realm,
+ 'username' => $this->identity,
+ ),
+ 'messages' => array()
+ );
+
+ while (($line = fgets($fileHandle)) !== false) {
+ $line = trim($line);
+ if (empty($line)) {
+ break;
+ }
+ if (substr($line, 0, $idLength) === $id) {
+ if (CryptUtils::compareStrings(substr($line, -32), md5("$this->identity:$this->realm:$this->credential"))) {
+ return new AuthenticationResult(AuthenticationResult::SUCCESS, $result['identity'], $result['messages']);
+ }
+ $result['messages'][] = 'Password incorrect';
+ return new AuthenticationResult(AuthenticationResult::FAILURE_CREDENTIAL_INVALID, $result['identity'], $result['messages']);
+ }
+ }
+
+ $result['messages'][] = "Username '$this->identity' and realm '$this->realm' combination not found";
+ return new AuthenticationResult(AuthenticationResult::FAILURE_IDENTITY_NOT_FOUND, $result['identity'], $result['messages']);
+ }
+}
diff --git a/library/Zend/Authentication/Adapter/Exception/ExceptionInterface.php b/library/Zend/Authentication/Adapter/Exception/ExceptionInterface.php
new file mode 100755
index 0000000000..4c9b9d4be1
--- /dev/null
+++ b/library/Zend/Authentication/Adapter/Exception/ExceptionInterface.php
@@ -0,0 +1,16 @@
+ 'basic'|'digest'|'basic digest'
+ * 'realm' =>
+ * 'digest_domains' => Space-delimited list of URIs
+ * 'nonce_timeout' =>
+ * 'use_opaque' => Whether to send the opaque value in the header
+ * 'algorithm' => See $supportedAlgos. Default: MD5
+ * 'proxy_auth' => Whether to do authentication as a Proxy
+ * @throws Exception\InvalidArgumentException
+ */
+ public function __construct(array $config)
+ {
+ $this->request = null;
+ $this->response = null;
+ $this->ieNoOpaque = false;
+
+ if (empty($config['accept_schemes'])) {
+ throw new Exception\InvalidArgumentException('Config key "accept_schemes" is required');
+ }
+
+ $schemes = explode(' ', $config['accept_schemes']);
+ $this->acceptSchemes = array_intersect($schemes, $this->supportedSchemes);
+ if (empty($this->acceptSchemes)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'No supported schemes given in "accept_schemes". Valid values: %s',
+ implode(', ', $this->supportedSchemes)
+ ));
+ }
+
+ // Double-quotes are used to delimit the realm string in the HTTP header,
+ // and colons are field delimiters in the password file.
+ if (empty($config['realm']) ||
+ !ctype_print($config['realm']) ||
+ strpos($config['realm'], ':') !== false ||
+ strpos($config['realm'], '"') !== false) {
+ throw new Exception\InvalidArgumentException(
+ 'Config key \'realm\' is required, and must contain only printable characters,'
+ . 'excluding quotation marks and colons'
+ );
+ } else {
+ $this->realm = $config['realm'];
+ }
+
+ if (in_array('digest', $this->acceptSchemes)) {
+ if (empty($config['digest_domains']) ||
+ !ctype_print($config['digest_domains']) ||
+ strpos($config['digest_domains'], '"') !== false) {
+ throw new Exception\InvalidArgumentException(
+ 'Config key \'digest_domains\' is required, and must contain '
+ . 'only printable characters, excluding quotation marks'
+ );
+ } else {
+ $this->domains = $config['digest_domains'];
+ }
+
+ if (empty($config['nonce_timeout']) ||
+ !is_numeric($config['nonce_timeout'])) {
+ throw new Exception\InvalidArgumentException(
+ 'Config key \'nonce_timeout\' is required, and must be an integer'
+ );
+ } else {
+ $this->nonceTimeout = (int) $config['nonce_timeout'];
+ }
+
+ // We use the opaque value unless explicitly told not to
+ if (isset($config['use_opaque']) && false == (bool) $config['use_opaque']) {
+ $this->useOpaque = false;
+ } else {
+ $this->useOpaque = true;
+ }
+
+ if (isset($config['algorithm']) && in_array($config['algorithm'], $this->supportedAlgos)) {
+ $this->algo = $config['algorithm'];
+ } else {
+ $this->algo = 'MD5';
+ }
+ }
+
+ // Don't be a proxy unless explicitly told to do so
+ if (isset($config['proxy_auth']) && true == (bool) $config['proxy_auth']) {
+ $this->imaProxy = true; // I'm a Proxy
+ } else {
+ $this->imaProxy = false;
+ }
+ }
+
+ /**
+ * Setter for the basicResolver property
+ *
+ * @param Http\ResolverInterface $resolver
+ * @return Http Provides a fluent interface
+ */
+ public function setBasicResolver(Http\ResolverInterface $resolver)
+ {
+ $this->basicResolver = $resolver;
+
+ return $this;
+ }
+
+ /**
+ * Getter for the basicResolver property
+ *
+ * @return Http\ResolverInterface
+ */
+ public function getBasicResolver()
+ {
+ return $this->basicResolver;
+ }
+
+ /**
+ * Setter for the digestResolver property
+ *
+ * @param Http\ResolverInterface $resolver
+ * @return Http Provides a fluent interface
+ */
+ public function setDigestResolver(Http\ResolverInterface $resolver)
+ {
+ $this->digestResolver = $resolver;
+
+ return $this;
+ }
+
+ /**
+ * Getter for the digestResolver property
+ *
+ * @return Http\ResolverInterface
+ */
+ public function getDigestResolver()
+ {
+ return $this->digestResolver;
+ }
+
+ /**
+ * Setter for the Request object
+ *
+ * @param HTTPRequest $request
+ * @return Http Provides a fluent interface
+ */
+ public function setRequest(HTTPRequest $request)
+ {
+ $this->request = $request;
+
+ return $this;
+ }
+
+ /**
+ * Getter for the Request object
+ *
+ * @return HTTPRequest
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ /**
+ * Setter for the Response object
+ *
+ * @param HTTPResponse $response
+ * @return Http Provides a fluent interface
+ */
+ public function setResponse(HTTPResponse $response)
+ {
+ $this->response = $response;
+
+ return $this;
+ }
+
+ /**
+ * Getter for the Response object
+ *
+ * @return HTTPResponse
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ /**
+ * Authenticate
+ *
+ * @throws Exception\RuntimeException
+ * @return Authentication\Result
+ */
+ public function authenticate()
+ {
+ if (empty($this->request) || empty($this->response)) {
+ throw new Exception\RuntimeException(
+ 'Request and Response objects must be set before calling authenticate()'
+ );
+ }
+
+ if ($this->imaProxy) {
+ $getHeader = 'Proxy-Authorization';
+ } else {
+ $getHeader = 'Authorization';
+ }
+
+ $headers = $this->request->getHeaders();
+ if (!$headers->has($getHeader)) {
+ return $this->challengeClient();
+ }
+ $authHeader = $headers->get($getHeader)->getFieldValue();
+ if (!$authHeader) {
+ return $this->challengeClient();
+ }
+
+ list($clientScheme) = explode(' ', $authHeader);
+ $clientScheme = strtolower($clientScheme);
+
+ // The server can issue multiple challenges, but the client should
+ // answer with only the selected auth scheme.
+ if (!in_array($clientScheme, $this->supportedSchemes)) {
+ $this->response->setStatusCode(400);
+ return new Authentication\Result(
+ Authentication\Result::FAILURE_UNCATEGORIZED,
+ array(),
+ array('Client requested an incorrect or unsupported authentication scheme')
+ );
+ }
+
+ // client sent a scheme that is not the one required
+ if (!in_array($clientScheme, $this->acceptSchemes)) {
+ // challenge again the client
+ return $this->challengeClient();
+ }
+
+ switch ($clientScheme) {
+ case 'basic':
+ $result = $this->_basicAuth($authHeader);
+ break;
+ case 'digest':
+ $result = $this->_digestAuth($authHeader);
+ break;
+ default:
+ throw new Exception\RuntimeException('Unsupported authentication scheme: ' . $clientScheme);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @deprecated
+ * @see Http::challengeClient()
+ * @return Authentication\Result Always returns a non-identity Auth result
+ */
+ protected function _challengeClient()
+ {
+ trigger_error(sprintf(
+ 'The method "%s" is deprecated and will be removed in the future; '
+ . 'please use the public method "%s::challengeClient()" instead',
+ __METHOD__,
+ __CLASS__
+ ), E_USER_DEPRECATED);
+
+ return $this->challengeClient();
+ }
+
+ /**
+ * Challenge Client
+ *
+ * Sets a 401 or 407 Unauthorized response code, and creates the
+ * appropriate Authenticate header(s) to prompt for credentials.
+ *
+ * @return Authentication\Result Always returns a non-identity Auth result
+ */
+ public function challengeClient()
+ {
+ if ($this->imaProxy) {
+ $statusCode = 407;
+ $headerName = 'Proxy-Authenticate';
+ } else {
+ $statusCode = 401;
+ $headerName = 'WWW-Authenticate';
+ }
+
+ $this->response->setStatusCode($statusCode);
+
+ // Send a challenge in each acceptable authentication scheme
+ $headers = $this->response->getHeaders();
+ if (in_array('basic', $this->acceptSchemes)) {
+ $headers->addHeaderLine($headerName, $this->_basicHeader());
+ }
+ if (in_array('digest', $this->acceptSchemes)) {
+ $headers->addHeaderLine($headerName, $this->_digestHeader());
+ }
+ return new Authentication\Result(
+ Authentication\Result::FAILURE_CREDENTIAL_INVALID,
+ array(),
+ array('Invalid or absent credentials; challenging client')
+ );
+ }
+
+ /**
+ * Basic Header
+ *
+ * Generates a Proxy- or WWW-Authenticate header value in the Basic
+ * authentication scheme.
+ *
+ * @return string Authenticate header value
+ */
+ protected function _basicHeader()
+ {
+ return 'Basic realm="' . $this->realm . '"';
+ }
+
+ /**
+ * Digest Header
+ *
+ * Generates a Proxy- or WWW-Authenticate header value in the Digest
+ * authentication scheme.
+ *
+ * @return string Authenticate header value
+ */
+ protected function _digestHeader()
+ {
+ $wwwauth = 'Digest realm="' . $this->realm . '", '
+ . 'domain="' . $this->domains . '", '
+ . 'nonce="' . $this->_calcNonce() . '", '
+ . ($this->useOpaque ? 'opaque="' . $this->_calcOpaque() . '", ' : '')
+ . 'algorithm="' . $this->algo . '", '
+ . 'qop="' . implode(',', $this->supportedQops) . '"';
+
+ return $wwwauth;
+ }
+
+ /**
+ * Basic Authentication
+ *
+ * @param string $header Client's Authorization header
+ * @throws Exception\ExceptionInterface
+ * @return Authentication\Result
+ */
+ protected function _basicAuth($header)
+ {
+ if (empty($header)) {
+ throw new Exception\RuntimeException('The value of the client Authorization header is required');
+ }
+ if (empty($this->basicResolver)) {
+ throw new Exception\RuntimeException(
+ 'A basicResolver object must be set before doing Basic authentication'
+ );
+ }
+
+ // Decode the Authorization header
+ $auth = substr($header, strlen('Basic '));
+ $auth = base64_decode($auth);
+ if (!$auth) {
+ throw new Exception\RuntimeException('Unable to base64_decode Authorization header value');
+ }
+
+ // See ZF-1253. Validate the credentials the same way the digest
+ // implementation does. If invalid credentials are detected,
+ // re-challenge the client.
+ if (!ctype_print($auth)) {
+ return $this->challengeClient();
+ }
+ // Fix for ZF-1515: Now re-challenges on empty username or password
+ $creds = array_filter(explode(':', $auth));
+ if (count($creds) != 2) {
+ return $this->challengeClient();
+ }
+
+ $result = $this->basicResolver->resolve($creds[0], $this->realm, $creds[1]);
+
+ if ($result instanceof Authentication\Result && $result->isValid()) {
+ return $result;
+ }
+
+ if (!$result instanceof Authentication\Result
+ && !is_array($result)
+ && CryptUtils::compareStrings($result, $creds[1])
+ ) {
+ $identity = array('username' => $creds[0], 'realm' => $this->realm);
+ return new Authentication\Result(Authentication\Result::SUCCESS, $identity);
+ } elseif (is_array($result)) {
+ return new Authentication\Result(Authentication\Result::SUCCESS, $result);
+ }
+
+ return $this->challengeClient();
+ }
+
+ /**
+ * Digest Authentication
+ *
+ * @param string $header Client's Authorization header
+ * @throws Exception\ExceptionInterface
+ * @return Authentication\Result Valid auth result only on successful auth
+ */
+ protected function _digestAuth($header)
+ {
+ if (empty($header)) {
+ throw new Exception\RuntimeException('The value of the client Authorization header is required');
+ }
+ if (empty($this->digestResolver)) {
+ throw new Exception\RuntimeException('A digestResolver object must be set before doing Digest authentication');
+ }
+
+ $data = $this->_parseDigestAuth($header);
+ if ($data === false) {
+ $this->response->setStatusCode(400);
+ return new Authentication\Result(
+ Authentication\Result::FAILURE_UNCATEGORIZED,
+ array(),
+ array('Invalid Authorization header format')
+ );
+ }
+
+ // See ZF-1052. This code was a bit too unforgiving of invalid
+ // usernames. Now, if the username is bad, we re-challenge the client.
+ if ('::invalid::' == $data['username']) {
+ return $this->challengeClient();
+ }
+
+ // Verify that the client sent back the same nonce
+ if ($this->_calcNonce() != $data['nonce']) {
+ return $this->challengeClient();
+ }
+ // The opaque value is also required to match, but of course IE doesn't
+ // play ball.
+ if (!$this->ieNoOpaque && $this->_calcOpaque() != $data['opaque']) {
+ return $this->challengeClient();
+ }
+
+ // Look up the user's password hash. If not found, deny access.
+ // This makes no assumptions about how the password hash was
+ // constructed beyond that it must have been built in such a way as
+ // to be recreatable with the current settings of this object.
+ $ha1 = $this->digestResolver->resolve($data['username'], $data['realm']);
+ if ($ha1 === false) {
+ return $this->challengeClient();
+ }
+
+ // If MD5-sess is used, a1 value is made of the user's password
+ // hash with the server and client nonce appended, separated by
+ // colons.
+ if ($this->algo == 'MD5-sess') {
+ $ha1 = hash('md5', $ha1 . ':' . $data['nonce'] . ':' . $data['cnonce']);
+ }
+
+ // Calculate h(a2). The value of this hash depends on the qop
+ // option selected by the client and the supported hash functions
+ switch ($data['qop']) {
+ case 'auth':
+ $a2 = $this->request->getMethod() . ':' . $data['uri'];
+ break;
+ case 'auth-int':
+ // Should be REQUEST_METHOD . ':' . uri . ':' . hash(entity-body),
+ // but this isn't supported yet, so fall through to default case
+ default:
+ throw new Exception\RuntimeException('Client requested an unsupported qop option');
+ }
+ // Using hash() should make parameterizing the hash algorithm
+ // easier
+ $ha2 = hash('md5', $a2);
+
+ // Calculate the server's version of the request-digest. This must
+ // match $data['response']. See RFC 2617, section 3.2.2.1
+ $message = $data['nonce'] . ':' . $data['nc'] . ':' . $data['cnonce'] . ':' . $data['qop'] . ':' . $ha2;
+ $digest = hash('md5', $ha1 . ':' . $message);
+
+ // If our digest matches the client's let them in, otherwise return
+ // a 401 code and exit to prevent access to the protected resource.
+ if (CryptUtils::compareStrings($digest, $data['response'])) {
+ $identity = array('username' => $data['username'], 'realm' => $data['realm']);
+ return new Authentication\Result(Authentication\Result::SUCCESS, $identity);
+ }
+
+ return $this->challengeClient();
+ }
+
+ /**
+ * Calculate Nonce
+ *
+ * @return string The nonce value
+ */
+ protected function _calcNonce()
+ {
+ // Once subtle consequence of this timeout calculation is that it
+ // actually divides all of time into nonceTimeout-sized sections, such
+ // that the value of timeout is the point in time of the next
+ // approaching "boundary" of a section. This allows the server to
+ // consistently generate the same timeout (and hence the same nonce
+ // value) across requests, but only as long as one of those
+ // "boundaries" is not crossed between requests. If that happens, the
+ // nonce will change on its own, and effectively log the user out. This
+ // would be surprising if the user just logged in.
+ $timeout = ceil(time() / $this->nonceTimeout) * $this->nonceTimeout;
+
+ $userAgentHeader = $this->request->getHeaders()->get('User-Agent');
+ if ($userAgentHeader) {
+ $userAgent = $userAgentHeader->getFieldValue();
+ } elseif (isset($_SERVER['HTTP_USER_AGENT'])) {
+ $userAgent = $_SERVER['HTTP_USER_AGENT'];
+ } else {
+ $userAgent = 'Zend_Authenticaion';
+ }
+ $nonce = hash('md5', $timeout . ':' . $userAgent . ':' . __CLASS__);
+ return $nonce;
+ }
+
+ /**
+ * Calculate Opaque
+ *
+ * The opaque string can be anything; the client must return it exactly as
+ * it was sent. It may be useful to store data in this string in some
+ * applications. Ideally, a new value for this would be generated each time
+ * a WWW-Authenticate header is sent (in order to reduce predictability),
+ * but we would have to be able to create the same exact value across at
+ * least two separate requests from the same client.
+ *
+ * @return string The opaque value
+ */
+ protected function _calcOpaque()
+ {
+ return hash('md5', 'Opaque Data:' . __CLASS__);
+ }
+
+ /**
+ * Parse Digest Authorization header
+ *
+ * @param string $header Client's Authorization: HTTP header
+ * @return array|bool Data elements from header, or false if any part of
+ * the header is invalid
+ */
+ protected function _parseDigestAuth($header)
+ {
+ $temp = null;
+ $data = array();
+
+ // See ZF-1052. Detect invalid usernames instead of just returning a
+ // 400 code.
+ $ret = preg_match('/username="([^"]+)"/', $header, $temp);
+ if (!$ret || empty($temp[1])
+ || !ctype_print($temp[1])
+ || strpos($temp[1], ':') !== false) {
+ $data['username'] = '::invalid::';
+ } else {
+ $data['username'] = $temp[1];
+ }
+ $temp = null;
+
+ $ret = preg_match('/realm="([^"]+)"/', $header, $temp);
+ if (!$ret || empty($temp[1])) {
+ return false;
+ }
+ if (!ctype_print($temp[1]) || strpos($temp[1], ':') !== false) {
+ return false;
+ } else {
+ $data['realm'] = $temp[1];
+ }
+ $temp = null;
+
+ $ret = preg_match('/nonce="([^"]+)"/', $header, $temp);
+ if (!$ret || empty($temp[1])) {
+ return false;
+ }
+ if (!ctype_xdigit($temp[1])) {
+ return false;
+ }
+
+ $data['nonce'] = $temp[1];
+ $temp = null;
+
+ $ret = preg_match('/uri="([^"]+)"/', $header, $temp);
+ if (!$ret || empty($temp[1])) {
+ return false;
+ }
+ // Section 3.2.2.5 in RFC 2617 says the authenticating server must
+ // verify that the URI field in the Authorization header is for the
+ // same resource requested in the Request Line.
+ $rUri = $this->request->getUri();
+ $cUri = UriFactory::factory($temp[1]);
+
+ // Make sure the path portion of both URIs is the same
+ if ($rUri->getPath() != $cUri->getPath()) {
+ return false;
+ }
+
+ // Section 3.2.2.5 seems to suggest that the value of the URI
+ // Authorization field should be made into an absolute URI if the
+ // Request URI is absolute, but it's vague, and that's a bunch of
+ // code I don't want to write right now.
+ $data['uri'] = $temp[1];
+ $temp = null;
+
+ $ret = preg_match('/response="([^"]+)"/', $header, $temp);
+ if (!$ret || empty($temp[1])) {
+ return false;
+ }
+ if (32 != strlen($temp[1]) || !ctype_xdigit($temp[1])) {
+ return false;
+ }
+
+ $data['response'] = $temp[1];
+ $temp = null;
+
+ // The spec says this should default to MD5 if omitted. OK, so how does
+ // that square with the algo we send out in the WWW-Authenticate header,
+ // if it can easily be overridden by the client?
+ $ret = preg_match('/algorithm="?(' . $this->algo . ')"?/', $header, $temp);
+ if ($ret && !empty($temp[1])
+ && in_array($temp[1], $this->supportedAlgos)) {
+ $data['algorithm'] = $temp[1];
+ } else {
+ $data['algorithm'] = 'MD5'; // = $this->algo; ?
+ }
+ $temp = null;
+
+ // Not optional in this implementation
+ $ret = preg_match('/cnonce="([^"]+)"/', $header, $temp);
+ if (!$ret || empty($temp[1])) {
+ return false;
+ }
+ if (!ctype_print($temp[1])) {
+ return false;
+ }
+
+ $data['cnonce'] = $temp[1];
+ $temp = null;
+
+ // If the server sent an opaque value, the client must send it back
+ if ($this->useOpaque) {
+ $ret = preg_match('/opaque="([^"]+)"/', $header, $temp);
+ if (!$ret || empty($temp[1])) {
+ // Big surprise: IE isn't RFC 2617-compliant.
+ $headers = $this->request->getHeaders();
+ if (!$headers->has('User-Agent')) {
+ return false;
+ }
+ $userAgent = $headers->get('User-Agent')->getFieldValue();
+ if (false === strpos($userAgent, 'MSIE')) {
+ return false;
+ }
+
+ $temp[1] = '';
+ $this->ieNoOpaque = true;
+ }
+
+ // This implementation only sends MD5 hex strings in the opaque value
+ if (!$this->ieNoOpaque &&
+ (32 != strlen($temp[1]) || !ctype_xdigit($temp[1]))) {
+ return false;
+ }
+
+ $data['opaque'] = $temp[1];
+ $temp = null;
+ }
+
+ // Not optional in this implementation, but must be one of the supported
+ // qop types
+ $ret = preg_match('/qop="?(' . implode('|', $this->supportedQops) . ')"?/', $header, $temp);
+ if (!$ret || empty($temp[1])) {
+ return false;
+ }
+ if (!in_array($temp[1], $this->supportedQops)) {
+ return false;
+ }
+
+ $data['qop'] = $temp[1];
+ $temp = null;
+
+ // Not optional in this implementation. The spec says this value
+ // shouldn't be a quoted string, but apparently some implementations
+ // quote it anyway. See ZF-1544.
+ $ret = preg_match('/nc="?([0-9A-Fa-f]{8})"?/', $header, $temp);
+ if (!$ret || empty($temp[1])) {
+ return false;
+ }
+ if (8 != strlen($temp[1]) || !ctype_xdigit($temp[1])) {
+ return false;
+ }
+
+ $data['nc'] = $temp[1];
+ $temp = null;
+
+ return $data;
+ }
+}
diff --git a/library/Zend/Authentication/Adapter/Http/ApacheResolver.php b/library/Zend/Authentication/Adapter/Http/ApacheResolver.php
new file mode 100755
index 0000000000..a7b8b3d6d8
--- /dev/null
+++ b/library/Zend/Authentication/Adapter/Http/ApacheResolver.php
@@ -0,0 +1,171 @@
+setFile($path);
+ }
+ }
+
+ /**
+ * Set the path to the credentials file
+ *
+ * @param string $path
+ * @return self Provides a fluent interface
+ * @throws Exception\InvalidArgumentException if path is not readable
+ */
+ public function setFile($path)
+ {
+ if (empty($path) || !is_readable($path)) {
+ throw new Exception\InvalidArgumentException('Path not readable: ' . $path);
+ }
+ $this->file = $path;
+
+ return $this;
+ }
+
+ /**
+ * Returns the path to the credentials file
+ *
+ * @return string
+ */
+ public function getFile()
+ {
+ return $this->file;
+ }
+
+ /**
+ * Returns the Apache Password object
+ *
+ * @return ApachePassword
+ */
+ protected function getApachePassword()
+ {
+ if (empty($this->apachePassword)) {
+ $this->apachePassword = new ApachePassword();
+ }
+ return $this->apachePassword;
+ }
+
+ /**
+ * Resolve credentials
+ *
+ *
+ *
+ * @param string $username Username
+ * @param string $realm Authentication Realm
+ * @param string $password The password to authenticate
+ * @return AuthResult
+ * @throws Exception\ExceptionInterface
+ */
+ public function resolve($username, $realm, $password = null)
+ {
+ if (empty($username)) {
+ throw new Exception\InvalidArgumentException('Username is required');
+ }
+
+ if (!ctype_print($username) || strpos($username, ':') !== false) {
+ throw new Exception\InvalidArgumentException(
+ 'Username must consist only of printable characters, excluding the colon'
+ );
+ }
+
+ if (!empty($realm) && (!ctype_print($realm) || strpos($realm, ':') !== false)) {
+ throw new Exception\InvalidArgumentException(
+ 'Realm must consist only of printable characters, excluding the colon'
+ );
+ }
+
+ if (empty($password)) {
+ throw new Exception\InvalidArgumentException('Password is required');
+ }
+
+ // Open file, read through looking for matching credentials
+ ErrorHandler::start(E_WARNING);
+ $fp = fopen($this->file, 'r');
+ $error = ErrorHandler::stop();
+ if (!$fp) {
+ throw new Exception\RuntimeException('Unable to open password file: ' . $this->file, 0, $error);
+ }
+
+ // No real validation is done on the contents of the password file. The
+ // assumption is that we trust the administrators to keep it secure.
+ while (($line = fgetcsv($fp, 512, ':')) !== false) {
+ if ($line[0] != $username) {
+ continue;
+ }
+
+ if (isset($line[2])) {
+ if ($line[1] == $realm) {
+ $matchedHash = $line[2];
+ break;
+ }
+ continue;
+ }
+
+ $matchedHash = $line[1];
+ break;
+ }
+ fclose($fp);
+
+ if (!isset($matchedHash)) {
+ return new AuthResult(AuthResult::FAILURE_IDENTITY_NOT_FOUND, null, array('Username not found in provided htpasswd file'));
+ }
+
+ // Plaintext password
+ if ($matchedHash === $password) {
+ return new AuthResult(AuthResult::SUCCESS, $username);
+ }
+
+ $apache = $this->getApachePassword();
+ $apache->setUserName($username);
+ if (!empty($realm)) {
+ $apache->setAuthName($realm);
+ }
+
+ if ($apache->verify($password, $matchedHash)) {
+ return new AuthResult(AuthResult::SUCCESS, $username);
+ }
+
+ return new AuthResult(AuthResult::FAILURE_CREDENTIAL_INVALID, null, array('Passwords did not match.'));
+ }
+}
diff --git a/library/Zend/Authentication/Adapter/Http/Exception/ExceptionInterface.php b/library/Zend/Authentication/Adapter/Http/Exception/ExceptionInterface.php
new file mode 100755
index 0000000000..4ec6a3f7ca
--- /dev/null
+++ b/library/Zend/Authentication/Adapter/Http/Exception/ExceptionInterface.php
@@ -0,0 +1,19 @@
+setFile($path);
+ }
+ }
+
+ /**
+ * Set the path to the credentials file
+ *
+ * @param string $path
+ * @return FileResolver Provides a fluent interface
+ * @throws Exception\InvalidArgumentException if path is not readable
+ */
+ public function setFile($path)
+ {
+ if (empty($path) || !is_readable($path)) {
+ throw new Exception\InvalidArgumentException('Path not readable: ' . $path);
+ }
+ $this->file = $path;
+
+ return $this;
+ }
+
+ /**
+ * Returns the path to the credentials file
+ *
+ * @return string
+ */
+ public function getFile()
+ {
+ return $this->file;
+ }
+
+ /**
+ * Resolve credentials
+ *
+ * Only the first matching username/realm combination in the file is
+ * returned. If the file contains credentials for Digest authentication,
+ * the returned string is the password hash, or h(a1) from RFC 2617. The
+ * returned string is the plain-text password for Basic authentication.
+ *
+ * The expected format of the file is:
+ * username:realm:sharedSecret
+ *
+ * That is, each line consists of the user's username, the applicable
+ * authentication realm, and the password or hash, each delimited by
+ * colons.
+ *
+ * @param string $username Username
+ * @param string $realm Authentication Realm
+ * @return string|false User's shared secret, if the user is found in the
+ * realm, false otherwise.
+ * @throws Exception\ExceptionInterface
+ */
+ public function resolve($username, $realm, $password = null)
+ {
+ if (empty($username)) {
+ throw new Exception\InvalidArgumentException('Username is required');
+ } elseif (!ctype_print($username) || strpos($username, ':') !== false) {
+ throw new Exception\InvalidArgumentException(
+ 'Username must consist only of printable characters, excluding the colon'
+ );
+ }
+ if (empty($realm)) {
+ throw new Exception\InvalidArgumentException('Realm is required');
+ } elseif (!ctype_print($realm) || strpos($realm, ':') !== false) {
+ throw new Exception\InvalidArgumentException(
+ 'Realm must consist only of printable characters, excluding the colon.'
+ );
+ }
+
+ // Open file, read through looking for matching credentials
+ ErrorHandler::start(E_WARNING);
+ $fp = fopen($this->file, 'r');
+ $error = ErrorHandler::stop();
+ if (!$fp) {
+ throw new Exception\RuntimeException('Unable to open password file: ' . $this->file, 0, $error);
+ }
+
+ // No real validation is done on the contents of the password file. The
+ // assumption is that we trust the administrators to keep it secure.
+ while (($line = fgetcsv($fp, 512, ':')) !== false) {
+ if ($line[0] == $username && $line[1] == $realm) {
+ $password = $line[2];
+ fclose($fp);
+ return $password;
+ }
+ }
+
+ fclose($fp);
+ return false;
+ }
+}
diff --git a/library/Zend/Authentication/Adapter/Http/ResolverInterface.php b/library/Zend/Authentication/Adapter/Http/ResolverInterface.php
new file mode 100755
index 0000000000..b236f84588
--- /dev/null
+++ b/library/Zend/Authentication/Adapter/Http/ResolverInterface.php
@@ -0,0 +1,30 @@
+setOptions($options);
+ if ($identity !== null) {
+ $this->setIdentity($identity);
+ }
+ if ($credential !== null) {
+ $this->setCredential($credential);
+ }
+ }
+
+ /**
+ * Returns the array of arrays of Zend\Ldap\Ldap options of this adapter.
+ *
+ * @return array|null
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * Sets the array of arrays of Zend\Ldap\Ldap options to be used by
+ * this adapter.
+ *
+ * @param array $options The array of arrays of Zend\Ldap\Ldap options
+ * @return Ldap Provides a fluent interface
+ */
+ public function setOptions($options)
+ {
+ $this->options = is_array($options) ? $options : array();
+ if (array_key_exists('identity', $this->options)) {
+ $this->options['username'] = $this->options['identity'];
+ }
+ if (array_key_exists('credential', $this->options)) {
+ $this->options['password'] = $this->options['credential'];
+ }
+ return $this;
+ }
+
+ /**
+ * Returns the username of the account being authenticated, or
+ * NULL if none is set.
+ *
+ * @return string|null
+ */
+ public function getUsername()
+ {
+ return $this->getIdentity();
+ }
+
+ /**
+ * Sets the username for binding
+ *
+ * @param string $username The username for binding
+ * @return Ldap Provides a fluent interface
+ */
+ public function setUsername($username)
+ {
+ return $this->setIdentity($username);
+ }
+
+ /**
+ * Returns the password of the account being authenticated, or
+ * NULL if none is set.
+ *
+ * @return string|null
+ */
+ public function getPassword()
+ {
+ return $this->getCredential();
+ }
+
+ /**
+ * Sets the password for the account
+ *
+ * @param string $password The password of the account being authenticated
+ * @return Ldap Provides a fluent interface
+ */
+ public function setPassword($password)
+ {
+ return $this->setCredential($password);
+ }
+
+ /**
+ * Returns the LDAP Object
+ *
+ * @return ZendLdap\Ldap The Zend\Ldap\Ldap object used to authenticate the credentials
+ */
+ public function getLdap()
+ {
+ if ($this->ldap === null) {
+ $this->ldap = new ZendLdap\Ldap();
+ }
+
+ return $this->ldap;
+ }
+
+ /**
+ * Set an Ldap connection
+ *
+ * @param ZendLdap\Ldap $ldap An existing Ldap object
+ * @return Ldap Provides a fluent interface
+ */
+ public function setLdap(ZendLdap\Ldap $ldap)
+ {
+ $this->ldap = $ldap;
+
+ $this->setOptions(array($ldap->getOptions()));
+
+ return $this;
+ }
+
+ /**
+ * Returns a domain name for the current LDAP options. This is used
+ * for skipping redundant operations (e.g. authentications).
+ *
+ * @return string
+ */
+ protected function getAuthorityName()
+ {
+ $options = $this->getLdap()->getOptions();
+ $name = $options['accountDomainName'];
+ if (!$name) {
+ $name = $options['accountDomainNameShort'];
+ }
+
+ return $name ? $name : '';
+ }
+
+ /**
+ * Authenticate the user
+ *
+ * @return AuthenticationResult
+ * @throws Exception\ExceptionInterface
+ */
+ public function authenticate()
+ {
+ $messages = array();
+ $messages[0] = ''; // reserved
+ $messages[1] = ''; // reserved
+
+ $username = $this->identity;
+ $password = $this->credential;
+
+ if (!$username) {
+ $code = AuthenticationResult::FAILURE_IDENTITY_NOT_FOUND;
+ $messages[0] = 'A username is required';
+ return new AuthenticationResult($code, '', $messages);
+ }
+ if (!$password) {
+ /* A password is required because some servers will
+ * treat an empty password as an anonymous bind.
+ */
+ $code = AuthenticationResult::FAILURE_CREDENTIAL_INVALID;
+ $messages[0] = 'A password is required';
+ return new AuthenticationResult($code, '', $messages);
+ }
+
+ $ldap = $this->getLdap();
+
+ $code = AuthenticationResult::FAILURE;
+ $messages[0] = "Authority not found: $username";
+ $failedAuthorities = array();
+
+ /* Iterate through each server and try to authenticate the supplied
+ * credentials against it.
+ */
+ foreach ($this->options as $options) {
+ if (!is_array($options)) {
+ throw new Exception\InvalidArgumentException('Adapter options array not an array');
+ }
+ $adapterOptions = $this->prepareOptions($ldap, $options);
+ $dname = '';
+
+ try {
+ if ($messages[1]) {
+ $messages[] = $messages[1];
+ }
+
+ $messages[1] = '';
+ $messages[] = $this->optionsToString($options);
+
+ $dname = $this->getAuthorityName();
+ if (isset($failedAuthorities[$dname])) {
+ /* If multiple sets of server options for the same domain
+ * are supplied, we want to skip redundant authentications
+ * where the identity or credentials where found to be
+ * invalid with another server for the same domain. The
+ * $failedAuthorities array tracks this condition (and also
+ * serves to supply the original error message).
+ * This fixes issue ZF-4093.
+ */
+ $messages[1] = $failedAuthorities[$dname];
+ $messages[] = "Skipping previously failed authority: $dname";
+ continue;
+ }
+
+ $canonicalName = $ldap->getCanonicalAccountName($username);
+ $ldap->bind($canonicalName, $password);
+ /*
+ * Fixes problem when authenticated user is not allowed to retrieve
+ * group-membership information or own account.
+ * This requires that the user specified with "username" and optionally
+ * "password" in the Zend\Ldap\Ldap options is able to retrieve the required
+ * information.
+ */
+ $requireRebind = false;
+ if (isset($options['username'])) {
+ $ldap->bind();
+ $requireRebind = true;
+ }
+ $dn = $ldap->getCanonicalAccountName($canonicalName, ZendLdap\Ldap::ACCTNAME_FORM_DN);
+
+ $groupResult = $this->checkGroupMembership($ldap, $canonicalName, $dn, $adapterOptions);
+ if ($groupResult === true) {
+ $this->authenticatedDn = $dn;
+ $messages[0] = '';
+ $messages[1] = '';
+ $messages[] = "$canonicalName authentication successful";
+ if ($requireRebind === true) {
+ // rebinding with authenticated user
+ $ldap->bind($dn, $password);
+ }
+ return new AuthenticationResult(AuthenticationResult::SUCCESS, $canonicalName, $messages);
+ } else {
+ $messages[0] = 'Account is not a member of the specified group';
+ $messages[1] = $groupResult;
+ $failedAuthorities[$dname] = $groupResult;
+ }
+ } catch (LdapException $zle) {
+ /* LDAP based authentication is notoriously difficult to diagnose. Therefore
+ * we bend over backwards to capture and record every possible bit of
+ * information when something goes wrong.
+ */
+
+ $err = $zle->getCode();
+
+ if ($err == LdapException::LDAP_X_DOMAIN_MISMATCH) {
+ /* This error indicates that the domain supplied in the
+ * username did not match the domains in the server options
+ * and therefore we should just skip to the next set of
+ * server options.
+ */
+ continue;
+ } elseif ($err == LdapException::LDAP_NO_SUCH_OBJECT) {
+ $code = AuthenticationResult::FAILURE_IDENTITY_NOT_FOUND;
+ $messages[0] = "Account not found: $username";
+ $failedAuthorities[$dname] = $zle->getMessage();
+ } elseif ($err == LdapException::LDAP_INVALID_CREDENTIALS) {
+ $code = AuthenticationResult::FAILURE_CREDENTIAL_INVALID;
+ $messages[0] = 'Invalid credentials';
+ $failedAuthorities[$dname] = $zle->getMessage();
+ } else {
+ $line = $zle->getLine();
+ $messages[] = $zle->getFile() . "($line): " . $zle->getMessage();
+ $messages[] = preg_replace(
+ '/\b'.preg_quote(substr($password, 0, 15), '/').'\b/',
+ '*****',
+ $zle->getTraceAsString()
+ );
+ $messages[0] = 'An unexpected failure occurred';
+ }
+ $messages[1] = $zle->getMessage();
+ }
+ }
+
+ $msg = isset($messages[1]) ? $messages[1] : $messages[0];
+ $messages[] = "$username authentication failed: $msg";
+
+ return new AuthenticationResult($code, $username, $messages);
+ }
+
+ /**
+ * Sets the LDAP specific options on the Zend\Ldap\Ldap instance
+ *
+ * @param ZendLdap\Ldap $ldap
+ * @param array $options
+ * @return array of auth-adapter specific options
+ */
+ protected function prepareOptions(ZendLdap\Ldap $ldap, array $options)
+ {
+ $adapterOptions = array(
+ 'group' => null,
+ 'groupDn' => $ldap->getBaseDn(),
+ 'groupScope' => ZendLdap\Ldap::SEARCH_SCOPE_SUB,
+ 'groupAttr' => 'cn',
+ 'groupFilter' => 'objectClass=groupOfUniqueNames',
+ 'memberAttr' => 'uniqueMember',
+ 'memberIsDn' => true
+ );
+ foreach ($adapterOptions as $key => $value) {
+ if (array_key_exists($key, $options)) {
+ $value = $options[$key];
+ unset($options[$key]);
+ switch ($key) {
+ case 'groupScope':
+ $value = (int) $value;
+ if (in_array(
+ $value,
+ array(
+ ZendLdap\Ldap::SEARCH_SCOPE_BASE,
+ ZendLdap\Ldap::SEARCH_SCOPE_ONE,
+ ZendLdap\Ldap::SEARCH_SCOPE_SUB,
+ ),
+ true
+ )) {
+ $adapterOptions[$key] = $value;
+ }
+ break;
+ case 'memberIsDn':
+ $adapterOptions[$key] = ($value === true ||
+ $value === '1' || strcasecmp($value, 'true') == 0);
+ break;
+ default:
+ $adapterOptions[$key] = trim($value);
+ break;
+ }
+ }
+ }
+ $ldap->setOptions($options);
+ return $adapterOptions;
+ }
+
+ /**
+ * Checks the group membership of the bound user
+ *
+ * @param ZendLdap\Ldap $ldap
+ * @param string $canonicalName
+ * @param string $dn
+ * @param array $adapterOptions
+ * @return string|true
+ */
+ protected function checkGroupMembership(ZendLdap\Ldap $ldap, $canonicalName, $dn, array $adapterOptions)
+ {
+ if ($adapterOptions['group'] === null) {
+ return true;
+ }
+
+ if ($adapterOptions['memberIsDn'] === false) {
+ $user = $canonicalName;
+ } else {
+ $user = $dn;
+ }
+
+ $groupName = ZendLdap\Filter::equals($adapterOptions['groupAttr'], $adapterOptions['group']);
+ $membership = ZendLdap\Filter::equals($adapterOptions['memberAttr'], $user);
+ $group = ZendLdap\Filter::andFilter($groupName, $membership);
+ $groupFilter = $adapterOptions['groupFilter'];
+ if (!empty($groupFilter)) {
+ $group = $group->addAnd($groupFilter);
+ }
+
+ $result = $ldap->count($group, $adapterOptions['groupDn'], $adapterOptions['groupScope']);
+
+ if ($result === 1) {
+ return true;
+ }
+
+ return 'Failed to verify group membership with ' . $group->toString();
+ }
+
+ /**
+ * getAccountObject() - Returns the result entry as a stdClass object
+ *
+ * This resembles the feature {@see Zend\Authentication\Adapter\DbTable::getResultRowObject()}.
+ * Closes ZF-6813
+ *
+ * @param array $returnAttribs
+ * @param array $omitAttribs
+ * @return stdClass|bool
+ */
+ public function getAccountObject(array $returnAttribs = array(), array $omitAttribs = array())
+ {
+ if (!$this->authenticatedDn) {
+ return false;
+ }
+
+ $returnObject = new stdClass();
+
+ $returnAttribs = array_map('strtolower', $returnAttribs);
+ $omitAttribs = array_map('strtolower', $omitAttribs);
+ $returnAttribs = array_diff($returnAttribs, $omitAttribs);
+
+ $entry = $this->getLdap()->getEntry($this->authenticatedDn, $returnAttribs, true);
+ foreach ($entry as $attr => $value) {
+ if (in_array($attr, $omitAttribs)) {
+ // skip attributes marked to be omitted
+ continue;
+ }
+ if (is_array($value)) {
+ $returnObject->$attr = (count($value) > 1) ? $value : $value[0];
+ } else {
+ $returnObject->$attr = $value;
+ }
+ }
+ return $returnObject;
+ }
+
+ /**
+ * Converts options to string
+ *
+ * @param array $options
+ * @return string
+ */
+ private function optionsToString(array $options)
+ {
+ $str = '';
+ foreach ($options as $key => $val) {
+ if ($key === 'password' || $key === 'credential') {
+ $val = '*****';
+ }
+ if ($str) {
+ $str .= ',';
+ }
+ $str .= $key . '=' . $val;
+ }
+ return $str;
+ }
+}
diff --git a/library/Zend/Authentication/Adapter/ValidatableAdapterInterface.php b/library/Zend/Authentication/Adapter/ValidatableAdapterInterface.php
new file mode 100755
index 0000000000..3c4f01b2c7
--- /dev/null
+++ b/library/Zend/Authentication/Adapter/ValidatableAdapterInterface.php
@@ -0,0 +1,45 @@
+setStorage($storage);
+ }
+ if (null !== $adapter) {
+ $this->setAdapter($adapter);
+ }
+ }
+
+ /**
+ * Returns the authentication adapter
+ *
+ * The adapter does not have a default if the storage adapter has not been set.
+ *
+ * @return Adapter\AdapterInterface|null
+ */
+ public function getAdapter()
+ {
+ return $this->adapter;
+ }
+
+ /**
+ * Sets the authentication adapter
+ *
+ * @param Adapter\AdapterInterface $adapter
+ * @return AuthenticationService Provides a fluent interface
+ */
+ public function setAdapter(Adapter\AdapterInterface $adapter)
+ {
+ $this->adapter = $adapter;
+ return $this;
+ }
+
+ /**
+ * Returns the persistent storage handler
+ *
+ * Session storage is used by default unless a different storage adapter has been set.
+ *
+ * @return Storage\StorageInterface
+ */
+ public function getStorage()
+ {
+ if (null === $this->storage) {
+ $this->setStorage(new Storage\Session());
+ }
+
+ return $this->storage;
+ }
+
+ /**
+ * Sets the persistent storage handler
+ *
+ * @param Storage\StorageInterface $storage
+ * @return AuthenticationService Provides a fluent interface
+ */
+ public function setStorage(Storage\StorageInterface $storage)
+ {
+ $this->storage = $storage;
+ return $this;
+ }
+
+ /**
+ * Authenticates against the supplied adapter
+ *
+ * @param Adapter\AdapterInterface $adapter
+ * @return Result
+ * @throws Exception\RuntimeException
+ */
+ public function authenticate(Adapter\AdapterInterface $adapter = null)
+ {
+ if (!$adapter) {
+ if (!$adapter = $this->getAdapter()) {
+ throw new Exception\RuntimeException('An adapter must be set or passed prior to calling authenticate()');
+ }
+ }
+ $result = $adapter->authenticate();
+
+ /**
+ * ZF-7546 - prevent multiple successive calls from storing inconsistent results
+ * Ensure storage has clean state
+ */
+ if ($this->hasIdentity()) {
+ $this->clearIdentity();
+ }
+
+ if ($result->isValid()) {
+ $this->getStorage()->write($result->getIdentity());
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns true if and only if an identity is available from storage
+ *
+ * @return bool
+ */
+ public function hasIdentity()
+ {
+ return !$this->getStorage()->isEmpty();
+ }
+
+ /**
+ * Returns the identity from storage or null if no identity is available
+ *
+ * @return mixed|null
+ */
+ public function getIdentity()
+ {
+ $storage = $this->getStorage();
+
+ if ($storage->isEmpty()) {
+ return null;
+ }
+
+ return $storage->read();
+ }
+
+ /**
+ * Clears the identity from persistent storage
+ *
+ * @return void
+ */
+ public function clearIdentity()
+ {
+ $this->getStorage()->clear();
+ }
+}
diff --git a/library/Zend/Authentication/AuthenticationServiceInterface.php b/library/Zend/Authentication/AuthenticationServiceInterface.php
new file mode 100755
index 0000000000..fcf74ea17e
--- /dev/null
+++ b/library/Zend/Authentication/AuthenticationServiceInterface.php
@@ -0,0 +1,44 @@
+code = (int) $code;
+ $this->identity = $identity;
+ $this->messages = $messages;
+ }
+
+ /**
+ * Returns whether the result represents a successful authentication attempt
+ *
+ * @return bool
+ */
+ public function isValid()
+ {
+ return ($this->code > 0) ? true : false;
+ }
+
+ /**
+ * getCode() - Get the result code for this authentication attempt
+ *
+ * @return int
+ */
+ public function getCode()
+ {
+ return $this->code;
+ }
+
+ /**
+ * Returns the identity used in the authentication attempt
+ *
+ * @return mixed
+ */
+ public function getIdentity()
+ {
+ return $this->identity;
+ }
+
+ /**
+ * Returns an array of string reasons why the authentication attempt was unsuccessful
+ *
+ * If authentication was successful, this method returns an empty array.
+ *
+ * @return array
+ */
+ public function getMessages()
+ {
+ return $this->messages;
+ }
+}
diff --git a/library/Zend/Authentication/Storage/Chain.php b/library/Zend/Authentication/Storage/Chain.php
new file mode 100755
index 0000000000..8d995a2c0a
--- /dev/null
+++ b/library/Zend/Authentication/Storage/Chain.php
@@ -0,0 +1,109 @@
+storageChain = new PriorityQueue();
+ }
+
+ /**
+ * @param StorageInterface $storage
+ * @param int $priority
+ */
+ public function add(StorageInterface $storage, $priority = 1)
+ {
+ $this->storageChain->insert($storage, $priority);
+ }
+
+ /**
+ * Loop over the queue of storage until a storage is found that is non-empty. If such
+ * storage is not found, then this chain storage itself is empty.
+ *
+ * In case a non-empty storage is found then this chain storage is also non-empty. Report
+ * that, but also make sure that all storage with higher priorty that are empty
+ * are filled.
+ *
+ * @see StorageInterface::isEmpty()
+ */
+ public function isEmpty()
+ {
+ $storageWithHigherPriority = array();
+
+ // Loop invariant: $storageWithHigherPriority contains all storage with higher priorty
+ // than the current one.
+ foreach ($this->storageChain as $storage) {
+ if ($storage->isEmpty()) {
+ $storageWithHigherPriority[] = $storage;
+ continue;
+ }
+
+ $storageValue = $storage->read();
+ foreach ($storageWithHigherPriority as $higherPriorityStorage) {
+ $higherPriorityStorage->write($storageValue);
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * If the chain is non-empty then the storage with the top priority is guaranteed to be
+ * filled. Return its value.
+ *
+ * @see StorageInterface::read()
+ */
+ public function read()
+ {
+ return $this->storageChain->top()->read();
+ }
+
+ /**
+ * Write the new $contents to all storage in the chain.
+ *
+ * @see StorageInterface::write()
+ */
+ public function write($contents)
+ {
+ foreach ($this->storageChain as $storage) {
+ $storage->write($contents);
+ }
+ }
+
+ /**
+ * Clear all storage in the chain.
+ *
+ * @see StorageInterface::clear()
+ */
+ public function clear()
+ {
+ foreach ($this->storageChain as $storage) {
+ $storage->clear();
+ }
+ }
+}
diff --git a/library/Zend/Authentication/Storage/NonPersistent.php b/library/Zend/Authentication/Storage/NonPersistent.php
new file mode 100755
index 0000000000..8d12049390
--- /dev/null
+++ b/library/Zend/Authentication/Storage/NonPersistent.php
@@ -0,0 +1,67 @@
+data);
+ }
+
+ /**
+ * Returns the contents of storage
+ * Behavior is undefined when storage is empty.
+ *
+ * @return mixed
+ */
+ public function read()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Writes $contents to storage
+ *
+ * @param mixed $contents
+ * @return void
+ */
+ public function write($contents)
+ {
+ $this->data = $contents;
+ }
+
+ /**
+ * Clears contents from storage
+ *
+ * @return void
+ */
+ public function clear()
+ {
+ $this->data = null;
+ }
+}
diff --git a/library/Zend/Authentication/Storage/Session.php b/library/Zend/Authentication/Storage/Session.php
new file mode 100755
index 0000000000..6c66bec8f6
--- /dev/null
+++ b/library/Zend/Authentication/Storage/Session.php
@@ -0,0 +1,126 @@
+namespace = $namespace;
+ }
+ if ($member !== null) {
+ $this->member = $member;
+ }
+ $this->session = new SessionContainer($this->namespace, $manager);
+ }
+
+ /**
+ * Returns the session namespace
+ *
+ * @return string
+ */
+ public function getNamespace()
+ {
+ return $this->namespace;
+ }
+
+ /**
+ * Returns the name of the session object member
+ *
+ * @return string
+ */
+ public function getMember()
+ {
+ return $this->member;
+ }
+
+ /**
+ * Defined by Zend\Authentication\Storage\StorageInterface
+ *
+ * @return bool
+ */
+ public function isEmpty()
+ {
+ return !isset($this->session->{$this->member});
+ }
+
+ /**
+ * Defined by Zend\Authentication\Storage\StorageInterface
+ *
+ * @return mixed
+ */
+ public function read()
+ {
+ return $this->session->{$this->member};
+ }
+
+ /**
+ * Defined by Zend\Authentication\Storage\StorageInterface
+ *
+ * @param mixed $contents
+ * @return void
+ */
+ public function write($contents)
+ {
+ $this->session->{$this->member} = $contents;
+ }
+
+ /**
+ * Defined by Zend\Authentication\Storage\StorageInterface
+ *
+ * @return void
+ */
+ public function clear()
+ {
+ unset($this->session->{$this->member});
+ }
+}
diff --git a/library/Zend/Authentication/Storage/StorageInterface.php b/library/Zend/Authentication/Storage/StorageInterface.php
new file mode 100755
index 0000000000..4a9d41b9e6
--- /dev/null
+++ b/library/Zend/Authentication/Storage/StorageInterface.php
@@ -0,0 +1,48 @@
+ 'Invalid identity',
+ self::IDENTITY_AMBIGUOUS => 'Identity is ambiguous',
+ self::CREDENTIAL_INVALID => 'Invalid password',
+ self::UNCATEGORIZED => 'Authentication failed',
+ self::GENERAL => 'Authentication failed',
+ );
+
+ /**
+ * Authentication Adapter
+ * @var ValidatableAdapterInterface
+ */
+ protected $adapter;
+
+ /**
+ * Identity (or field)
+ * @var string
+ */
+ protected $identity;
+
+ /**
+ * Credential (or field)
+ * @var string
+ */
+ protected $credential;
+
+ /**
+ * Authentication Service
+ * @var AuthenticationService
+ */
+ protected $service;
+
+ /**
+ * Sets validator options
+ *
+ * @param mixed $options
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Traversable) {
+ $options = ArrayUtils::iteratorToArray($options);
+ }
+
+ if (is_array($options)) {
+ if (array_key_exists('adapter', $options)) {
+ $this->setAdapter($options['adapter']);
+ }
+ if (array_key_exists('identity', $options)) {
+ $this->setIdentity($options['identity']);
+ }
+ if (array_key_exists('credential', $options)) {
+ $this->setCredential($options['credential']);
+ }
+ if (array_key_exists('service', $options)) {
+ $this->setService($options['service']);
+ }
+ }
+ parent::__construct($options);
+ }
+
+ /**
+ * Get Adapter
+ *
+ * @return ValidatableAdapterInterface
+ */
+ public function getAdapter()
+ {
+ return $this->adapter;
+ }
+
+ /**
+ * Set Adapter
+ *
+ * @param ValidatableAdapterInterface $adapter
+ * @return Authentication
+ */
+ public function setAdapter(ValidatableAdapterInterface $adapter)
+ {
+ $this->adapter = $adapter;
+
+ return $this;
+ }
+
+ /**
+ * Get Identity
+ *
+ * @return mixed
+ */
+ public function getIdentity()
+ {
+ return $this->identity;
+ }
+
+ /**
+ * Set Identity
+ *
+ * @param mixed $identity
+ * @return Authentication
+ */
+ public function setIdentity($identity)
+ {
+ $this->identity = $identity;
+
+ return $this;
+ }
+
+ /**
+ * Get Credential
+ *
+ * @return mixed
+ */
+ public function getCredential()
+ {
+ return $this->credential;
+ }
+
+ /**
+ * Set Credential
+ *
+ * @param mixed $credential
+ * @return Authentication
+ */
+ public function setCredential($credential)
+ {
+ $this->credential = $credential;
+
+ return $this;
+ }
+
+ /**
+ * Get Service
+ *
+ * @return AuthenticationService
+ */
+ public function getService()
+ {
+ return $this->service;
+ }
+
+ /**
+ * Set Service
+ *
+ * @param AuthenticationService $service
+ * @return Authentication
+ */
+ public function setService(AuthenticationService $service)
+ {
+ $this->service = $service;
+
+ return $this;
+ }
+
+ /**
+ * Is Valid
+ *
+ * @param mixed $value
+ * @param array $context
+ * @return bool
+ */
+ public function isValid($value = null, $context = null)
+ {
+ if ($value !== null) {
+ $this->setCredential($value);
+ }
+
+ if (($context !== null) && array_key_exists($this->identity, $context)) {
+ $identity = $context[$this->identity];
+ } else {
+ $identity = $this->identity;
+ }
+ if (!$this->identity) {
+ throw new Exception\RuntimeException('Identity must be set prior to validation');
+ }
+
+ if (($context !== null) && array_key_exists($this->credential, $context)) {
+ $credential = $context[$this->credential];
+ } else {
+ $credential = $this->credential;
+ }
+
+ if (!$this->adapter) {
+ throw new Exception\RuntimeException('Adapter must be set prior to validation');
+ }
+ $this->adapter->setIdentity($identity);
+ $this->adapter->setCredential($credential);
+
+ if (!$this->service) {
+ throw new Exception\RuntimeException('AuthenticationService must be set prior to validation');
+ }
+ $result = $this->service->authenticate($this->adapter);
+
+ if ($result->getCode() != Result::SUCCESS) {
+ switch ($result->getCode()) {
+ case Result::FAILURE_IDENTITY_NOT_FOUND:
+ $this->error(self::IDENTITY_NOT_FOUND);
+ break;
+ case Result::FAILURE_CREDENTIAL_INVALID:
+ $this->error(self::CREDENTIAL_INVALID);
+ break;
+ case Result::FAILURE_IDENTITY_AMBIGUOUS:
+ $this->error(self::IDENTITY_AMBIGUOUS);
+ break;
+ case Result::FAILURE_UNCATEGORIZED:
+ $this->error(self::UNCATEGORIZED);
+ break;
+ default:
+ $this->error(self::GENERAL);
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/library/Zend/Authentication/composer.json b/library/Zend/Authentication/composer.json
new file mode 100755
index 0000000000..fd6d242b99
--- /dev/null
+++ b/library/Zend/Authentication/composer.json
@@ -0,0 +1,44 @@
+{
+ "name": "zendframework/zend-authentication",
+ "description": "provides an API for authentication and includes concrete authentication adapters for common use case scenarios",
+ "license": "BSD-3-Clause",
+ "keywords": [
+ "zf2",
+ "authentication"
+ ],
+ "homepage": "https://github.com/zendframework/zf2",
+ "autoload": {
+ "psr-0": {
+ "Zend\\Authentication\\": ""
+ }
+ },
+ "target-dir": "Zend/Authentication",
+ "require": {
+ "php": ">=5.3.23",
+ "zendframework/zend-stdlib": "self.version"
+ },
+ "require-dev": {
+ "zendframework/zend-db": "self.version",
+ "zendframework/zend-crypt": "self.version",
+ "zendframework/zend-http": "self.version",
+ "zendframework/zend-ldap": "self.version",
+ "zendframework/zend-session": "self.version",
+ "zendframework/zend-validator": "self.version",
+ "zendframework/zend-uri": "self.version"
+ },
+ "suggest": {
+ "zendframework/zend-db": "Zend\\Db component",
+ "zendframework/zend-crypt": "Zend\\Crypt component",
+ "zendframework/zend-http": "Zend\\Http component",
+ "zendframework/zend-ldap": "Zend\\Ldap component",
+ "zendframework/zend-session": "Zend\\Session component",
+ "zendframework/zend-uri": "Zend\\Uri component",
+ "zendframework/zend-validator": "Zend\\Validator component"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev",
+ "dev-develop": "2.4-dev"
+ }
+ }
+}
diff --git a/library/Zend/Barcode/Barcode.php b/library/Zend/Barcode/Barcode.php
new file mode 100755
index 0000000000..b2176b478a
--- /dev/null
+++ b/library/Zend/Barcode/Barcode.php
@@ -0,0 +1,304 @@
+ $e->getMessage()));
+ $renderer = static::makeRenderer($renderer, array());
+ } else {
+ throw $e;
+ }
+ }
+
+ $renderer->setAutomaticRenderError($automaticRenderError);
+ return $renderer->setBarcode($barcode);
+ }
+
+ /**
+ * Barcode Constructor
+ *
+ * @param mixed $barcode String name of barcode class, or Traversable object, or barcode object.
+ * @param mixed $barcodeConfig OPTIONAL; an array or Traversable object with barcode parameters.
+ * @throws Exception\InvalidArgumentException
+ * @return Object
+ */
+ public static function makeBarcode($barcode, $barcodeConfig = array())
+ {
+ if ($barcode instanceof Object\ObjectInterface) {
+ return $barcode;
+ }
+
+ /*
+ * Convert Traversable argument to plain string
+ * barcode name and separate configuration.
+ */
+ if ($barcode instanceof Traversable) {
+ $barcode = ArrayUtils::iteratorToArray($barcode);
+ if (isset($barcode['barcodeParams']) && is_array($barcode['barcodeParams'])) {
+ $barcodeConfig = $barcode['barcodeParams'];
+ }
+ if (isset($barcode['barcode'])) {
+ $barcode = (string) $barcode['barcode'];
+ } else {
+ $barcode = null;
+ }
+ }
+ if ($barcodeConfig instanceof Traversable) {
+ $barcodeConfig = ArrayUtils::iteratorToArray($barcodeConfig);
+ }
+
+ /*
+ * Verify that barcode parameters are in an array.
+ */
+ if (!is_array($barcodeConfig)) {
+ throw new Exception\InvalidArgumentException(
+ 'Barcode parameters must be in an array or a Traversable object'
+ );
+ }
+
+ /*
+ * Verify that a barcode name has been specified.
+ */
+ if (!is_string($barcode) || empty($barcode)) {
+ throw new Exception\InvalidArgumentException(
+ 'Barcode name must be specified in a string'
+ );
+ }
+
+ return static::getObjectPluginManager()->get($barcode, $barcodeConfig);
+ }
+
+ /**
+ * Renderer Constructor
+ *
+ * @param mixed $renderer String name of renderer class, or Traversable object.
+ * @param mixed $rendererConfig OPTIONAL; an array or Traversable object with renderer parameters.
+ * @throws Exception\RendererCreationException
+ * @return Renderer\RendererInterface
+ */
+ public static function makeRenderer($renderer = 'image', $rendererConfig = array())
+ {
+ if ($renderer instanceof Renderer\RendererInterface) {
+ return $renderer;
+ }
+
+ /*
+ * Convert Traversable argument to plain string
+ * barcode name and separate config object.
+ */
+ if ($renderer instanceof Traversable) {
+ $renderer = ArrayUtils::iteratorToArray($renderer);
+ if (isset($renderer['rendererParams'])) {
+ $rendererConfig = $renderer['rendererParams'];
+ }
+ if (isset($renderer['renderer'])) {
+ $renderer = (string) $renderer['renderer'];
+ }
+ }
+ if ($rendererConfig instanceof Traversable) {
+ $rendererConfig = ArrayUtils::iteratorToArray($rendererConfig);
+ }
+
+ /*
+ * Verify that barcode parameters are in an array.
+ */
+ if (!is_array($rendererConfig)) {
+ throw new Exception\RendererCreationException(
+ 'Barcode parameters must be in an array or a Traversable object'
+ );
+ }
+
+ /*
+ * Verify that a barcode name has been specified.
+ */
+ if (!is_string($renderer) || empty($renderer)) {
+ throw new Exception\RendererCreationException(
+ 'Renderer name must be specified in a string'
+ );
+ }
+
+ return static::getRendererPluginManager()->get($renderer, $rendererConfig);
+ }
+
+ /**
+ * Proxy to renderer render() method
+ *
+ * @param string | Object\ObjectInterface | array | Traversable $barcode
+ * @param string | Renderer\RendererInterface $renderer
+ * @param array | Traversable $barcodeConfig
+ * @param array | Traversable $rendererConfig
+ */
+ public static function render(
+ $barcode,
+ $renderer,
+ $barcodeConfig = array(),
+ $rendererConfig = array()
+ ) {
+ static::factory($barcode, $renderer, $barcodeConfig, $rendererConfig)->render();
+ }
+
+ /**
+ * Proxy to renderer draw() method
+ *
+ * @param string | Object\ObjectInterface | array | Traversable $barcode
+ * @param string | Renderer\RendererInterface $renderer
+ * @param array | Traversable $barcodeConfig
+ * @param array | Traversable $rendererConfig
+ * @return mixed
+ */
+ public static function draw(
+ $barcode,
+ $renderer,
+ $barcodeConfig = array(),
+ $rendererConfig = array()
+ ) {
+ return static::factory($barcode, $renderer, $barcodeConfig, $rendererConfig)->draw();
+ }
+
+ /**
+ * Set the default font for new instances of barcode
+ *
+ * @param string $font
+ * @return void
+ */
+ public static function setBarcodeFont($font)
+ {
+ static::$staticFont = $font;
+ }
+
+ /**
+ * Get current default font
+ *
+ * @return string
+ */
+ public static function getBarcodeFont()
+ {
+ return static::$staticFont;
+ }
+}
diff --git a/library/Zend/Barcode/CONTRIBUTING.md b/library/Zend/Barcode/CONTRIBUTING.md
new file mode 100755
index 0000000000..e77f5d2d5b
--- /dev/null
+++ b/library/Zend/Barcode/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# CONTRIBUTING
+
+Please don't open pull requests against this repository, please use https://github.com/zendframework/zf2.
\ No newline at end of file
diff --git a/library/Zend/Barcode/Exception/ExceptionInterface.php b/library/Zend/Barcode/Exception/ExceptionInterface.php
new file mode 100755
index 0000000000..f421ceb947
--- /dev/null
+++ b/library/Zend/Barcode/Exception/ExceptionInterface.php
@@ -0,0 +1,17 @@
+getDefaultOptions();
+ $this->font = Barcode\Barcode::getBarcodeFont();
+ if ($options instanceof Traversable) {
+ $options = ArrayUtils::iteratorToArray($options);
+ }
+ if (is_array($options)) {
+ $this->setOptions($options);
+ }
+ $this->type = strtolower(substr(get_class($this), strlen($this->barcodeNamespace) + 1));
+ if ($this->mandatoryChecksum) {
+ $this->withChecksum = true;
+ $this->withChecksumInText = true;
+ }
+ }
+
+ /**
+ * Set default options for particular object
+ * @return void
+ */
+ protected function getDefaultOptions()
+ {
+ }
+
+ /**
+ * Set barcode state from options array
+ * @param array $options
+ * @return \Zend\Barcode\Object\ObjectInterface
+ */
+ public function setOptions($options)
+ {
+ foreach ($options as $key => $value) {
+ $method = 'set' . $key;
+ if (method_exists($this, $method)) {
+ $this->$method($value);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Set barcode namespace for autoloading
+ *
+ * @param string $namespace
+ * @return \Zend\Barcode\Object\ObjectInterface
+ */
+ public function setBarcodeNamespace($namespace)
+ {
+ $this->barcodeNamespace = $namespace;
+ return $this;
+ }
+
+ /**
+ * Retrieve barcode namespace
+ *
+ * @return string
+ */
+ public function getBarcodeNamespace()
+ {
+ return $this->barcodeNamespace;
+ }
+
+ /**
+ * Retrieve type of barcode
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * Set height of the barcode bar
+ * @param int $value
+ * @return \Zend\Barcode\Object\ObjectInterface
+ * @throws \Zend\Barcode\Object\Exception\ExceptionInterface
+ */
+ public function setBarHeight($value)
+ {
+ if (intval($value) <= 0) {
+ throw new Exception\OutOfRangeException(
+ 'Bar height must be greater than 0'
+ );
+ }
+ $this->barHeight = intval($value);
+ return $this;
+ }
+
+ /**
+ * Get height of the barcode bar
+ * @return int
+ */
+ public function getBarHeight()
+ {
+ return $this->barHeight;
+ }
+
+ /**
+ * Set thickness of thin bar
+ * @param int $value
+ * @return \Zend\Barcode\Object\ObjectInterface
+ * @throws \Zend\Barcode\Object\Exception\ExceptionInterface
+ */
+ public function setBarThinWidth($value)
+ {
+ if (intval($value) <= 0) {
+ throw new Exception\OutOfRangeException(
+ 'Bar width must be greater than 0'
+ );
+ }
+ $this->barThinWidth = intval($value);
+ return $this;
+ }
+
+ /**
+ * Get thickness of thin bar
+ * @return int
+ */
+ public function getBarThinWidth()
+ {
+ return $this->barThinWidth;
+ }
+
+ /**
+ * Set thickness of thick bar
+ * @param int $value
+ * @return \Zend\Barcode\Object\ObjectInterface
+ * @throws \Zend\Barcode\Object\Exception\ExceptionInterface
+ */
+ public function setBarThickWidth($value)
+ {
+ if (intval($value) <= 0) {
+ throw new Exception\OutOfRangeException(
+ 'Bar width must be greater than 0'
+ );
+ }
+ $this->barThickWidth = intval($value);
+ return $this;
+ }
+
+ /**
+ * Get thickness of thick bar
+ * @return int
+ */
+ public function getBarThickWidth()
+ {
+ return $this->barThickWidth;
+ }
+
+ /**
+ * Set factor applying to
+ * thinBarWidth - thickBarWidth - barHeight - fontSize
+ * @param float $value
+ * @return \Zend\Barcode\Object\ObjectInterface
+ * @throws \Zend\Barcode\Object\Exception\ExceptionInterface
+ */
+ public function setFactor($value)
+ {
+ if (floatval($value) <= 0) {
+ throw new Exception\OutOfRangeException(
+ 'Factor must be greater than 0'
+ );
+ }
+ $this->factor = floatval($value);
+ return $this;
+ }
+
+ /**
+ * Get factor applying to
+ * thinBarWidth - thickBarWidth - barHeight - fontSize
+ * @return int
+ */
+ public function getFactor()
+ {
+ return $this->factor;
+ }
+
+ /**
+ * Set color of the barcode and text
+ * @param string $value
+ * @return \Zend\Barcode\Object\ObjectInterface
+ * @throws \Zend\Barcode\Object\Exception\ExceptionInterface
+ */
+ public function setForeColor($value)
+ {
+ if (preg_match('`\#[0-9A-F]{6}`', $value)) {
+ $this->foreColor = hexdec($value);
+ } elseif (is_numeric($value) && $value >= 0 && $value <= 16777125) {
+ $this->foreColor = intval($value);
+ } else {
+ throw new Exception\InvalidArgumentException(
+ 'Text color must be set as #[0-9A-F]{6}'
+ );
+ }
+ return $this;
+ }
+
+ /**
+ * Retrieve color of the barcode and text
+ * @return int
+ */
+ public function getForeColor()
+ {
+ return $this->foreColor;
+ }
+
+ /**
+ * Set the color of the background
+ * @param int $value
+ * @return \Zend\Barcode\Object\ObjectInterface
+ * @throws \Zend\Barcode\Object\Exception\ExceptionInterface
+ */
+ public function setBackgroundColor($value)
+ {
+ if (preg_match('`\#[0-9A-F]{6}`', $value)) {
+ $this->backgroundColor = hexdec($value);
+ } elseif (is_numeric($value) && $value >= 0 && $value <= 16777125) {
+ $this->backgroundColor = intval($value);
+ } else {
+ throw new Exception\InvalidArgumentException(
+ 'Background color must be set as #[0-9A-F]{6}'
+ );
+ }
+ return $this;
+ }
+
+ /**
+ * Retrieve background color of the image
+ * @return int
+ */
+ public function getBackgroundColor()
+ {
+ return $this->backgroundColor;
+ }
+
+ /**
+ * Activate/deactivate drawing of the bar
+ * @param bool $value
+ * @return \Zend\Barcode\Object\ObjectInterface
+ */
+ public function setWithBorder($value)
+ {
+ $this->withBorder = (bool) $value;
+ return $this;
+ }
+
+ /**
+ * Retrieve if border are draw or not
+ * @return bool
+ */
+ public function getWithBorder()
+ {
+ return $this->withBorder;
+ }
+
+ /**
+ * Activate/deactivate drawing of the quiet zones
+ * @param bool $value
+ * @return AbstractObject
+ */
+ public function setWithQuietZones($value)
+ {
+ $this->withQuietZones = (bool) $value;
+ return $this;
+ }
+
+ /**
+ * Retrieve if quiet zones are draw or not
+ * @return bool
+ */
+ public function getWithQuietZones()
+ {
+ return $this->withQuietZones;
+ }
+
+ /**
+ * Allow fast inversion of font/bars color and background color
+ * @return \Zend\Barcode\Object\ObjectInterface
+ */
+ public function setReverseColor()
+ {
+ $tmp = $this->foreColor;
+ $this->foreColor = $this->backgroundColor;
+ $this->backgroundColor = $tmp;
+ return $this;
+ }
+
+ /**
+ * Set orientation of barcode and text
+ * @param float $value
+ * @return \Zend\Barcode\Object\ObjectInterface
+ * @throws \Zend\Barcode\Object\Exception\ExceptionInterface
+ */
+ public function setOrientation($value)
+ {
+ $this->orientation = floatval($value) - floor(floatval($value) / 360) * 360;
+ return $this;
+ }
+
+ /**
+ * Retrieve orientation of barcode and text
+ * @return float
+ */
+ public function getOrientation()
+ {
+ return $this->orientation;
+ }
+
+ /**
+ * Set text to encode
+ * @param string $value
+ * @return \Zend\Barcode\Object\ObjectInterface
+ */
+ public function setText($value)
+ {
+ $this->text = trim($value);
+ return $this;
+ }
+
+ /**
+ * Retrieve text to encode
+ * @return string
+ */
+ public function getText()
+ {
+ $text = $this->text;
+ if ($this->withChecksum) {
+ $text .= $this->getChecksum($this->text);
+ }
+ return $this->addLeadingZeros($text);
+ }
+
+ /**
+ * Automatically add leading zeros if barcode length is fixed
+ * @param string $text
+ * @param bool $withoutChecksum
+ * @return string
+ */
+ protected function addLeadingZeros($text, $withoutChecksum = false)
+ {
+ if ($this->barcodeLength && $this->addLeadingZeros) {
+ $omitChecksum = (int) ($this->withChecksum && $withoutChecksum);
+ if (is_int($this->barcodeLength)) {
+ $length = $this->barcodeLength - $omitChecksum;
+ if (strlen($text) < $length) {
+ $text = str_repeat('0', $length - strlen($text)) . $text;
+ }
+ } else {
+ if ($this->barcodeLength == 'even') {
+ $text = ((strlen($text) - $omitChecksum) % 2 ? '0' . $text : $text);
+ }
+ }
+ }
+ return $text;
+ }
+
+ /**
+ * Retrieve text to encode
+ * @return string
+ */
+ public function getRawText()
+ {
+ return $this->text;
+ }
+
+ /**
+ * Retrieve text to display
+ * @return string
+ */
+ public function getTextToDisplay()
+ {
+ if ($this->withChecksumInText) {
+ return $this->getText();
+ }
+
+ return $this->addLeadingZeros($this->text, true);
+ }
+
+ /**
+ * Activate/deactivate drawing of text to encode
+ * @param bool $value
+ * @return \Zend\Barcode\Object\ObjectInterface
+ */
+ public function setDrawText($value)
+ {
+ $this->drawText = (bool) $value;
+ return $this;
+ }
+
+ /**
+ * Retrieve if drawing of text to encode is enabled
+ * @return bool
+ */
+ public function getDrawText()
+ {
+ return $this->drawText;
+ }
+
+ /**
+ * Activate/deactivate the adjustment of the position
+ * of the characters to the position of the bars
+ * @param bool $value
+ * @return \Zend\Barcode\Object\ObjectInterface
+ * @throws \Zend\Barcode\Object\Exception\ExceptionInterface
+ */
+ public function setStretchText($value)
+ {
+ $this->stretchText = (bool) $value;
+ return $this;
+ }
+
+ /**
+ * Retrieve if the adjustment of the position of the characters
+ * to the position of the bars is enabled
+ * @return bool
+ */
+ public function getStretchText()
+ {
+ return $this->stretchText;
+ }
+
+ /**
+ * Activate/deactivate the automatic generation
+ * of the checksum character
+ * added to the barcode text
+ * @param bool $value
+ * @return \Zend\Barcode\Object\ObjectInterface
+ */
+ public function setWithChecksum($value)
+ {
+ if (!$this->mandatoryChecksum) {
+ $this->withChecksum = (bool) $value;
+ }
+ return $this;
+ }
+
+ /**
+ * Retrieve if the checksum character is automatically
+ * added to the barcode text
+ * @return bool
+ */
+ public function getWithChecksum()
+ {
+ return $this->withChecksum;
+ }
+
+ /**
+ * Activate/deactivate the automatic generation
+ * of the checksum character
+ * added to the barcode text
+ * @param bool $value
+ * @return \Zend\Barcode\Object\ObjectInterface
+ * @throws \Zend\Barcode\Object\Exception\ExceptionInterface
+ */
+ public function setWithChecksumInText($value)
+ {
+ if (!$this->mandatoryChecksum) {
+ $this->withChecksumInText = (bool) $value;
+ }
+ return $this;
+ }
+
+ /**
+ * Retrieve if the checksum character is automatically
+ * added to the barcode text
+ * @return bool
+ */
+ public function getWithChecksumInText()
+ {
+ return $this->withChecksumInText;
+ }
+
+ /**
+ * Set the font:
+ * - if integer between 1 and 5, use gd built-in fonts
+ * - if string, $value is assumed to be the path to a TTF font
+ * @param int|string $value
+ * @return \Zend\Barcode\Object\ObjectInterface
+ * @throws \Zend\Barcode\Object\Exception\ExceptionInterface
+ */
+ public function setFont($value)
+ {
+ if (is_int($value) && $value >= 1 && $value <= 5) {
+ if (!extension_loaded('gd')) {
+ throw new Exception\ExtensionNotLoadedException(
+ 'GD extension is required to use numeric font'
+ );
+ }
+
+ // Case of numeric font with GD
+ $this->font = $value;
+
+ // In this case font size is given by:
+ $this->fontSize = imagefontheight($value);
+ } elseif (is_string($value)) {
+ $this->font = $value;
+ } else {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Invalid font "%s" provided to setFont()',
+ $value
+ ));
+ }
+ return $this;
+ }
+
+ /**
+ * Retrieve the font
+ * @return int|string
+ */
+ public function getFont()
+ {
+ return $this->font;
+ }
+
+ /**
+ * Set the size of the font in case of TTF
+ * @param float $value
+ * @return \Zend\Barcode\Object\ObjectInterface
+ * @throws \Zend\Barcode\Object\Exception\ExceptionInterface
+ */
+ public function setFontSize($value)
+ {
+ if (is_numeric($this->font)) {
+ // Case of numeric font with GD
+ return $this;
+ }
+
+ if (!is_numeric($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Font size must be a numeric value'
+ );
+ }
+
+ $this->fontSize = $value;
+ return $this;
+ }
+
+ /**
+ * Retrieve the size of the font in case of TTF
+ * @return float
+ */
+ public function getFontSize()
+ {
+ return $this->fontSize;
+ }
+
+ /**
+ * Quiet zone before first bar
+ * and after the last bar
+ * @return int
+ */
+ public function getQuietZone()
+ {
+ if ($this->withQuietZones || $this->mandatoryQuietZones) {
+ return 10 * $this->barThinWidth * $this->factor;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Add an instruction in the array of instructions
+ * @param array $instruction
+ */
+ protected function addInstruction(array $instruction)
+ {
+ $this->instructions[] = $instruction;
+ }
+
+ /**
+ * Retrieve the set of drawing instructions
+ * @return array
+ */
+ public function getInstructions()
+ {
+ return $this->instructions;
+ }
+
+ /**
+ * Add a polygon drawing instruction in the set of instructions
+ * @param array $points
+ * @param int $color
+ * @param bool $filled
+ */
+ protected function addPolygon(array $points, $color = null, $filled = true)
+ {
+ if ($color === null) {
+ $color = $this->foreColor;
+ }
+ $this->addInstruction(array(
+ 'type' => 'polygon',
+ 'points' => $points,
+ 'color' => $color,
+ 'filled' => $filled,
+ ));
+ }
+
+ /**
+ * Add a text drawing instruction in the set of instructions
+ * @param string $text
+ * @param float $size
+ * @param int[] $position
+ * @param string $font
+ * @param int $color
+ * @param string $alignment
+ * @param float $orientation
+ */
+ protected function addText(
+ $text,
+ $size,
+ $position,
+ $font,
+ $color,
+ $alignment = 'center',
+ $orientation = 0
+ ) {
+ if ($color === null) {
+ $color = $this->foreColor;
+ }
+ $this->addInstruction(array(
+ 'type' => 'text',
+ 'text' => $text,
+ 'size' => $size,
+ 'position' => $position,
+ 'font' => $font,
+ 'color' => $color,
+ 'alignment' => $alignment,
+ 'orientation' => $orientation,
+ ));
+ }
+
+ /**
+ * Checking of parameters after all settings
+ * @return bool
+ */
+ public function checkParams()
+ {
+ $this->checkText();
+ $this->checkFontAndOrientation();
+ $this->checkSpecificParams();
+ return true;
+ }
+
+ /**
+ * Check if a text is really provided to barcode
+ * @param string|null $value
+ * @return void
+ * @throws \Zend\Barcode\Object\Exception\ExceptionInterface
+ */
+ protected function checkText($value = null)
+ {
+ if ($value === null) {
+ $value = $this->text;
+ }
+ if (!strlen($value)) {
+ throw new Exception\RuntimeException(
+ 'A text must be provide to Barcode before drawing'
+ );
+ }
+ $this->validateText($value);
+ }
+
+ /**
+ * Check the ratio between the thick and the thin bar
+ * @param int $min
+ * @param int $max
+ * @return void
+ * @throws \Zend\Barcode\Object\Exception\ExceptionInterface
+ */
+ protected function checkRatio($min = 2, $max = 3)
+ {
+ $ratio = $this->barThickWidth / $this->barThinWidth;
+ if (!($ratio >= $min && $ratio <= $max)) {
+ throw new Exception\OutOfRangeException(sprintf(
+ 'Ratio thick/thin bar must be between %0.1f and %0.1f (actual %0.3f)',
+ $min,
+ $max,
+ $ratio
+ ));
+ }
+ }
+
+ /**
+ * Drawing with an angle is just allow TTF font
+ * @return void
+ * @throws \Zend\Barcode\Object\Exception\ExceptionInterface
+ */
+ protected function checkFontAndOrientation()
+ {
+ if (is_numeric($this->font) && $this->orientation != 0) {
+ throw new Exception\RuntimeException(
+ 'Only drawing with TTF font allow orientation of the barcode.'
+ );
+ }
+ }
+
+ /**
+ * Width of the result image
+ * (before any rotation)
+ * @return int
+ */
+ protected function calculateWidth()
+ {
+ return (int) $this->withBorder
+ + $this->calculateBarcodeWidth()
+ + (int) $this->withBorder;
+ }
+
+ /**
+ * Calculate the width of the barcode
+ * @return int
+ */
+ abstract protected function calculateBarcodeWidth();
+
+ /**
+ * Height of the result object
+ * @return int
+ */
+ protected function calculateHeight()
+ {
+ return (int) $this->withBorder * 2
+ + $this->calculateBarcodeHeight()
+ + (int) $this->withBorder * 2;
+ }
+
+ /**
+ * Height of the barcode
+ * @return int
+ */
+ protected function calculateBarcodeHeight()
+ {
+ $textHeight = 0;
+ $extraHeight = 0;
+ if ($this->drawText) {
+ $textHeight += $this->fontSize;
+ $extraHeight = 2;
+ }
+ return ($this->barHeight + $textHeight) * $this->factor + $extraHeight;
+ }
+
+ /**
+ * Get height of the result object
+ * @param bool $recalculate
+ * @return int
+ */
+ public function getHeight($recalculate = false)
+ {
+ if ($this->height === null || $recalculate) {
+ $this->height =
+ abs($this->calculateHeight() * cos($this->orientation / 180 * pi()))
+ + abs($this->calculateWidth() * sin($this->orientation / 180 * pi()));
+ }
+ return $this->height;
+ }
+
+ /**
+ * Get width of the result object
+ * @param bool $recalculate
+ * @return int
+ */
+ public function getWidth($recalculate = false)
+ {
+ if ($this->width === null || $recalculate) {
+ $this->width =
+ abs($this->calculateWidth() * cos($this->orientation / 180 * pi()))
+ + abs($this->calculateHeight() * sin($this->orientation / 180 * pi()));
+ }
+ return $this->width;
+ }
+
+ /**
+ * Calculate the offset from the left of the object
+ * if an orientation is activated
+ * @param bool $recalculate
+ * @return float
+ */
+ public function getOffsetLeft($recalculate = false)
+ {
+ if ($this->offsetLeft === null || $recalculate) {
+ $this->offsetLeft = - min(
+ array(
+ 0 * cos($this->orientation / 180 * pi()) - 0 * sin($this->orientation / 180 * pi()),
+ 0 * cos($this->orientation / 180 * pi()) - $this->calculateBarcodeHeight() * sin($this->orientation / 180 * pi()),
+ $this->calculateBarcodeWidth() * cos($this->orientation / 180 * pi()) - $this->calculateBarcodeHeight() * sin($this->orientation / 180 * pi()),
+ $this->calculateBarcodeWidth() * cos($this->orientation / 180 * pi()) - 0 * sin($this->orientation / 180 * pi()),
+ )
+ );
+ }
+ return $this->offsetLeft;
+ }
+
+ /**
+ * Calculate the offset from the top of the object
+ * if an orientation is activated
+ * @param bool $recalculate
+ * @return float
+ */
+ public function getOffsetTop($recalculate = false)
+ {
+ if ($this->offsetTop === null || $recalculate) {
+ $this->offsetTop = - min(
+ array(
+ 0 * cos($this->orientation / 180 * pi()) + 0 * sin($this->orientation / 180 * pi()),
+ $this->calculateBarcodeHeight() * cos($this->orientation / 180 * pi()) + 0 * sin($this->orientation / 180 * pi()),
+ $this->calculateBarcodeHeight() * cos($this->orientation / 180 * pi()) + $this->calculateBarcodeWidth() * sin($this->orientation / 180 * pi()),
+ 0 * cos($this->orientation / 180 * pi()) + $this->calculateBarcodeWidth() * sin($this->orientation / 180 * pi()),
+ )
+ );
+ }
+ return $this->offsetTop;
+ }
+
+ /**
+ * Apply rotation on a point in X/Y dimensions
+ * @param float $x1 x-position before rotation
+ * @param float $y1 y-position before rotation
+ * @return int[] Array of two elements corresponding to the new XY point
+ */
+ protected function rotate($x1, $y1)
+ {
+ $x2 = $x1 * cos($this->orientation / 180 * pi())
+ - $y1 * sin($this->orientation / 180 * pi())
+ + $this->getOffsetLeft();
+ $y2 = $y1 * cos($this->orientation / 180 * pi())
+ + $x1 * sin($this->orientation / 180 * pi())
+ + $this->getOffsetTop();
+ return array(intval($x2), intval($y2));
+ }
+
+ /**
+ * Complete drawing of the barcode
+ * @return array Table of instructions
+ */
+ public function draw()
+ {
+ $this->checkParams();
+ $this->drawBarcode();
+ $this->drawBorder();
+ $this->drawText();
+ return $this->getInstructions();
+ }
+
+ /**
+ * Draw the barcode
+ * @return void
+ */
+ protected function drawBarcode()
+ {
+ $barcodeTable = $this->prepareBarcode();
+
+ $this->preDrawBarcode();
+
+ $xpos = (int) $this->withBorder;
+ $ypos = (int) $this->withBorder;
+
+ $point1 = $this->rotate(0, 0);
+ $point2 = $this->rotate(0, $this->calculateHeight() - 1);
+ $point3 = $this->rotate(
+ $this->calculateWidth() - 1,
+ $this->calculateHeight() - 1
+ );
+ $point4 = $this->rotate($this->calculateWidth() - 1, 0);
+
+ $this->addPolygon(array(
+ $point1,
+ $point2,
+ $point3,
+ $point4
+ ), $this->backgroundColor);
+
+ $xpos += $this->getQuietZone();
+ $barLength = $this->barHeight * $this->factor;
+
+ foreach ($barcodeTable as $bar) {
+ $width = $bar[1] * $this->factor;
+ if ($bar[0]) {
+ $point1 = $this->rotate($xpos, $ypos + $bar[2] * $barLength);
+ $point2 = $this->rotate($xpos, $ypos + $bar[3] * $barLength);
+ $point3 = $this->rotate(
+ $xpos + $width - 1,
+ $ypos + $bar[3] * $barLength
+ );
+ $point4 = $this->rotate(
+ $xpos + $width - 1,
+ $ypos + $bar[2] * $barLength
+ );
+ $this->addPolygon(array(
+ $point1,
+ $point2,
+ $point3,
+ $point4,
+ ));
+ }
+ $xpos += $width;
+ }
+
+ $this->postDrawBarcode();
+ }
+
+ /**
+ * Partial function to draw border
+ * @return void
+ */
+ protected function drawBorder()
+ {
+ if ($this->withBorder) {
+ $point1 = $this->rotate(0, 0);
+ $point2 = $this->rotate($this->calculateWidth() - 1, 0);
+ $point3 = $this->rotate(
+ $this->calculateWidth() - 1,
+ $this->calculateHeight() - 1
+ );
+ $point4 = $this->rotate(0, $this->calculateHeight() - 1);
+ $this->addPolygon(array(
+ $point1,
+ $point2,
+ $point3,
+ $point4,
+ $point1,
+ ), $this->foreColor, false);
+ }
+ }
+
+ /**
+ * Partial function to draw text
+ * @return void
+ */
+ protected function drawText()
+ {
+ if ($this->drawText) {
+ $text = $this->getTextToDisplay();
+ if ($this->stretchText) {
+ $textLength = strlen($text);
+ $space = ($this->calculateWidth() - 2 * $this->getQuietZone()) / $textLength;
+ for ($i = 0; $i < $textLength; $i ++) {
+ $leftPosition = $this->getQuietZone() + $space * ($i + 0.5);
+ $this->addText(
+ $text{$i},
+ $this->fontSize * $this->factor,
+ $this->rotate(
+ $leftPosition,
+ (int) $this->withBorder * 2 + $this->factor * ($this->barHeight + $this->fontSize) + 1
+ ),
+ $this->font,
+ $this->foreColor,
+ 'center',
+ - $this->orientation
+ );
+ }
+ } else {
+ $this->addText(
+ $text,
+ $this->fontSize * $this->factor,
+ $this->rotate(
+ $this->calculateWidth() / 2,
+ (int) $this->withBorder * 2 + $this->factor * ($this->barHeight + $this->fontSize) + 1
+ ),
+ $this->font,
+ $this->foreColor,
+ 'center',
+ - $this->orientation
+ );
+ }
+ }
+ }
+
+ /**
+ * Check for invalid characters
+ * @param string $value Text to be checked
+ * @return void
+ */
+ public function validateText($value)
+ {
+ $this->validateSpecificText($value);
+ }
+
+ /**
+ * Standard validation for most of barcode objects
+ * @param string $value
+ * @param array $options
+ */
+ protected function validateSpecificText($value, $options = array())
+ {
+ $validatorName = (isset($options['validator'])) ? $options['validator'] : $this->getType();
+
+ $validator = new BarcodeValidator(array(
+ 'adapter' => $validatorName,
+ 'usechecksum' => false,
+ ));
+
+ $checksumCharacter = '';
+ $withChecksum = false;
+ if ($this->mandatoryChecksum) {
+ $checksumCharacter = $this->substituteChecksumCharacter;
+ $withChecksum = true;
+ }
+
+ $value = $this->addLeadingZeros($value, $withChecksum) . $checksumCharacter;
+
+ if (!$validator->isValid($value)) {
+ $message = implode("\n", $validator->getMessages());
+ throw new Exception\BarcodeValidationException($message);
+ }
+ }
+
+ /**
+ * Each child must prepare the barcode and return
+ * a table like array(
+ * 0 => array(
+ * 0 => int (visible(black) or not(white))
+ * 1 => int (width of the bar)
+ * 2 => float (0->1 position from the top of the beginning of the bar in %)
+ * 3 => float (0->1 position from the top of the end of the bar in %)
+ * ),
+ * 1 => ...
+ * )
+ *
+ * @return array
+ */
+ abstract protected function prepareBarcode();
+
+ /**
+ * Checking of parameters after all settings
+ *
+ * @return void
+ */
+ abstract protected function checkSpecificParams();
+
+ /**
+ * Allow each child to draw something else
+ *
+ * @return void
+ */
+ protected function preDrawBarcode()
+ {
+ }
+
+ /**
+ * Allow each child to draw something else
+ * (ex: bearer bars in interleaved 2 of 5 code)
+ *
+ * @return void
+ */
+ protected function postDrawBarcode()
+ {
+ }
+}
diff --git a/library/Zend/Barcode/Object/Codabar.php b/library/Zend/Barcode/Object/Codabar.php
new file mode 100755
index 0000000000..71120c6015
--- /dev/null
+++ b/library/Zend/Barcode/Object/Codabar.php
@@ -0,0 +1,77 @@
+ "101010011", '1' => "101011001", '2' => "101001011",
+ '3' => "110010101", '4' => "101101001", '5' => "110101001",
+ '6' => "100101011", '7' => "100101101", '8' => "100110101",
+ '9' => "110100101", '-' => "101001101", '$' => "101100101",
+ ':' => "1101011011", '/' => "1101101011", '.' => "1101101101",
+ '+' => "1011011011", 'A' => "1011001001", 'B' => "1010010011",
+ 'C' => "1001001011", 'D' => "1010011001"
+ );
+
+ /**
+ * Width of the barcode (in pixels)
+ * @return int
+ */
+ protected function calculateBarcodeWidth()
+ {
+ $quietZone = $this->getQuietZone();
+ $encodedData = 0;
+ $barcodeChar = str_split($this->getText());
+ if (count($barcodeChar) > 1) {
+ foreach ($barcodeChar as $c) {
+ $encodedData += ((strlen($this->codingMap[$c]) + 1) * $this->barThinWidth) * $this->factor;
+ }
+ }
+ $encodedData -= (1 * $this->barThinWidth * $this->factor);
+ return $quietZone + $encodedData + $quietZone;
+ }
+
+ /**
+ * Partial check of Codabar barcode
+ * @return void
+ */
+ protected function checkSpecificParams()
+ {
+ }
+
+ /**
+ * Prepare array to draw barcode
+ * @return array
+ */
+ protected function prepareBarcode()
+ {
+ $text = str_split($this->getText());
+ $barcodeTable = array();
+ foreach ($text as $char) {
+ $barcodeChar = str_split($this->codingMap[$char]);
+ foreach ($barcodeChar as $c) {
+ // visible, width, top, length
+ $barcodeTable[] = array($c, $this->barThinWidth, 0, 1);
+ }
+ $barcodeTable[] = array(0, $this->barThinWidth);
+ }
+ return $barcodeTable;
+ }
+}
diff --git a/library/Zend/Barcode/Object/Code128.php b/library/Zend/Barcode/Object/Code128.php
new file mode 100755
index 0000000000..3bb0cc0c75
--- /dev/null
+++ b/library/Zend/Barcode/Object/Code128.php
@@ -0,0 +1,311 @@
+ "11011001100", 1 => "11001101100", 2 => "11001100110",
+ 3 => "10010011000", 4 => "10010001100", 5 => "10001001100",
+ 6 => "10011001000", 7 => "10011000100", 8 => "10001100100",
+ 9 => "11001001000", 10 => "11001000100", 11 => "11000100100",
+ 12 => "10110011100", 13 => "10011011100", 14 => "10011001110",
+ 15 => "10111001100", 16 => "10011101100", 17 => "10011100110",
+ 18 => "11001110010", 19 => "11001011100", 20 => "11001001110",
+ 21 => "11011100100", 22 => "11001110100", 23 => "11101101110",
+ 24 => "11101001100", 25 => "11100101100", 26 => "11100100110",
+ 27 => "11101100100", 28 => "11100110100", 29 => "11100110010",
+ 30 => "11011011000", 31 => "11011000110", 32 => "11000110110",
+ 33 => "10100011000", 34 => "10001011000", 35 => "10001000110",
+ 36 => "10110001000", 37 => "10001101000", 38 => "10001100010",
+ 39 => "11010001000", 40 => "11000101000", 41 => "11000100010",
+ 42 => "10110111000", 43 => "10110001110", 44 => "10001101110",
+ 45 => "10111011000", 46 => "10111000110", 47 => "10001110110",
+ 48 => "11101110110", 49 => "11010001110", 50 => "11000101110",
+ 51 => "11011101000", 52 => "11011100010", 53 => "11011101110",
+ 54 => "11101011000", 55 => "11101000110", 56 => "11100010110",
+ 57 => "11101101000", 58 => "11101100010", 59 => "11100011010",
+ 60 => "11101111010", 61 => "11001000010", 62 => "11110001010",
+ 63 => "10100110000", 64 => "10100001100", 65 => "10010110000",
+ 66 => "10010000110", 67 => "10000101100", 68 => "10000100110",
+ 69 => "10110010000", 70 => "10110000100", 71 => "10011010000",
+ 72 => "10011000010", 73 => "10000110100", 74 => "10000110010",
+ 75 => "11000010010", 76 => "11001010000", 77 => "11110111010",
+ 78 => "11000010100", 79 => "10001111010", 80 => "10100111100",
+ 81 => "10010111100", 82 => "10010011110", 83 => "10111100100",
+ 84 => "10011110100", 85 => "10011110010", 86 => "11110100100",
+ 87 => "11110010100", 88 => "11110010010", 89 => "11011011110",
+ 90 => "11011110110", 91 => "11110110110", 92 => "10101111000",
+ 93 => "10100011110", 94 => "10001011110", 95 => "10111101000",
+ 96 => "10111100010", 97 => "11110101000", 98 => "11110100010",
+ 99 => "10111011110", 100 => "10111101110", 101 => "11101011110",
+ 102 => "11110101110",
+ 103 => "11010000100", 104 => "11010010000", 105 => "11010011100",
+ 106 => "1100011101011");
+
+ /**
+ * Character sets ABC
+ * @var array
+ */
+ protected $charSets = array(
+ 'A' => array(
+ ' ', '!', '"', '#', '$', '%', '&', "'",
+ '(', ')', '*', '+', ',', '-', '.', '/',
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', ':', ';', '<', '=', '>', '?',
+ '@', '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', '[', '\\', ']', '^', '_',
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+ 'FNC3', 'FNC2', 'SHIFT', 'Code C', 'Code B', 'FNC4', 'FNC1',
+ 'START A', 'START B', 'START C', 'STOP'),
+ 'B' => array(
+ ' ', '!', '"', '#', '$', '%', '&', "'",
+ '(', ')', '*', '+', ',', '-', '.', '/',
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', ':', ';', '<', '=', '>', '?',
+ '@', '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', '{', '|', '}', '~', 0x7F,
+ 'FNC3', 'FNC2', 'SHIFT', 'Code C', 'FNC4', 'Code A', 'FNC1',
+ 'START A', 'START B', 'START C', 'STOP',),
+ 'C' => array(
+ '00', '01', '02', '03', '04', '05', '06', '07', '08', '09',
+ '10', '11', '12', '13', '14', '15', '16', '17', '18', '19',
+ '20', '21', '22', '23', '24', '25', '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', '52', '53', '54', '55', '56', '57', '58', '59',
+ '60', '61', '62', '63', '64', '65', '66', '67', '68', '69',
+ '70', '71', '72', '73', '74', '75', '76', '77', '78', '79',
+ '80', '81', '82', '83', '84', '85', '86', '87', '88', '89',
+ '90', '91', '92', '93', '94', '95', '96', '97', '98', '99',
+ 'Code B', 'Code A', 'FNC1', 'START A', 'START B', 'START C', 'STOP'));
+
+ /**
+ * Width of the barcode (in pixels)
+ * @return int
+ */
+ protected function calculateBarcodeWidth()
+ {
+ $quietZone = $this->getQuietZone();
+ // Each characters contain 11 bars...
+ $characterLength = 11 * $this->barThinWidth * $this->factor;
+ $convertedChars = count($this->convertToBarcodeChars($this->getText()));
+ if ($this->withChecksum) {
+ $convertedChars++;
+ }
+ $encodedData = $convertedChars * $characterLength;
+ // ...except the STOP character (13)
+ $encodedData += $characterLength + 2 * $this->barThinWidth * $this->factor;
+ $width = $quietZone + $encodedData + $quietZone;
+ return $width;
+ }
+
+ /**
+ * Partial check of code128 barcode
+ * @return void
+ */
+ protected function checkSpecificParams()
+ {
+ }
+
+ /**
+ * Prepare array to draw barcode
+ * @return array
+ */
+ protected function prepareBarcode()
+ {
+ $barcodeTable = array();
+
+ $convertedChars = $this->convertToBarcodeChars($this->getText());
+
+ if ($this->withChecksum) {
+ $convertedChars[] = $this->getChecksum($this->getText());
+ }
+
+ // STOP CHARACTER
+ $convertedChars[] = 106;
+
+ foreach ($convertedChars as $barcodeChar) {
+ $barcodePattern = $this->codingMap[$barcodeChar];
+ foreach (str_split($barcodePattern) as $c) {
+ $barcodeTable[] = array($c, $this->barThinWidth, 0, 1);
+ }
+ }
+ return $barcodeTable;
+ }
+
+ /**
+ * Checks if the next $length chars of $string starting at $pos are numeric.
+ * Returns false if the end of the string is reached.
+ * @param string $string String to search
+ * @param int $pos Starting position
+ * @param int $length Length to search
+ * @return bool
+ */
+ protected static function _isDigit($string, $pos, $length = 2)
+ {
+ if ($pos + $length > strlen($string)) {
+ return false;
+ }
+
+ for ($i = $pos; $i < $pos + $length; $i++) {
+ if (!is_numeric($string[$i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Convert string to barcode string
+ * @param string $string
+ * @return array
+ */
+ protected function convertToBarcodeChars($string)
+ {
+ $string = (string) $string;
+ if (!strlen($string)) {
+ return array();
+ }
+
+ if (isset($this->convertedText[md5($string)])) {
+ return $this->convertedText[md5($string)];
+ }
+
+ $currentCharset = null;
+ $result = array();
+
+ $strlen = strlen($string);
+ for ($pos = 0; $pos < $strlen; $pos++) {
+ $char = $string[$pos];
+
+ if (static::_isDigit($string, $pos, 4) && $currentCharset != 'C'
+ || static::_isDigit($string, $pos, 2) && $currentCharset == 'C') {
+ /**
+ * Switch to C if the next 4 chars are numeric or stay C if the next 2
+ * chars are numeric
+ */
+ if ($currentCharset != 'C') {
+ if ($pos == 0) {
+ $code = array_search("START C", $this->charSets['C']);
+ } else {
+ $code = array_search("Code C", $this->charSets[$currentCharset]);
+ }
+ $result[] = $code;
+ $currentCharset = 'C';
+ }
+ } elseif (in_array($char, $this->charSets['B']) && $currentCharset != 'B'
+ && !(in_array($char, $this->charSets['A']) && $currentCharset == 'A')) {
+ /**
+ * Switch to B as B contains the char and B is not the current charset.
+ */
+ if ($pos == 0) {
+ $code = array_search("START B", $this->charSets['B']);
+ } else {
+ $code = array_search("Code B", $this->charSets[$currentCharset]);
+ }
+ $result[] = $code;
+ $currentCharset = 'B';
+ } elseif (array_key_exists($char, $this->charSets['A']) && $currentCharset != 'A'
+ && !(array_key_exists($char, $this->charSets['B']) && $currentCharset == 'B')) {
+ /**
+ * Switch to C as C contains the char and C is not the current charset.
+ */
+ if ($pos == 0) {
+ $code = array_search("START A", $this->charSets['A']);
+ } else {
+ $code = array_search("Code A", $this->charSets[$currentCharset]);
+ }
+ $result[] = $code;
+ $currentCharset = 'A';
+ }
+
+ if ($currentCharset == 'C') {
+ $code = array_search(substr($string, $pos, 2), $this->charSets['C']);
+ $pos++; //Two chars from input
+ } else {
+ $code = array_search($string[$pos], $this->charSets[$currentCharset]);
+ }
+ $result[] = $code;
+ }
+
+ $this->convertedText[md5($string)] = $result;
+ return $result;
+ }
+
+ /**
+ * Set text to encode
+ * @param string $value
+ * @return Code128
+ */
+ public function setText($value)
+ {
+ $this->text = $value;
+ return $this;
+ }
+
+ /**
+ * Retrieve text to encode
+ * @return string
+ */
+ public function getText()
+ {
+ return $this->text;
+ }
+
+ /**
+ * Get barcode checksum
+ *
+ * @param string $text
+ * @return int
+ */
+ public function getChecksum($text)
+ {
+ $tableOfChars = $this->convertToBarcodeChars($text);
+
+ $sum = $tableOfChars[0];
+ unset($tableOfChars[0]);
+
+ $k = 1;
+ foreach ($tableOfChars as $char) {
+ $sum += ($k++) * $char;
+ }
+
+ $checksum = $sum % 103;
+
+ return $checksum;
+ }
+}
diff --git a/library/Zend/Barcode/Object/Code25.php b/library/Zend/Barcode/Object/Code25.php
new file mode 100755
index 0000000000..2f32ca7af0
--- /dev/null
+++ b/library/Zend/Barcode/Object/Code25.php
@@ -0,0 +1,117 @@
+ '00110',
+ '1' => '10001',
+ '2' => '01001',
+ '3' => '11000',
+ '4' => '00101',
+ '5' => '10100',
+ '6' => '01100',
+ '7' => '00011',
+ '8' => '10010',
+ '9' => '01010',
+ );
+
+ /**
+ * Width of the barcode (in pixels)
+ * @return int
+ */
+ protected function calculateBarcodeWidth()
+ {
+ $quietZone = $this->getQuietZone();
+ $startCharacter = (2 * $this->barThickWidth + 4 * $this->barThinWidth) * $this->factor;
+ $characterLength = (3 * $this->barThinWidth + 2 * $this->barThickWidth + 5 * $this->barThinWidth)
+ * $this->factor;
+ $encodedData = strlen($this->getText()) * $characterLength;
+ $stopCharacter = (2 * $this->barThickWidth + 4 * $this->barThinWidth) * $this->factor;
+ return $quietZone + $startCharacter + $encodedData + $stopCharacter + $quietZone;
+ }
+
+ /**
+ * Partial check of interleaved 2 of 5 barcode
+ * @return void
+ */
+ protected function checkSpecificParams()
+ {
+ $this->checkRatio();
+ }
+
+ /**
+ * Prepare array to draw barcode
+ * @return array
+ */
+ protected function prepareBarcode()
+ {
+ $barcodeTable = array();
+
+ // Start character (30301)
+ $barcodeTable[] = array(1, $this->barThickWidth, 0, 1);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, 1);
+ $barcodeTable[] = array(1, $this->barThickWidth, 0, 1);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, 1);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, 1);
+ $barcodeTable[] = array(0, $this->barThinWidth);
+
+ $text = str_split($this->getText());
+ foreach ($text as $char) {
+ $barcodeChar = str_split($this->codingMap[$char]);
+ foreach ($barcodeChar as $c) {
+ /* visible, width, top, length */
+ $width = $c ? $this->barThickWidth : $this->barThinWidth;
+ $barcodeTable[] = array(1, $width, 0, 1);
+ $barcodeTable[] = array(0, $this->barThinWidth);
+ }
+ }
+
+ // Stop character (30103)
+ $barcodeTable[] = array(1, $this->barThickWidth, 0, 1);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, 1);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, 1);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, 1);
+ $barcodeTable[] = array(1, $this->barThickWidth, 0, 1);
+ return $barcodeTable;
+ }
+
+ /**
+ * Get barcode checksum
+ *
+ * @param string $text
+ * @return int
+ */
+ public function getChecksum($text)
+ {
+ $this->checkText($text);
+ $factor = 3;
+ $checksum = 0;
+
+ for ($i = strlen($text); $i > 0; $i --) {
+ $checksum += intval($text{$i - 1}) * $factor;
+ $factor = 4 - $factor;
+ }
+
+ $checksum = (10 - ($checksum % 10)) % 10;
+
+ return $checksum;
+ }
+}
diff --git a/library/Zend/Barcode/Object/Code25interleaved.php b/library/Zend/Barcode/Object/Code25interleaved.php
new file mode 100755
index 0000000000..0fb3ec33a1
--- /dev/null
+++ b/library/Zend/Barcode/Object/Code25interleaved.php
@@ -0,0 +1,159 @@
+barcodeLength = 'even';
+ }
+
+ /**
+ * Activate/deactivate drawing of bearer bars
+ * @param bool $value
+ * @return Code25
+ */
+ public function setWithBearerBars($value)
+ {
+ $this->withBearerBars = (bool) $value;
+ return $this;
+ }
+
+ /**
+ * Retrieve if bearer bars are enabled
+ * @return bool
+ */
+ public function getWithBearerBars()
+ {
+ return $this->withBearerBars;
+ }
+
+ /**
+ * Width of the barcode (in pixels)
+ * @return int
+ */
+ protected function calculateBarcodeWidth()
+ {
+ $quietZone = $this->getQuietZone();
+ $startCharacter = (4 * $this->barThinWidth) * $this->factor;
+ $characterLength = (3 * $this->barThinWidth + 2 * $this->barThickWidth) * $this->factor;
+ $encodedData = strlen($this->getText()) * $characterLength;
+ $stopCharacter = ($this->barThickWidth + 2 * $this->barThinWidth) * $this->factor;
+ return $quietZone + $startCharacter + $encodedData + $stopCharacter + $quietZone;
+ }
+
+ /**
+ * Prepare array to draw barcode
+ * @return array
+ */
+ protected function prepareBarcode()
+ {
+ if ($this->withBearerBars) {
+ $this->withBorder = false;
+ }
+
+ $barcodeTable = array();
+
+ // Start character (0000)
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, 1);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, 1);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, 1);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, 1);
+
+ // Encoded $text
+ $text = $this->getText();
+ for ($i = 0, $len = strlen($text); $i < $len; $i += 2) { // Draw 2 chars at a time
+ $char1 = substr($text, $i, 1);
+ $char2 = substr($text, $i + 1, 1);
+
+ // Interleave
+ for ($ibar = 0; $ibar < 5; $ibar ++) {
+ // Draws char1 bar (fore color)
+ $barWidth = (substr($this->codingMap[$char1], $ibar, 1))
+ ? $this->barThickWidth
+ : $this->barThinWidth;
+
+ $barcodeTable[] = array(1, $barWidth, 0, 1);
+
+ // Left space corresponding to char2 (background color)
+ $barWidth = (substr($this->codingMap[$char2], $ibar, 1))
+ ? $this->barThickWidth
+ : $this->barThinWidth;
+ $barcodeTable[] = array(0, $barWidth, 0, 1);
+ }
+ }
+
+ // Stop character (100)
+ $barcodeTable[] = array(1, $this->barThickWidth, 0, 1);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, 1);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, 1);
+ return $barcodeTable;
+ }
+
+ /**
+ * Drawing of bearer bars (if enabled)
+ *
+ * @return void
+ */
+ protected function postDrawBarcode()
+ {
+ if (!$this->withBearerBars) {
+ return;
+ }
+
+ $width = $this->barThickWidth * $this->factor;
+ $point1 = $this->rotate(-1, -1);
+ $point2 = $this->rotate($this->calculateWidth() - 1, -1);
+ $point3 = $this->rotate($this->calculateWidth() - 1, $width - 1);
+ $point4 = $this->rotate(-1, $width - 1);
+ $this->addPolygon(array(
+ $point1,
+ $point2,
+ $point3,
+ $point4,
+ ));
+ $point1 = $this->rotate(
+ 0,
+ 0 + $this->barHeight * $this->factor - 1
+ );
+ $point2 = $this->rotate(
+ $this->calculateWidth() - 1,
+ 0 + $this->barHeight * $this->factor - 1
+ );
+ $point3 = $this->rotate(
+ $this->calculateWidth() - 1,
+ 0 + $this->barHeight * $this->factor - $width
+ );
+ $point4 = $this->rotate(
+ 0,
+ 0 + $this->barHeight * $this->factor - $width
+ );
+ $this->addPolygon(array(
+ $point1,
+ $point2,
+ $point3,
+ $point4,
+ ));
+ }
+}
diff --git a/library/Zend/Barcode/Object/Code39.php b/library/Zend/Barcode/Object/Code39.php
new file mode 100755
index 0000000000..c70e289eb5
--- /dev/null
+++ b/library/Zend/Barcode/Object/Code39.php
@@ -0,0 +1,162 @@
+ '000110100',
+ '1' => '100100001',
+ '2' => '001100001',
+ '3' => '101100000',
+ '4' => '000110001',
+ '5' => '100110000',
+ '6' => '001110000',
+ '7' => '000100101',
+ '8' => '100100100',
+ '9' => '001100100',
+ 'A' => '100001001',
+ 'B' => '001001001',
+ 'C' => '101001000',
+ 'D' => '000011001',
+ 'E' => '100011000',
+ 'F' => '001011000',
+ 'G' => '000001101',
+ 'H' => '100001100',
+ 'I' => '001001100',
+ 'J' => '000011100',
+ 'K' => '100000011',
+ 'L' => '001000011',
+ 'M' => '101000010',
+ 'N' => '000010011',
+ 'O' => '100010010',
+ 'P' => '001010010',
+ 'Q' => '000000111',
+ 'R' => '100000110',
+ 'S' => '001000110',
+ 'T' => '000010110',
+ 'U' => '110000001',
+ 'V' => '011000001',
+ 'W' => '111000000',
+ 'X' => '010010001',
+ 'Y' => '110010000',
+ 'Z' => '011010000',
+ '-' => '010000101',
+ '.' => '110000100',
+ ' ' => '011000100',
+ '$' => '010101000',
+ '/' => '010100010',
+ '+' => '010001010',
+ '%' => '000101010',
+ '*' => '010010100',
+ );
+
+ /**
+ * Partial check of Code39 barcode
+ * @return void
+ */
+ protected function checkSpecificParams()
+ {
+ $this->checkRatio();
+ }
+
+ /**
+ * Width of the barcode (in pixels)
+ * @return int
+ */
+ protected function calculateBarcodeWidth()
+ {
+ $quietZone = $this->getQuietZone();
+ $characterLength = (6 * $this->barThinWidth + 3 * $this->barThickWidth + 1) * $this->factor;
+ $encodedData = strlen($this->getText()) * $characterLength - $this->factor;
+ return $quietZone + $encodedData + $quietZone;
+ }
+
+ /**
+ * Set text to encode
+ * @param string $value
+ * @return Code39
+ */
+ public function setText($value)
+ {
+ $this->text = $value;
+ return $this;
+ }
+
+ /**
+ * Retrieve text to display
+ * @return string
+ */
+ public function getText()
+ {
+ return '*' . parent::getText() . '*';
+ }
+
+ /**
+ * Retrieve text to display
+ * @return string
+ */
+ public function getTextToDisplay()
+ {
+ $text = parent::getTextToDisplay();
+ if (substr($text, 0, 1) != '*' && substr($text, -1) != '*') {
+ return '*' . $text . '*';
+ }
+
+ return $text;
+ }
+
+ /**
+ * Prepare array to draw barcode
+ * @return array
+ */
+ protected function prepareBarcode()
+ {
+ $text = str_split($this->getText());
+ $barcodeTable = array();
+ foreach ($text as $char) {
+ $barcodeChar = str_split($this->codingMap[$char]);
+ $visible = true;
+ foreach ($barcodeChar as $c) {
+ /* visible, width, top, length */
+ $width = $c ? $this->barThickWidth : $this->barThinWidth;
+ $barcodeTable[] = array((int) $visible, $width, 0, 1);
+ $visible = ! $visible;
+ }
+ $barcodeTable[] = array(0, $this->barThinWidth);
+ }
+ return $barcodeTable;
+ }
+
+ /**
+ * Get barcode checksum
+ *
+ * @param string $text
+ * @return int
+ */
+ public function getChecksum($text)
+ {
+ $this->checkText($text);
+ $text = str_split($text);
+ $charset = array_flip(array_keys($this->codingMap));
+ $checksum = 0;
+ foreach ($text as $character) {
+ $checksum += $charset[$character];
+ }
+ return array_search(($checksum % 43), $charset);
+ }
+}
diff --git a/library/Zend/Barcode/Object/Ean13.php b/library/Zend/Barcode/Object/Ean13.php
new file mode 100755
index 0000000000..602668d97f
--- /dev/null
+++ b/library/Zend/Barcode/Object/Ean13.php
@@ -0,0 +1,198 @@
+ array(
+ 0 => "0001101", 1 => "0011001", 2 => "0010011", 3 => "0111101", 4 => "0100011",
+ 5 => "0110001", 6 => "0101111", 7 => "0111011", 8 => "0110111", 9 => "0001011"
+ ),
+ 'B' => array(
+ 0 => "0100111", 1 => "0110011", 2 => "0011011", 3 => "0100001", 4 => "0011101",
+ 5 => "0111001", 6 => "0000101", 7 => "0010001", 8 => "0001001", 9 => "0010111"
+ ),
+ 'C' => array(
+ 0 => "1110010", 1 => "1100110", 2 => "1101100", 3 => "1000010", 4 => "1011100",
+ 5 => "1001110", 6 => "1010000", 7 => "1000100", 8 => "1001000", 9 => "1110100"
+ ));
+
+ protected $parities = array(
+ 0 => array('A','A','A','A','A','A'),
+ 1 => array('A','A','B','A','B','B'),
+ 2 => array('A','A','B','B','A','B'),
+ 3 => array('A','A','B','B','B','A'),
+ 4 => array('A','B','A','A','B','B'),
+ 5 => array('A','B','B','A','A','B'),
+ 6 => array('A','B','B','B','A','A'),
+ 7 => array('A','B','A','B','A','B'),
+ 8 => array('A','B','A','B','B','A'),
+ 9 => array('A','B','B','A','B','A')
+ );
+
+ /**
+ * Default options for Postnet barcode
+ * @return void
+ */
+ protected function getDefaultOptions()
+ {
+ $this->barcodeLength = 13;
+ $this->mandatoryChecksum = true;
+ $this->mandatoryQuietZones = true;
+ }
+
+ /**
+ * Width of the barcode (in pixels)
+ * @return int
+ */
+ protected function calculateBarcodeWidth()
+ {
+ $quietZone = $this->getQuietZone();
+ $startCharacter = (3 * $this->barThinWidth) * $this->factor;
+ $middleCharacter = (5 * $this->barThinWidth) * $this->factor;
+ $stopCharacter = (3 * $this->barThinWidth) * $this->factor;
+ $encodedData = (7 * $this->barThinWidth) * $this->factor * 12;
+ return $quietZone + $startCharacter + $middleCharacter + $encodedData + $stopCharacter + $quietZone;
+ }
+
+ /**
+ * Partial check of interleaved EAN/UPC barcode
+ * @return void
+ */
+ protected function checkSpecificParams()
+ {
+ }
+
+ /**
+ * Prepare array to draw barcode
+ * @return array
+ */
+ protected function prepareBarcode()
+ {
+ $barcodeTable = array();
+ $height = ($this->drawText) ? 1.1 : 1;
+
+ // Start character (101)
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+
+ $textTable = str_split($this->getText());
+ $parity = $this->parities[$textTable[0]];
+
+ // First part
+ for ($i = 1; $i < 7; $i++) {
+ $bars = str_split($this->codingMap[$parity[$i - 1]][$textTable[$i]]);
+ foreach ($bars as $b) {
+ $barcodeTable[] = array($b, $this->barThinWidth, 0, 1);
+ }
+ }
+
+ // Middle character (01010)
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, $height);
+
+ // Second part
+ for ($i = 7; $i < 13; $i++) {
+ $bars = str_split($this->codingMap['C'][$textTable[$i]]);
+ foreach ($bars as $b) {
+ $barcodeTable[] = array($b, $this->barThinWidth, 0, 1);
+ }
+ }
+
+ // Stop character (101)
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+ return $barcodeTable;
+ }
+
+ /**
+ * Get barcode checksum
+ *
+ * @param string $text
+ * @return int
+ */
+ public function getChecksum($text)
+ {
+ $this->checkText($text);
+ $factor = 3;
+ $checksum = 0;
+
+ for ($i = strlen($text); $i > 0; $i --) {
+ $checksum += intval($text{$i - 1}) * $factor;
+ $factor = 4 - $factor;
+ }
+
+ $checksum = (10 - ($checksum % 10)) % 10;
+
+ return $checksum;
+ }
+
+ /**
+ * Partial function to draw text
+ * @return void
+ */
+ protected function drawText()
+ {
+ if (get_class($this) == 'Zend\Barcode\Object\Ean13') {
+ $this->drawEan13Text();
+ } else {
+ parent::drawText();
+ }
+ }
+
+ protected function drawEan13Text()
+ {
+ if ($this->drawText) {
+ $text = $this->getTextToDisplay();
+ $characterWidth = (7 * $this->barThinWidth) * $this->factor;
+ $leftPosition = $this->getQuietZone() - $characterWidth;
+ for ($i = 0; $i < $this->barcodeLength; $i ++) {
+ $this->addText(
+ $text{$i},
+ $this->fontSize * $this->factor,
+ $this->rotate(
+ $leftPosition,
+ (int) $this->withBorder * 2 + $this->factor * ($this->barHeight + $this->fontSize) + 1
+ ),
+ $this->font,
+ $this->foreColor,
+ 'left',
+ - $this->orientation
+ );
+ switch ($i) {
+ case 0:
+ $factor = 3;
+ break;
+ case 6:
+ $factor = 4;
+ break;
+ default:
+ $factor = 0;
+ }
+ $leftPosition = $leftPosition + $characterWidth + ($factor * $this->barThinWidth * $this->factor);
+ }
+ }
+ }
+}
diff --git a/library/Zend/Barcode/Object/Ean2.php b/library/Zend/Barcode/Object/Ean2.php
new file mode 100755
index 0000000000..b260a62d68
--- /dev/null
+++ b/library/Zend/Barcode/Object/Ean2.php
@@ -0,0 +1,38 @@
+ array('A','A'),
+ 1 => array('A','B'),
+ 2 => array('B','A'),
+ 3 => array('B','B')
+ );
+
+ /**
+ * Default options for Ean2 barcode
+ * @return void
+ */
+ protected function getDefaultOptions()
+ {
+ $this->barcodeLength = 2;
+ }
+
+ protected function getParity($i)
+ {
+ $modulo = $this->getText() % 4;
+ return $this->parities[$modulo][$i];
+ }
+}
diff --git a/library/Zend/Barcode/Object/Ean5.php b/library/Zend/Barcode/Object/Ean5.php
new file mode 100755
index 0000000000..c04708bdc3
--- /dev/null
+++ b/library/Zend/Barcode/Object/Ean5.php
@@ -0,0 +1,124 @@
+ array('B','B','A','A','A'),
+ 1 => array('B','A','B','A','A'),
+ 2 => array('B','A','A','B','A'),
+ 3 => array('B','A','A','A','B'),
+ 4 => array('A','B','B','A','A'),
+ 5 => array('A','A','B','B','A'),
+ 6 => array('A','A','A','B','B'),
+ 7 => array('A','B','A','B','A'),
+ 8 => array('A','B','A','A','B'),
+ 9 => array('A','A','B','A','B')
+ );
+
+ /**
+ * Default options for Ean5 barcode
+ * @return void
+ */
+ protected function getDefaultOptions()
+ {
+ $this->barcodeLength = 5;
+ }
+
+ /**
+ * Width of the barcode (in pixels)
+ * @return int
+ */
+ protected function calculateBarcodeWidth()
+ {
+ $quietZone = $this->getQuietZone();
+ $startCharacter = (5 * $this->barThinWidth) * $this->factor;
+ $middleCharacter = (2 * $this->barThinWidth) * $this->factor;
+ $encodedData = (7 * $this->barThinWidth) * $this->factor;
+ return $quietZone + $startCharacter + ($this->barcodeLength - 1) * $middleCharacter + $this->barcodeLength * $encodedData + $quietZone;
+ }
+
+ /**
+ * Prepare array to draw barcode
+ * @return array
+ */
+ protected function prepareBarcode()
+ {
+ $barcodeTable = array();
+
+ // Start character (01011)
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, 1);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, 1);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, 1);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, 1);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, 1);
+
+ $firstCharacter = true;
+ $textTable = str_split($this->getText());
+
+ // Characters
+ for ($i = 0; $i < $this->barcodeLength; $i++) {
+ if ($firstCharacter) {
+ $firstCharacter = false;
+ } else {
+ // Intermediate character (01)
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, 1);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, 1);
+ }
+ $bars = str_split($this->codingMap[$this->getParity($i)][$textTable[$i]]);
+ foreach ($bars as $b) {
+ $barcodeTable[] = array($b, $this->barThinWidth, 0, 1);
+ }
+ }
+
+ return $barcodeTable;
+ }
+
+ /**
+ * Get barcode checksum
+ *
+ * @param string $text
+ * @return int
+ */
+ public function getChecksum($text)
+ {
+ $this->checkText($text);
+ $checksum = 0;
+
+ for ($i = 0; $i < $this->barcodeLength; $i ++) {
+ $checksum += intval($text{$i}) * ($i % 2 ? 9 : 3);
+ }
+
+ return ($checksum % 10);
+ }
+
+ /**
+ * @param int $i
+ * @return string
+ */
+ protected function getParity($i)
+ {
+ $checksum = $this->getChecksum($this->getText());
+ return $this->parities[$checksum][$i];
+ }
+
+ /**
+ * Retrieve text to encode
+ * @return string
+ */
+ public function getText()
+ {
+ return $this->addLeadingZeros($this->text);
+ }
+}
diff --git a/library/Zend/Barcode/Object/Ean8.php b/library/Zend/Barcode/Object/Ean8.php
new file mode 100755
index 0000000000..b36ec3e4c8
--- /dev/null
+++ b/library/Zend/Barcode/Object/Ean8.php
@@ -0,0 +1,146 @@
+barcodeLength = 8;
+ $this->mandatoryChecksum = true;
+ }
+
+ /**
+ * Width of the barcode (in pixels)
+ * @return int
+ */
+ protected function calculateBarcodeWidth()
+ {
+ $quietZone = $this->getQuietZone();
+ $startCharacter = (3 * $this->barThinWidth) * $this->factor;
+ $middleCharacter = (5 * $this->barThinWidth) * $this->factor;
+ $stopCharacter = (3 * $this->barThinWidth) * $this->factor;
+ $encodedData = (7 * $this->barThinWidth) * $this->factor * 8;
+ return $quietZone + $startCharacter + $middleCharacter + $encodedData + $stopCharacter + $quietZone;
+ }
+
+ /**
+ * Prepare array to draw barcode
+ * @return array
+ */
+ protected function prepareBarcode()
+ {
+ $barcodeTable = array();
+ $height = ($this->drawText) ? 1.1 : 1;
+
+ // Start character (101)
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+
+ $textTable = str_split($this->getText());
+
+ // First part
+ for ($i = 0; $i < 4; $i++) {
+ $bars = str_split($this->codingMap['A'][$textTable[$i]]);
+ foreach ($bars as $b) {
+ $barcodeTable[] = array($b, $this->barThinWidth, 0, 1);
+ }
+ }
+
+ // Middle character (01010)
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, $height);
+
+ // Second part
+ for ($i = 4; $i < 8; $i++) {
+ $bars = str_split($this->codingMap['C'][$textTable[$i]]);
+ foreach ($bars as $b) {
+ $barcodeTable[] = array($b, $this->barThinWidth, 0, 1);
+ }
+ }
+
+ // Stop character (101)
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+ return $barcodeTable;
+ }
+
+ /**
+ * Partial function to draw text
+ * @return void
+ */
+ protected function drawText()
+ {
+ if ($this->drawText) {
+ $text = $this->getTextToDisplay();
+ $characterWidth = (7 * $this->barThinWidth) * $this->factor;
+ $leftPosition = $this->getQuietZone() + (3 * $this->barThinWidth) * $this->factor;
+ for ($i = 0; $i < $this->barcodeLength; $i ++) {
+ $this->addText(
+ $text{$i},
+ $this->fontSize * $this->factor,
+ $this->rotate(
+ $leftPosition,
+ (int) $this->withBorder * 2 + $this->factor * ($this->barHeight + $this->fontSize) + 1
+ ),
+ $this->font,
+ $this->foreColor,
+ 'left',
+ - $this->orientation
+ );
+ switch ($i) {
+ case 3:
+ $factor = 4;
+ break;
+ default:
+ $factor = 0;
+ }
+ $leftPosition = $leftPosition + $characterWidth + ($factor * $this->barThinWidth * $this->factor);
+ }
+ }
+ }
+
+ /**
+ * Particular validation for Ean8 barcode objects
+ * (to suppress checksum character substitution)
+ *
+ * @param string $value
+ * @param array $options
+ * @throws Exception\BarcodeValidationException
+ */
+ protected function validateSpecificText($value, $options = array())
+ {
+ $validator = new BarcodeValidator(array(
+ 'adapter' => 'ean8',
+ 'checksum' => false,
+ ));
+
+ $value = $this->addLeadingZeros($value, true);
+
+ if (!$validator->isValid($value)) {
+ $message = implode("\n", $validator->getMessages());
+ throw new Exception\BarcodeValidationException($message);
+ }
+ }
+}
diff --git a/library/Zend/Barcode/Object/Error.php b/library/Zend/Barcode/Object/Error.php
new file mode 100755
index 0000000000..15c36bcab2
--- /dev/null
+++ b/library/Zend/Barcode/Object/Error.php
@@ -0,0 +1,83 @@
+instructions = array();
+ $this->addText('ERROR:', 10, array(5, 18), $this->font, 0, 'left');
+ $this->addText($this->text, 10, array(5, 32), $this->font, 0, 'left');
+ return $this->instructions;
+ }
+
+ /**
+ * For compatibility reason
+ * @return void
+ */
+ protected function prepareBarcode()
+ {
+ }
+
+ /**
+ * For compatibility reason
+ * @return void
+ */
+ protected function checkSpecificParams()
+ {
+ }
+
+ /**
+ * For compatibility reason
+ * @return void
+ */
+ protected function calculateBarcodeWidth()
+ {
+ }
+}
diff --git a/library/Zend/Barcode/Object/Exception/BarcodeValidationException.php b/library/Zend/Barcode/Object/Exception/BarcodeValidationException.php
new file mode 100755
index 0000000000..64f3ea56e0
--- /dev/null
+++ b/library/Zend/Barcode/Object/Exception/BarcodeValidationException.php
@@ -0,0 +1,17 @@
+barcodeLength = 12;
+ $this->mandatoryChecksum = true;
+ }
+
+ /**
+ * Retrieve text to display
+ * @return string
+ */
+ public function getTextToDisplay()
+ {
+ return preg_replace('/([0-9]{2})([0-9]{3})([0-9]{3})([0-9]{3})([0-9])/', '$1.$2 $3.$4 $5', $this->getText());
+ }
+
+ /**
+ * Check allowed characters
+ * @param string $value
+ * @return string
+ * @throws Exception\BarcodeValidationException
+ */
+ public function validateText($value)
+ {
+ $this->validateSpecificText($value, array('validator' => $this->getType()));
+ }
+
+ /**
+ * Get barcode checksum
+ *
+ * @param string $text
+ * @return int
+ */
+ public function getChecksum($text)
+ {
+ $this->checkText($text);
+ $checksum = 0;
+
+ for ($i = strlen($text); $i > 0; $i --) {
+ $checksum += intval($text{$i - 1}) * (($i % 2) ? 4 : 9);
+ }
+
+ $checksum = (10 - ($checksum % 10)) % 10;
+
+ return $checksum;
+ }
+}
diff --git a/library/Zend/Barcode/Object/Itf14.php b/library/Zend/Barcode/Object/Itf14.php
new file mode 100755
index 0000000000..361f9bd563
--- /dev/null
+++ b/library/Zend/Barcode/Object/Itf14.php
@@ -0,0 +1,26 @@
+barcodeLength = 14;
+ $this->mandatoryChecksum = true;
+ }
+}
diff --git a/library/Zend/Barcode/Object/Leitcode.php b/library/Zend/Barcode/Object/Leitcode.php
new file mode 100755
index 0000000000..f82f3a5c03
--- /dev/null
+++ b/library/Zend/Barcode/Object/Leitcode.php
@@ -0,0 +1,35 @@
+barcodeLength = 14;
+ $this->mandatoryChecksum = true;
+ }
+
+ /**
+ * Retrieve text to display
+ * @return string
+ */
+ public function getTextToDisplay()
+ {
+ return preg_replace('/([0-9]{5})([0-9]{3})([0-9]{3})([0-9]{2})([0-9])/', '$1.$2.$3.$4 $5', $this->getText());
+ }
+}
diff --git a/library/Zend/Barcode/Object/ObjectInterface.php b/library/Zend/Barcode/Object/ObjectInterface.php
new file mode 100755
index 0000000000..836e0138ee
--- /dev/null
+++ b/library/Zend/Barcode/Object/ObjectInterface.php
@@ -0,0 +1,337 @@
+ "00111",
+ 1 => "11100",
+ 2 => "11010",
+ 3 => "11001",
+ 4 => "10110",
+ 5 => "10101",
+ 6 => "10011",
+ 7 => "01110",
+ 8 => "01101",
+ 9 => "01011"
+ );
+}
diff --git a/library/Zend/Barcode/Object/Postnet.php b/library/Zend/Barcode/Object/Postnet.php
new file mode 100755
index 0000000000..07b1d3c2d3
--- /dev/null
+++ b/library/Zend/Barcode/Object/Postnet.php
@@ -0,0 +1,110 @@
+ "11000",
+ 1 => "00011",
+ 2 => "00101",
+ 3 => "00110",
+ 4 => "01001",
+ 5 => "01010",
+ 6 => "01100",
+ 7 => "10001",
+ 8 => "10010",
+ 9 => "10100"
+ );
+
+ /**
+ * Default options for Postnet barcode
+ * @return void
+ */
+ protected function getDefaultOptions()
+ {
+ $this->barThinWidth = 2;
+ $this->barHeight = 20;
+ $this->drawText = false;
+ $this->stretchText = true;
+ $this->mandatoryChecksum = true;
+ }
+
+ /**
+ * Width of the barcode (in pixels)
+ * @return int
+ */
+ protected function calculateBarcodeWidth()
+ {
+ $quietZone = $this->getQuietZone();
+ $startCharacter = (2 * $this->barThinWidth) * $this->factor;
+ $stopCharacter = (1 * $this->barThinWidth) * $this->factor;
+ $encodedData = (10 * $this->barThinWidth) * $this->factor * strlen($this->getText());
+ return $quietZone + $startCharacter + $encodedData + $stopCharacter + $quietZone;
+ }
+
+ /**
+ * Partial check of interleaved Postnet barcode
+ * @return void
+ */
+ protected function checkSpecificParams()
+ {
+ }
+
+ /**
+ * Prepare array to draw barcode
+ * @return array
+ */
+ protected function prepareBarcode()
+ {
+ $barcodeTable = array();
+
+ // Start character (1)
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, 1);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, 1);
+
+ // Text to encode
+ $textTable = str_split($this->getText());
+ foreach ($textTable as $char) {
+ $bars = str_split($this->codingMap[$char]);
+ foreach ($bars as $b) {
+ $barcodeTable[] = array(1, $this->barThinWidth, 0.5 - $b * 0.5, 1);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, 1);
+ }
+ }
+
+ // Stop character (1)
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, 1);
+ return $barcodeTable;
+ }
+
+ /**
+ * Get barcode checksum
+ *
+ * @param string $text
+ * @return int
+ */
+ public function getChecksum($text)
+ {
+ $this->checkText($text);
+ $sum = array_sum(str_split($text));
+ $checksum = (10 - ($sum % 10)) % 10;
+ return $checksum;
+ }
+}
diff --git a/library/Zend/Barcode/Object/Royalmail.php b/library/Zend/Barcode/Object/Royalmail.php
new file mode 100755
index 0000000000..23d8a709a7
--- /dev/null
+++ b/library/Zend/Barcode/Object/Royalmail.php
@@ -0,0 +1,137 @@
+ '3300', '1' => '3210', '2' => '3201', '3' => '2310', '4' => '2301', '5' => '2211',
+ '6' => '3120', '7' => '3030', '8' => '3021', '9' => '2130', 'A' => '2121', 'B' => '2031',
+ 'C' => '3102', 'D' => '3012', 'E' => '3003', 'F' => '2112', 'G' => '2103', 'H' => '2013',
+ 'I' => '1320', 'J' => '1230', 'K' => '1221', 'L' => '0330', 'M' => '0321', 'N' => '0231',
+ 'O' => '1302', 'P' => '1212', 'Q' => '1203', 'R' => '0312', 'S' => '0303', 'T' => '0213',
+ 'U' => '1122', 'V' => '1032', 'W' => '1023', 'X' => '0132', 'Y' => '0123', 'Z' => '0033'
+ );
+
+ protected $rows = array(
+ '0' => 1, '1' => 1, '2' => 1, '3' => 1, '4' => 1, '5' => 1,
+ '6' => 2, '7' => 2, '8' => 2, '9' => 2, 'A' => 2, 'B' => 2,
+ 'C' => 3, 'D' => 3, 'E' => 3, 'F' => 3, 'G' => 3, 'H' => 3,
+ 'I' => 4, 'J' => 4, 'K' => 4, 'L' => 4, 'M' => 4, 'N' => 4,
+ 'O' => 5, 'P' => 5, 'Q' => 5, 'R' => 5, 'S' => 5, 'T' => 5,
+ 'U' => 0, 'V' => 0, 'W' => 0, 'X' => 0, 'Y' => 0, 'Z' => 0,
+ );
+
+ protected $columns = array(
+ '0' => 1, '1' => 2, '2' => 3, '3' => 4, '4' => 5, '5' => 0,
+ '6' => 1, '7' => 2, '8' => 3, '9' => 4, 'A' => 5, 'B' => 0,
+ 'C' => 1, 'D' => 2, 'E' => 3, 'F' => 4, 'G' => 5, 'H' => 0,
+ 'I' => 1, 'J' => 2, 'K' => 3, 'L' => 4, 'M' => 5, 'N' => 0,
+ 'O' => 1, 'P' => 2, 'Q' => 3, 'R' => 4, 'S' => 5, 'T' => 0,
+ 'U' => 1, 'V' => 2, 'W' => 3, 'X' => 4, 'Y' => 5, 'Z' => 0,
+ );
+
+ /**
+ * Default options for Postnet barcode
+ * @return void
+ */
+ protected function getDefaultOptions()
+ {
+ $this->barThinWidth = 2;
+ $this->barHeight = 20;
+ $this->drawText = false;
+ $this->stretchText = true;
+ $this->mandatoryChecksum = true;
+ }
+
+ /**
+ * Width of the barcode (in pixels)
+ * @return int
+ */
+ protected function calculateBarcodeWidth()
+ {
+ $quietZone = $this->getQuietZone();
+ $startCharacter = (2 * $this->barThinWidth) * $this->factor;
+ $stopCharacter = (1 * $this->barThinWidth) * $this->factor;
+ $encodedData = (8 * $this->barThinWidth) * $this->factor * strlen($this->getText());
+ return $quietZone + $startCharacter + $encodedData + $stopCharacter + $quietZone;
+ }
+
+ /**
+ * Partial check of interleaved Postnet barcode
+ * @return void
+ */
+ protected function checkSpecificParams()
+ {
+ }
+
+ /**
+ * Prepare array to draw barcode
+ * @return array
+ */
+ protected function prepareBarcode()
+ {
+ $barcodeTable = array();
+
+ // Start character (1)
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, 5/8);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, 1);
+
+ // Text to encode
+ $textTable = str_split($this->getText());
+ foreach ($textTable as $char) {
+ $bars = str_split($this->codingMap[$char]);
+ foreach ($bars as $b) {
+ $barcodeTable[] = array(1, $this->barThinWidth, ($b > 1 ? 3/8 : 0), ($b % 2 ? 5/8 : 1));
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, 1);
+ }
+ }
+
+ // Stop character (1)
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, 1);
+ return $barcodeTable;
+ }
+
+ /**
+ * Get barcode checksum
+ *
+ * @param string $text
+ * @return int
+ */
+ public function getChecksum($text)
+ {
+ $this->checkText($text);
+ $values = str_split($text);
+ $rowvalue = 0;
+ $colvalue = 0;
+ foreach ($values as $row) {
+ $rowvalue += $this->rows[$row];
+ $colvalue += $this->columns[$row];
+ }
+
+ $rowvalue %= 6;
+ $colvalue %= 6;
+
+ $rowchkvalue = array_keys($this->rows, $rowvalue);
+ $colchkvalue = array_keys($this->columns, $colvalue);
+ return current(array_intersect($rowchkvalue, $colchkvalue));
+ }
+}
diff --git a/library/Zend/Barcode/Object/Upca.php b/library/Zend/Barcode/Object/Upca.php
new file mode 100755
index 0000000000..1add66c781
--- /dev/null
+++ b/library/Zend/Barcode/Object/Upca.php
@@ -0,0 +1,144 @@
+barcodeLength = 12;
+ $this->mandatoryChecksum = true;
+ $this->mandatoryQuietZones = true;
+ }
+
+ /**
+ * Width of the barcode (in pixels)
+ * @return int
+ */
+ protected function calculateBarcodeWidth()
+ {
+ $quietZone = $this->getQuietZone();
+ $startCharacter = (3 * $this->barThinWidth) * $this->factor;
+ $middleCharacter = (5 * $this->barThinWidth) * $this->factor;
+ $stopCharacter = (3 * $this->barThinWidth) * $this->factor;
+ $encodedData = (7 * $this->barThinWidth) * $this->factor * 12;
+ return $quietZone + $startCharacter + $middleCharacter + $encodedData + $stopCharacter + $quietZone;
+ }
+
+ /**
+ * Prepare array to draw barcode
+ * @return array
+ */
+ protected function prepareBarcode()
+ {
+ $barcodeTable = array();
+ $height = ($this->drawText) ? 1.1 : 1;
+
+ // Start character (101)
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+
+ $textTable = str_split($this->getText());
+
+ // First character
+ $bars = str_split($this->codingMap['A'][$textTable[0]]);
+ foreach ($bars as $b) {
+ $barcodeTable[] = array($b, $this->barThinWidth, 0, $height);
+ }
+
+ // First part
+ for ($i = 1; $i < 6; $i++) {
+ $bars = str_split($this->codingMap['A'][$textTable[$i]]);
+ foreach ($bars as $b) {
+ $barcodeTable[] = array($b, $this->barThinWidth, 0, 1);
+ }
+ }
+
+ // Middle character (01010)
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, $height);
+
+ // Second part
+ for ($i = 6; $i < 11; $i++) {
+ $bars = str_split($this->codingMap['C'][$textTable[$i]]);
+ foreach ($bars as $b) {
+ $barcodeTable[] = array($b, $this->barThinWidth, 0, 1);
+ }
+ }
+
+ // Last character
+ $bars = str_split($this->codingMap['C'][$textTable[11]]);
+ foreach ($bars as $b) {
+ $barcodeTable[] = array($b, $this->barThinWidth, 0, $height);
+ }
+
+ // Stop character (101)
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+ return $barcodeTable;
+ }
+
+ /**
+ * Partial function to draw text
+ * @return void
+ */
+ protected function drawText()
+ {
+ if ($this->drawText) {
+ $text = $this->getTextToDisplay();
+ $characterWidth = (7 * $this->barThinWidth) * $this->factor;
+ $leftPosition = $this->getQuietZone() - $characterWidth;
+ for ($i = 0; $i < $this->barcodeLength; $i ++) {
+ $fontSize = $this->fontSize;
+ if ($i == 0 || $i == 11) {
+ $fontSize *= 0.8;
+ }
+ $this->addText(
+ $text{$i},
+ $fontSize * $this->factor,
+ $this->rotate(
+ $leftPosition,
+ (int) $this->withBorder * 2 + $this->factor * ($this->barHeight + $fontSize) + 1
+ ),
+ $this->font,
+ $this->foreColor,
+ 'left',
+ - $this->orientation
+ );
+ switch ($i) {
+ case 0:
+ $factor = 10;
+ break;
+ case 5:
+ $factor = 4;
+ break;
+ case 10:
+ $factor = 11;
+ break;
+ default:
+ $factor = 0;
+ }
+ $leftPosition = $leftPosition + $characterWidth + ($factor * $this->barThinWidth * $this->factor);
+ }
+ }
+ }
+}
diff --git a/library/Zend/Barcode/Object/Upce.php b/library/Zend/Barcode/Object/Upce.php
new file mode 100755
index 0000000000..2114b11e30
--- /dev/null
+++ b/library/Zend/Barcode/Object/Upce.php
@@ -0,0 +1,199 @@
+ array(
+ 0 => array('B','B','B','A','A','A'),
+ 1 => array('B','B','A','B','A','A'),
+ 2 => array('B','B','A','A','B','A'),
+ 3 => array('B','B','A','A','A','B'),
+ 4 => array('B','A','B','B','A','A'),
+ 5 => array('B','A','A','B','B','A'),
+ 6 => array('B','A','A','A','B','B'),
+ 7 => array('B','A','B','A','B','A'),
+ 8 => array('B','A','B','A','A','B'),
+ 9 => array('B','A','A','B','A','B')),
+ 1 => array(
+ 0 => array('A','A','A','B','B','B'),
+ 1 => array('A','A','B','A','B','B'),
+ 2 => array('A','A','B','B','A','B'),
+ 3 => array('A','A','B','B','B','A'),
+ 4 => array('A','B','A','A','B','B'),
+ 5 => array('A','B','B','A','A','B'),
+ 6 => array('A','B','B','B','A','A'),
+ 7 => array('A','B','A','B','A','B'),
+ 8 => array('A','B','A','B','B','A'),
+ 9 => array('A','B','B','A','B','A'))
+ );
+
+ /**
+ * Default options for Postnet barcode
+ * @return void
+ */
+ protected function getDefaultOptions()
+ {
+ $this->barcodeLength = 8;
+ $this->mandatoryChecksum = true;
+ $this->mandatoryQuietZones = true;
+ }
+
+ /**
+ * Retrieve text to encode
+ * @return string
+ */
+ public function getText()
+ {
+ $text = parent::getText();
+ if ($text[0] != 1) {
+ $text[0] = 0;
+ }
+ return $text;
+ }
+
+ /**
+ * Width of the barcode (in pixels)
+ * @return int
+ */
+ protected function calculateBarcodeWidth()
+ {
+ $quietZone = $this->getQuietZone();
+ $startCharacter = (3 * $this->barThinWidth) * $this->factor;
+ $stopCharacter = (6 * $this->barThinWidth) * $this->factor;
+ $encodedData = (7 * $this->barThinWidth) * $this->factor * 6;
+ return $quietZone + $startCharacter + $encodedData + $stopCharacter + $quietZone;
+ }
+
+ /**
+ * Prepare array to draw barcode
+ * @return array
+ */
+ protected function prepareBarcode()
+ {
+ $barcodeTable = array();
+ $height = ($this->drawText) ? 1.1 : 1;
+
+ // Start character (101)
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+
+ $textTable = str_split($this->getText());
+ $system = 0;
+ if ($textTable[0] == 1) {
+ $system = 1;
+ }
+ $checksum = $textTable[7];
+ $parity = $this->parities[$system][$checksum];
+
+ for ($i = 1; $i < 7; $i++) {
+ $bars = str_split($this->codingMap[$parity[$i - 1]][$textTable[$i]]);
+ foreach ($bars as $b) {
+ $barcodeTable[] = array($b, $this->barThinWidth, 0, 1);
+ }
+ }
+
+ // Stop character (10101)
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(0, $this->barThinWidth, 0, $height);
+ $barcodeTable[] = array(1, $this->barThinWidth, 0, $height);
+ return $barcodeTable;
+ }
+
+ /**
+ * Partial function to draw text
+ * @return void
+ */
+ protected function drawText()
+ {
+ if ($this->drawText) {
+ $text = $this->getTextToDisplay();
+ $characterWidth = (7 * $this->barThinWidth) * $this->factor;
+ $leftPosition = $this->getQuietZone() - $characterWidth;
+ for ($i = 0; $i < $this->barcodeLength; $i ++) {
+ $fontSize = $this->fontSize;
+ if ($i == 0 || $i == 7) {
+ $fontSize *= 0.8;
+ }
+ $this->addText(
+ $text{$i},
+ $fontSize * $this->factor,
+ $this->rotate(
+ $leftPosition,
+ (int) $this->withBorder * 2 + $this->factor * ($this->barHeight + $fontSize) + 1
+ ),
+ $this->font,
+ $this->foreColor,
+ 'left',
+ - $this->orientation
+ );
+ switch ($i) {
+ case 0:
+ $factor = 3;
+ break;
+ case 6:
+ $factor = 5;
+ break;
+ default:
+ $factor = 0;
+ }
+ $leftPosition = $leftPosition + $characterWidth + ($factor * $this->barThinWidth * $this->factor);
+ }
+ }
+ }
+
+ /**
+ * Particular validation for Upce barcode objects
+ * (to suppress checksum character substitution)
+ *
+ * @param string $value
+ * @param array $options
+ * @throws Exception\BarcodeValidationException
+ */
+ protected function validateSpecificText($value, $options = array())
+ {
+ $validator = new BarcodeValidator(array(
+ 'adapter' => 'upce',
+ 'checksum' => false,
+ ));
+
+ $value = $this->addLeadingZeros($value, true);
+
+ if (!$validator->isValid($value)) {
+ $message = implode("\n", $validator->getMessages());
+ throw new Exception\BarcodeValidationException($message);
+ }
+ }
+
+ /**
+ * Get barcode checksum
+ *
+ * @param string $text
+ * @return int
+ */
+ public function getChecksum($text)
+ {
+ $text = $this->addLeadingZeros($text, true);
+ if ($text[0] != 1) {
+ $text[0] = 0;
+ }
+ return parent::getChecksum($text);
+ }
+}
diff --git a/library/Zend/Barcode/ObjectPluginManager.php b/library/Zend/Barcode/ObjectPluginManager.php
new file mode 100755
index 0000000000..40345f83bf
--- /dev/null
+++ b/library/Zend/Barcode/ObjectPluginManager.php
@@ -0,0 +1,77 @@
+ 'Zend\Barcode\Object\Codabar',
+ 'code128' => 'Zend\Barcode\Object\Code128',
+ 'code25' => 'Zend\Barcode\Object\Code25',
+ 'code25interleaved' => 'Zend\Barcode\Object\Code25interleaved',
+ 'code39' => 'Zend\Barcode\Object\Code39',
+ 'ean13' => 'Zend\Barcode\Object\Ean13',
+ 'ean2' => 'Zend\Barcode\Object\Ean2',
+ 'ean5' => 'Zend\Barcode\Object\Ean5',
+ 'ean8' => 'Zend\Barcode\Object\Ean8',
+ 'error' => 'Zend\Barcode\Object\Error',
+ 'identcode' => 'Zend\Barcode\Object\Identcode',
+ 'itf14' => 'Zend\Barcode\Object\Itf14',
+ 'leitcode' => 'Zend\Barcode\Object\Leitcode',
+ 'planet' => 'Zend\Barcode\Object\Planet',
+ 'postnet' => 'Zend\Barcode\Object\Postnet',
+ 'royalmail' => 'Zend\Barcode\Object\Royalmail',
+ 'upca' => 'Zend\Barcode\Object\Upca',
+ 'upce' => 'Zend\Barcode\Object\Upce',
+ );
+
+ /**
+ * Validate the plugin
+ *
+ * Checks that the barcode parser loaded is an instance
+ * of Object\AbstractObject.
+ *
+ * @param mixed $plugin
+ * @return void
+ * @throws Exception\InvalidArgumentException if invalid
+ */
+ public function validatePlugin($plugin)
+ {
+ if ($plugin instanceof Object\AbstractObject) {
+ // we're okay
+ return;
+ }
+
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Plugin of type %s is invalid; must extend %s\Object\AbstractObject',
+ (is_object($plugin) ? get_class($plugin) : gettype($plugin)),
+ __NAMESPACE__
+ ));
+ }
+}
diff --git a/library/Zend/Barcode/README.md b/library/Zend/Barcode/README.md
new file mode 100755
index 0000000000..2dc1c0895f
--- /dev/null
+++ b/library/Zend/Barcode/README.md
@@ -0,0 +1,14 @@
+Barcode Component from ZF2
+==========================
+
+This is the Barcode component for ZF2.
+
+- File issues at https://github.com/zendframework/zf2/issues
+- Create pull requests against https://github.com/zendframework/zf2
+- Documentation is at http://framework.zend.com/docs
+
+LICENSE
+-------
+
+The files in this archive are released under the [Zend Framework
+license](http://framework.zend.com/license), which is a 3-clause BSD license.
diff --git a/library/Zend/Barcode/Renderer/AbstractRenderer.php b/library/Zend/Barcode/Renderer/AbstractRenderer.php
new file mode 100755
index 0000000000..8a625e853b
--- /dev/null
+++ b/library/Zend/Barcode/Renderer/AbstractRenderer.php
@@ -0,0 +1,515 @@
+setOptions($options);
+ }
+ $this->type = strtolower(substr(
+ get_class($this),
+ strlen($this->rendererNamespace) + 1
+ ));
+ }
+
+ /**
+ * Set renderer state from options array
+ * @param array $options
+ * @return AbstractRenderer
+ */
+ public function setOptions($options)
+ {
+ foreach ($options as $key => $value) {
+ $method = 'set' . $key;
+ if (method_exists($this, $method)) {
+ $this->$method($value);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Set renderer namespace for autoloading
+ *
+ * @param string $namespace
+ * @return AbstractRenderer
+ */
+ public function setRendererNamespace($namespace)
+ {
+ $this->rendererNamespace = $namespace;
+ return $this;
+ }
+
+ /**
+ * Retrieve renderer namespace
+ *
+ * @return string
+ */
+ public function getRendererNamespace()
+ {
+ return $this->rendererNamespace;
+ }
+
+ /**
+ * Set whether background should be transparent
+ * Will work for SVG and Image (png and gif only)
+ *
+ * @param $bool
+ * @return $this
+ */
+ public function setTransparentBackground($bool)
+ {
+ $this->transparentBackground = $bool;
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getTransparentBackground()
+ {
+ return $this->transparentBackground;
+ }
+
+ /**
+ * Retrieve renderer type
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * Manually adjust top position
+ * @param int $value
+ * @return AbstractRenderer
+ * @throws Exception\OutOfRangeException
+ */
+ public function setTopOffset($value)
+ {
+ if (!is_numeric($value) || intval($value) < 0) {
+ throw new Exception\OutOfRangeException(
+ 'Vertical position must be greater than or equals 0'
+ );
+ }
+ $this->topOffset = intval($value);
+ return $this;
+ }
+
+ /**
+ * Retrieve vertical adjustment
+ * @return int
+ */
+ public function getTopOffset()
+ {
+ return $this->topOffset;
+ }
+
+ /**
+ * Manually adjust left position
+ * @param int $value
+ * @return AbstractRenderer
+ * @throws Exception\OutOfRangeException
+ */
+ public function setLeftOffset($value)
+ {
+ if (!is_numeric($value) || intval($value) < 0) {
+ throw new Exception\OutOfRangeException(
+ 'Horizontal position must be greater than or equals 0'
+ );
+ }
+ $this->leftOffset = intval($value);
+ return $this;
+ }
+
+ /**
+ * Retrieve vertical adjustment
+ * @return int
+ */
+ public function getLeftOffset()
+ {
+ return $this->leftOffset;
+ }
+
+ /**
+ * Activate/Deactivate the automatic rendering of exception
+ * @param bool $value
+ * @return AbstractRenderer
+ */
+ public function setAutomaticRenderError($value)
+ {
+ $this->automaticRenderError = (bool) $value;
+ return $this;
+ }
+
+ /**
+ * Horizontal position of the barcode in the rendering resource
+ * @param string $value
+ * @return AbstractRenderer
+ * @throws Exception\UnexpectedValueException
+ */
+ public function setHorizontalPosition($value)
+ {
+ if (!in_array($value, array('left', 'center', 'right'))) {
+ throw new Exception\UnexpectedValueException(
+ "Invalid barcode position provided must be 'left', 'center' or 'right'"
+ );
+ }
+ $this->horizontalPosition = $value;
+ return $this;
+ }
+
+ /**
+ * Horizontal position of the barcode in the rendering resource
+ * @return string
+ */
+ public function getHorizontalPosition()
+ {
+ return $this->horizontalPosition;
+ }
+
+ /**
+ * Vertical position of the barcode in the rendering resource
+ * @param string $value
+ * @return AbstractRenderer
+ * @throws Exception\UnexpectedValueException
+ */
+ public function setVerticalPosition($value)
+ {
+ if (!in_array($value, array('top', 'middle', 'bottom'))) {
+ throw new Exception\UnexpectedValueException(
+ "Invalid barcode position provided must be 'top', 'middle' or 'bottom'"
+ );
+ }
+ $this->verticalPosition = $value;
+ return $this;
+ }
+
+ /**
+ * Vertical position of the barcode in the rendering resource
+ * @return string
+ */
+ public function getVerticalPosition()
+ {
+ return $this->verticalPosition;
+ }
+
+ /**
+ * Set the size of a module
+ * @param float $value
+ * @return AbstractRenderer
+ * @throws Exception\OutOfRangeException
+ */
+ public function setModuleSize($value)
+ {
+ if (!is_numeric($value) || floatval($value) <= 0) {
+ throw new Exception\OutOfRangeException(
+ 'Float size must be greater than 0'
+ );
+ }
+ $this->moduleSize = floatval($value);
+ return $this;
+ }
+
+ /**
+ * Set the size of a module
+ * @return float
+ */
+ public function getModuleSize()
+ {
+ return $this->moduleSize;
+ }
+
+ /**
+ * Retrieve the automatic rendering of exception
+ * @return bool
+ */
+ public function getAutomaticRenderError()
+ {
+ return $this->automaticRenderError;
+ }
+
+ /**
+ * Set the barcode object
+ * @param Object\ObjectInterface $barcode
+ * @return AbstractRenderer
+ */
+ public function setBarcode(Object\ObjectInterface $barcode)
+ {
+ $this->barcode = $barcode;
+ return $this;
+ }
+
+ /**
+ * Retrieve the barcode object
+ * @return Object\ObjectInterface
+ */
+ public function getBarcode()
+ {
+ return $this->barcode;
+ }
+
+ /**
+ * Checking of parameters after all settings
+ * @return bool
+ */
+ public function checkParams()
+ {
+ $this->checkBarcodeObject();
+ $this->checkSpecificParams();
+ return true;
+ }
+
+ /**
+ * Check if a barcode object is correctly provided
+ * @return void
+ * @throws Exception\RuntimeException
+ */
+ protected function checkBarcodeObject()
+ {
+ if ($this->barcode === null) {
+ throw new Exception\RuntimeException(
+ 'No barcode object provided'
+ );
+ }
+ }
+
+ /**
+ * Calculate the left and top offset of the barcode in the
+ * rendering support
+ *
+ * @param float $supportHeight
+ * @param float $supportWidth
+ * @return void
+ */
+ protected function adjustPosition($supportHeight, $supportWidth)
+ {
+ $barcodeHeight = $this->barcode->getHeight(true) * $this->moduleSize;
+ if ($barcodeHeight != $supportHeight && $this->topOffset == 0) {
+ switch ($this->verticalPosition) {
+ case 'middle':
+ $this->topOffset = floor(($supportHeight - $barcodeHeight) / 2);
+ break;
+ case 'bottom':
+ $this->topOffset = $supportHeight - $barcodeHeight;
+ break;
+ case 'top':
+ default:
+ $this->topOffset = 0;
+ break;
+ }
+ }
+ $barcodeWidth = $this->barcode->getWidth(true) * $this->moduleSize;
+ if ($barcodeWidth != $supportWidth && $this->leftOffset == 0) {
+ switch ($this->horizontalPosition) {
+ case 'center':
+ $this->leftOffset = floor(($supportWidth - $barcodeWidth) / 2);
+ break;
+ case 'right':
+ $this->leftOffset = $supportWidth - $barcodeWidth;
+ break;
+ case 'left':
+ default:
+ $this->leftOffset = 0;
+ break;
+ }
+ }
+ }
+
+ /**
+ * Draw the barcode in the rendering resource
+ *
+ * @throws BarcodeException\ExceptionInterface
+ * @return mixed
+ */
+ public function draw()
+ {
+ try {
+ $this->checkParams();
+ $this->initRenderer();
+ $this->drawInstructionList();
+ } catch (BarcodeException\ExceptionInterface $e) {
+ if ($this->automaticRenderError && !($e instanceof BarcodeException\RendererCreationException)) {
+ $barcode = Barcode::makeBarcode(
+ 'error',
+ array('text' => $e->getMessage())
+ );
+ $this->setBarcode($barcode);
+ $this->resource = null;
+ $this->initRenderer();
+ $this->drawInstructionList();
+ } else {
+ throw $e;
+ }
+ }
+ return $this->resource;
+ }
+
+ /**
+ * Sub process to draw the barcode instructions
+ * Needed by the automatic error rendering
+ */
+ private function drawInstructionList()
+ {
+ $instructionList = $this->barcode->draw();
+ foreach ($instructionList as $instruction) {
+ switch ($instruction['type']) {
+ case 'polygon':
+ $this->drawPolygon(
+ $instruction['points'],
+ $instruction['color'],
+ $instruction['filled']
+ );
+ break;
+ case 'text': //$text, $size, $position, $font, $color, $alignment = 'center', $orientation = 0)
+ $this->drawText(
+ $instruction['text'],
+ $instruction['size'],
+ $instruction['position'],
+ $instruction['font'],
+ $instruction['color'],
+ $instruction['alignment'],
+ $instruction['orientation']
+ );
+ break;
+ default:
+ throw new Exception\UnexpectedValueException(
+ 'Unkown drawing command'
+ );
+ }
+ }
+ }
+
+ /**
+ * Checking of parameters after all settings
+ * @return void
+ */
+ abstract protected function checkSpecificParams();
+
+ /**
+ * Initialize the rendering resource
+ * @return void
+ */
+ abstract protected function initRenderer();
+
+ /**
+ * Draw a polygon in the rendering resource
+ * @param array $points
+ * @param int $color
+ * @param bool $filled
+ */
+ abstract protected function drawPolygon($points, $color, $filled = true);
+
+ /**
+ * Draw a polygon in the rendering resource
+ * @param string $text
+ * @param float $size
+ * @param array $position
+ * @param string $font
+ * @param int $color
+ * @param string $alignment
+ * @param float $orientation
+ */
+ abstract protected function drawText(
+ $text,
+ $size,
+ $position,
+ $font,
+ $color,
+ $alignment = 'center',
+ $orientation = 0
+ );
+}
diff --git a/library/Zend/Barcode/Renderer/Exception/ExceptionInterface.php b/library/Zend/Barcode/Renderer/Exception/ExceptionInterface.php
new file mode 100755
index 0000000000..f7f4d44547
--- /dev/null
+++ b/library/Zend/Barcode/Renderer/Exception/ExceptionInterface.php
@@ -0,0 +1,16 @@
+userHeight = intval($value);
+ return $this;
+ }
+
+ /**
+ * Get barcode height
+ *
+ * @return int
+ */
+ public function getHeight()
+ {
+ return $this->userHeight;
+ }
+
+ /**
+ * Set barcode width
+ *
+ * @param mixed $value
+ * @throws Exception\OutOfRangeException
+ * @return self
+ */
+ public function setWidth($value)
+ {
+ if (!is_numeric($value) || intval($value) < 0) {
+ throw new Exception\OutOfRangeException(
+ 'Image width must be greater than or equals 0'
+ );
+ }
+ $this->userWidth = intval($value);
+ return $this;
+ }
+
+ /**
+ * Get barcode width
+ *
+ * @return int
+ */
+ public function getWidth()
+ {
+ return $this->userWidth;
+ }
+
+ /**
+ * Set an image resource to draw the barcode inside
+ *
+ * @param resource $image
+ * @return Image
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setResource($image)
+ {
+ if (gettype($image) != 'resource' || get_resource_type($image) != 'gd') {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid image resource provided to setResource()'
+ );
+ }
+ $this->resource = $image;
+ return $this;
+ }
+
+ /**
+ * Set the image type to produce (png, jpeg, gif)
+ *
+ * @param string $value
+ * @throws Exception\InvalidArgumentException
+ * @return Image
+ */
+ public function setImageType($value)
+ {
+ if ($value == 'jpg') {
+ $value = 'jpeg';
+ }
+
+ if (!in_array($value, $this->allowedImageType)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Invalid type "%s" provided to setImageType()',
+ $value
+ ));
+ }
+
+ $this->imageType = $value;
+ return $this;
+ }
+
+ /**
+ * Retrieve the image type to produce
+ *
+ * @return string
+ */
+ public function getImageType()
+ {
+ return $this->imageType;
+ }
+
+ /**
+ * Initialize the image resource
+ *
+ * @return void
+ */
+ protected function initRenderer()
+ {
+ $barcodeWidth = $this->barcode->getWidth(true);
+ $barcodeHeight = $this->barcode->getHeight(true);
+
+ if (null === $this->resource) {
+ $width = $barcodeWidth;
+ $height = $barcodeHeight;
+ if ($this->userWidth && $this->barcode->getType() != 'error') {
+ $width = $this->userWidth;
+ }
+ if ($this->userHeight && $this->barcode->getType() != 'error') {
+ $height = $this->userHeight;
+ }
+
+ $this->resource = imagecreatetruecolor($width, $height);
+
+ $white = imagecolorallocate($this->resource, 255, 255, 255);
+ imagefilledrectangle($this->resource, 0, 0, $width - 1, $height - 1, $white);
+ }
+
+ $foreColor = $this->barcode->getForeColor();
+ $this->imageForeColor = imagecolorallocate(
+ $this->resource,
+ ($foreColor & 0xFF0000) >> 16,
+ ($foreColor & 0x00FF00) >> 8,
+ $foreColor & 0x0000FF
+ );
+
+ $backgroundColor = $this->barcode->getBackgroundColor();
+ $this->imageBackgroundColor = imagecolorallocate(
+ $this->resource,
+ ($backgroundColor & 0xFF0000) >> 16,
+ ($backgroundColor & 0x00FF00) >> 8,
+ $backgroundColor & 0x0000FF
+ );
+
+ // JPEG does not support transparency, if transparentBackground is true and
+ // image type is JPEG, ignore transparency
+ if ($this->getImageType() != "jpeg" && $this->transparentBackground) {
+ imagecolortransparent($this->resource, $this->imageBackgroundColor);
+ }
+
+ $this->adjustPosition(imagesy($this->resource), imagesx($this->resource));
+
+ imagefilledrectangle(
+ $this->resource,
+ $this->leftOffset,
+ $this->topOffset,
+ $this->leftOffset + $barcodeWidth - 1,
+ $this->topOffset + $barcodeHeight - 1,
+ $this->imageBackgroundColor
+ );
+ }
+
+ /**
+ * Check barcode parameters
+ *
+ * @return void
+ */
+ protected function checkSpecificParams()
+ {
+ $this->checkDimensions();
+ }
+
+ /**
+ * Check barcode dimensions
+ *
+ * @throws Exception\RuntimeException
+ * @return void
+ */
+ protected function checkDimensions()
+ {
+ if ($this->resource !== null) {
+ if (imagesy($this->resource) < $this->barcode->getHeight(true)) {
+ throw new Exception\RuntimeException(
+ 'Barcode is define outside the image (height)'
+ );
+ }
+ } else {
+ if ($this->userHeight) {
+ $height = $this->barcode->getHeight(true);
+ if ($this->userHeight < $height) {
+ throw new Exception\RuntimeException(sprintf(
+ "Barcode is define outside the image (calculated: '%d', provided: '%d')",
+ $height,
+ $this->userHeight
+ ));
+ }
+ }
+ }
+ if ($this->resource !== null) {
+ if (imagesx($this->resource) < $this->barcode->getWidth(true)) {
+ throw new Exception\RuntimeException(
+ 'Barcode is define outside the image (width)'
+ );
+ }
+ } else {
+ if ($this->userWidth) {
+ $width = $this->barcode->getWidth(true);
+ if ($this->userWidth < $width) {
+ throw new Exception\RuntimeException(sprintf(
+ "Barcode is define outside the image (calculated: '%d', provided: '%d')",
+ $width,
+ $this->userWidth
+ ));
+ }
+ }
+ }
+ }
+
+ /**
+ * Draw and render the barcode with correct headers
+ *
+ * @return mixed
+ */
+ public function render()
+ {
+ $this->draw();
+ header("Content-Type: image/" . $this->imageType);
+ $functionName = 'image' . $this->imageType;
+ $functionName($this->resource);
+
+ ErrorHandler::start(E_WARNING);
+ imagedestroy($this->resource);
+ ErrorHandler::stop();
+ }
+
+ /**
+ * Draw a polygon in the image resource
+ *
+ * @param array $points
+ * @param int $color
+ * @param bool $filled
+ */
+ protected function drawPolygon($points, $color, $filled = true)
+ {
+ $newPoints = array($points[0][0] + $this->leftOffset,
+ $points[0][1] + $this->topOffset,
+ $points[1][0] + $this->leftOffset,
+ $points[1][1] + $this->topOffset,
+ $points[2][0] + $this->leftOffset,
+ $points[2][1] + $this->topOffset,
+ $points[3][0] + $this->leftOffset,
+ $points[3][1] + $this->topOffset, );
+
+ $allocatedColor = imagecolorallocate(
+ $this->resource,
+ ($color & 0xFF0000) >> 16,
+ ($color & 0x00FF00) >> 8,
+ $color & 0x0000FF
+ );
+
+ if ($filled) {
+ imagefilledpolygon($this->resource, $newPoints, 4, $allocatedColor);
+ } else {
+ imagepolygon($this->resource, $newPoints, 4, $allocatedColor);
+ }
+ }
+
+ /**
+ * Draw a polygon in the image resource
+ *
+ * @param string $text
+ * @param float $size
+ * @param array $position
+ * @param string $font
+ * @param int $color
+ * @param string $alignment
+ * @param float $orientation
+ * @throws Exception\RuntimeException
+ */
+ protected function drawText($text, $size, $position, $font, $color, $alignment = 'center', $orientation = 0)
+ {
+ $allocatedColor = imagecolorallocate(
+ $this->resource,
+ ($color & 0xFF0000) >> 16,
+ ($color & 0x00FF00) >> 8,
+ $color & 0x0000FF
+ );
+
+ if ($font == null) {
+ $font = 3;
+ }
+ $position[0] += $this->leftOffset;
+ $position[1] += $this->topOffset;
+
+ if (is_numeric($font)) {
+ if ($orientation) {
+ /**
+ * imagestring() doesn't allow orientation, if orientation
+ * needed: a TTF font is required.
+ * Throwing an exception here, allow to use automaticRenderError
+ * to informe user of the problem instead of simply not drawing
+ * the text
+ */
+ throw new Exception\RuntimeException(
+ 'No orientation possible with GD internal font'
+ );
+ }
+ $fontWidth = imagefontwidth($font);
+ $positionY = $position[1] - imagefontheight($font) + 1;
+ switch ($alignment) {
+ case 'left':
+ $positionX = $position[0];
+ break;
+ case 'center':
+ $positionX = $position[0] - ceil(($fontWidth * strlen($text)) / 2);
+ break;
+ case 'right':
+ $positionX = $position[0] - ($fontWidth * strlen($text));
+ break;
+ }
+ imagestring($this->resource, $font, $positionX, $positionY, $text, $color);
+ } else {
+ if (!function_exists('imagettfbbox')) {
+ throw new Exception\RuntimeException(
+ 'A font was provided, but this instance of PHP does not have TTF (FreeType) support'
+ );
+ }
+
+ $box = imagettfbbox($size, 0, $font, $text);
+ switch ($alignment) {
+ case 'left':
+ $width = 0;
+ break;
+ case 'center':
+ $width = ($box[2] - $box[0]) / 2;
+ break;
+ case 'right':
+ $width = ($box[2] - $box[0]);
+ break;
+ }
+ imagettftext(
+ $this->resource,
+ $size,
+ $orientation,
+ $position[0] - ($width * cos(pi() * $orientation / 180)),
+ $position[1] + ($width * sin(pi() * $orientation / 180)),
+ $allocatedColor,
+ $font,
+ $text
+ );
+ }
+ }
+}
diff --git a/library/Zend/Barcode/Renderer/Pdf.php b/library/Zend/Barcode/Renderer/Pdf.php
new file mode 100755
index 0000000000..e4e084d491
--- /dev/null
+++ b/library/Zend/Barcode/Renderer/Pdf.php
@@ -0,0 +1,215 @@
+resource = $pdf;
+ $this->page = intval($page);
+
+ if (!count($this->resource->pages)) {
+ $this->page = 0;
+ $this->resource->pages[] = new Page(
+ Page::SIZE_A4
+ );
+ }
+ return $this;
+ }
+
+ /**
+ * Check renderer parameters
+ *
+ * @return void
+ */
+ protected function checkSpecificParams()
+ {
+ }
+
+ /**
+ * Draw the barcode in the PDF, send headers and the PDF
+ * @return mixed
+ */
+ public function render()
+ {
+ $this->draw();
+ header("Content-Type: application/pdf");
+ echo $this->resource->render();
+ }
+
+ /**
+ * Initialize the PDF resource
+ * @return void
+ */
+ protected function initRenderer()
+ {
+ if ($this->resource === null) {
+ $this->resource = new PdfDocument();
+ $this->resource->pages[] = new Page(
+ Page::SIZE_A4
+ );
+ }
+
+ $pdfPage = $this->resource->pages[$this->page];
+ $this->adjustPosition($pdfPage->getHeight(), $pdfPage->getWidth());
+ }
+
+ /**
+ * Draw a polygon in the rendering resource
+ * @param array $points
+ * @param int $color
+ * @param bool $filled
+ */
+ protected function drawPolygon($points, $color, $filled = true)
+ {
+ $page = $this->resource->pages[$this->page];
+ $x = array();
+ $y = array();
+ foreach ($points as $point) {
+ $x[] = $point[0] * $this->moduleSize + $this->leftOffset;
+ $y[] = $page->getHeight() - $point[1] * $this->moduleSize - $this->topOffset;
+ }
+ if (count($y) == 4) {
+ if ($x[0] != $x[3] && $y[0] == $y[3]) {
+ $y[0] -= ($this->moduleSize / 2);
+ $y[3] -= ($this->moduleSize / 2);
+ }
+ if ($x[1] != $x[2] && $y[1] == $y[2]) {
+ $y[1] += ($this->moduleSize / 2);
+ $y[2] += ($this->moduleSize / 2);
+ }
+ }
+
+ $color = new Color\Rgb(
+ (($color & 0xFF0000) >> 16) / 255.0,
+ (($color & 0x00FF00) >> 8) / 255.0,
+ ($color & 0x0000FF) / 255.0
+ );
+
+ $page->setLineColor($color);
+ $page->setFillColor($color);
+ $page->setLineWidth($this->moduleSize);
+
+ $fillType = ($filled)
+ ? Page::SHAPE_DRAW_FILL_AND_STROKE
+ : Page::SHAPE_DRAW_STROKE;
+
+ $page->drawPolygon($x, $y, $fillType);
+ }
+
+ /**
+ * Draw a polygon in the rendering resource
+ * @param string $text
+ * @param float $size
+ * @param array $position
+ * @param string $font
+ * @param int $color
+ * @param string $alignment
+ * @param float $orientation
+ */
+ protected function drawText(
+ $text,
+ $size,
+ $position,
+ $font,
+ $color,
+ $alignment = 'center',
+ $orientation = 0
+ ) {
+ $page = $this->resource->pages[$this->page];
+ $color = new Color\Rgb(
+ (($color & 0xFF0000) >> 16) / 255.0,
+ (($color & 0x00FF00) >> 8) / 255.0,
+ ($color & 0x0000FF) / 255.0
+ );
+
+ $page->setLineColor($color);
+ $page->setFillColor($color);
+ $page->setFont(Font::fontWithPath($font), $size * $this->moduleSize * 1.2);
+
+ $width = $this->widthForStringUsingFontSize(
+ $text,
+ Font::fontWithPath($font),
+ $size * $this->moduleSize
+ );
+
+ $angle = pi() * $orientation / 180;
+ $left = $position[0] * $this->moduleSize + $this->leftOffset;
+ $top = $page->getHeight() - $position[1] * $this->moduleSize - $this->topOffset;
+
+ switch ($alignment) {
+ case 'center':
+ $left -= ($width / 2) * cos($angle);
+ $top -= ($width / 2) * sin($angle);
+ break;
+ case 'right':
+ $left -= $width;
+ break;
+ }
+ $page->rotate($left, $top, $angle);
+ $page->drawText($text, $left, $top);
+ $page->rotate($left, $top, - $angle);
+ }
+
+ /**
+ * Calculate the width of a string:
+ * in case of using alignment parameter in drawText
+ * @param string $text
+ * @param Font $font
+ * @param float $fontSize
+ * @return float
+ */
+ public function widthForStringUsingFontSize($text, $font, $fontSize)
+ {
+ $drawingString = iconv('UTF-8', 'UTF-16BE//IGNORE', $text);
+ $characters = array();
+ for ($i = 0, $len = strlen($drawingString); $i < $len; $i++) {
+ $characters[] = (ord($drawingString[$i ++]) << 8) | ord($drawingString[$i]);
+ }
+ $glyphs = $font->glyphNumbersForCharacters($characters);
+ $widths = $font->widthsForGlyphs($glyphs);
+ $stringWidth = (array_sum($widths) / $font->getUnitsPerEm()) * $fontSize;
+ return $stringWidth;
+ }
+}
diff --git a/library/Zend/Barcode/Renderer/RendererInterface.php b/library/Zend/Barcode/Renderer/RendererInterface.php
new file mode 100755
index 0000000000..795654f303
--- /dev/null
+++ b/library/Zend/Barcode/Renderer/RendererInterface.php
@@ -0,0 +1,161 @@
+userHeight = intval($value);
+ return $this;
+ }
+
+ /**
+ * Get barcode height
+ *
+ * @return int
+ */
+ public function getHeight()
+ {
+ return $this->userHeight;
+ }
+
+ /**
+ * Set barcode width
+ *
+ * @param mixed $value
+ * @throws Exception\OutOfRangeException
+ * @return self
+ */
+ public function setWidth($value)
+ {
+ if (!is_numeric($value) || intval($value) < 0) {
+ throw new Exception\OutOfRangeException(
+ 'Svg width must be greater than or equals 0'
+ );
+ }
+ $this->userWidth = intval($value);
+ return $this;
+ }
+
+ /**
+ * Get barcode width
+ *
+ * @return int
+ */
+ public function getWidth()
+ {
+ return $this->userWidth;
+ }
+
+ /**
+ * Set an image resource to draw the barcode inside
+ *
+ * @param DOMDocument $svg
+ * @return Svg
+ */
+ public function setResource(DOMDocument $svg)
+ {
+ $this->resource = $svg;
+ return $this;
+ }
+
+ /**
+ * Initialize the image resource
+ *
+ * @return void
+ */
+ protected function initRenderer()
+ {
+ $barcodeWidth = $this->barcode->getWidth(true);
+ $barcodeHeight = $this->barcode->getHeight(true);
+
+ $backgroundColor = $this->barcode->getBackgroundColor();
+ $imageBackgroundColor = 'rgb(' . implode(', ', array(($backgroundColor & 0xFF0000) >> 16,
+ ($backgroundColor & 0x00FF00) >> 8,
+ ($backgroundColor & 0x0000FF))) . ')';
+
+ $width = $barcodeWidth;
+ $height = $barcodeHeight;
+ if ($this->userWidth && $this->barcode->getType() != 'error') {
+ $width = $this->userWidth;
+ }
+ if ($this->userHeight && $this->barcode->getType() != 'error') {
+ $height = $this->userHeight;
+ }
+ if ($this->resource === null) {
+ $this->resource = new DOMDocument('1.0', 'utf-8');
+ $this->resource->formatOutput = true;
+ $this->rootElement = $this->resource->createElement('svg');
+ $this->rootElement->setAttribute('xmlns', "http://www.w3.org/2000/svg");
+ $this->rootElement->setAttribute('version', '1.1');
+ $this->rootElement->setAttribute('width', $width);
+ $this->rootElement->setAttribute('height', $height);
+
+ $this->appendRootElement(
+ 'title',
+ array(),
+ "Barcode " . strtoupper($this->barcode->getType()) . " " . $this->barcode->getText()
+ );
+ } else {
+ $this->readRootElement();
+ $width = $this->rootElement->getAttribute('width');
+ $height = $this->rootElement->getAttribute('height');
+ }
+ $this->adjustPosition($height, $width);
+
+ $rect = array('x' => $this->leftOffset,
+ 'y' => $this->topOffset,
+ 'width' => ($this->leftOffset + $barcodeWidth - 1),
+ 'height' => ($this->topOffset + $barcodeHeight - 1),
+ 'fill' => $imageBackgroundColor);
+
+ if ($this->transparentBackground) {
+ $rect['fill-opacity'] = 0;
+ }
+
+ $this->appendRootElement('rect', $rect);
+ }
+
+ protected function readRootElement()
+ {
+ if ($this->resource !== null) {
+ $this->rootElement = $this->resource->documentElement;
+ }
+ }
+
+ /**
+ * Append a new DOMElement to the root element
+ *
+ * @param string $tagName
+ * @param array $attributes
+ * @param string $textContent
+ */
+ protected function appendRootElement($tagName, $attributes = array(), $textContent = null)
+ {
+ $newElement = $this->createElement($tagName, $attributes, $textContent);
+ $this->rootElement->appendChild($newElement);
+ }
+
+ /**
+ * Create DOMElement
+ *
+ * @param string $tagName
+ * @param array $attributes
+ * @param string $textContent
+ * @return DOMElement
+ */
+ protected function createElement($tagName, $attributes = array(), $textContent = null)
+ {
+ $element = $this->resource->createElement($tagName);
+ foreach ($attributes as $k => $v) {
+ $element->setAttribute($k, $v);
+ }
+ if ($textContent !== null) {
+ $element->appendChild(new DOMText((string) $textContent));
+ }
+ return $element;
+ }
+
+ /**
+ * Check barcode parameters
+ *
+ * @return void
+ */
+ protected function checkSpecificParams()
+ {
+ $this->checkDimensions();
+ }
+
+ /**
+ * Check barcode dimensions
+ *
+ * @throws Exception\RuntimeException
+ * @return void
+ */
+ protected function checkDimensions()
+ {
+ if ($this->resource !== null) {
+ $this->readRootElement();
+ $height = (float) $this->rootElement->getAttribute('height');
+ if ($height < $this->barcode->getHeight(true)) {
+ throw new Exception\RuntimeException(
+ 'Barcode is define outside the image (height)'
+ );
+ }
+ } else {
+ if ($this->userHeight) {
+ $height = $this->barcode->getHeight(true);
+ if ($this->userHeight < $height) {
+ throw new Exception\RuntimeException(sprintf(
+ "Barcode is define outside the image (calculated: '%d', provided: '%d')",
+ $height,
+ $this->userHeight
+ ));
+ }
+ }
+ }
+ if ($this->resource !== null) {
+ $this->readRootElement();
+ $width = $this->rootElement->getAttribute('width');
+ if ($width < $this->barcode->getWidth(true)) {
+ throw new Exception\RuntimeException(
+ 'Barcode is define outside the image (width)'
+ );
+ }
+ } else {
+ if ($this->userWidth) {
+ $width = (float) $this->barcode->getWidth(true);
+ if ($this->userWidth < $width) {
+ throw new Exception\RuntimeException(sprintf(
+ "Barcode is define outside the image (calculated: '%d', provided: '%d')",
+ $width,
+ $this->userWidth
+ ));
+ }
+ }
+ }
+ }
+
+ /**
+ * Draw the barcode in the rendering resource
+ * @return DOMDocument
+ */
+ public function draw()
+ {
+ parent::draw();
+ $this->resource->appendChild($this->rootElement);
+ return $this->resource;
+ }
+
+ /**
+ * Draw and render the barcode with correct headers
+ *
+ * @return mixed
+ */
+ public function render()
+ {
+ $this->draw();
+ header("Content-Type: image/svg+xml");
+ echo $this->resource->saveXML();
+ }
+
+ /**
+ * Draw a polygon in the svg resource
+ *
+ * @param array $points
+ * @param int $color
+ * @param bool $filled
+ */
+ protected function drawPolygon($points, $color, $filled = true)
+ {
+ $color = 'rgb(' . implode(', ', array(($color & 0xFF0000) >> 16,
+ ($color & 0x00FF00) >> 8,
+ ($color & 0x0000FF))) . ')';
+ $orientation = $this->getBarcode()->getOrientation();
+ $newPoints = array(
+ $points[0][0] + $this->leftOffset,
+ $points[0][1] + $this->topOffset,
+ $points[1][0] + $this->leftOffset,
+ $points[1][1] + $this->topOffset,
+ $points[2][0] + $this->leftOffset + cos(-$orientation),
+ $points[2][1] + $this->topOffset - sin($orientation),
+ $points[3][0] + $this->leftOffset + cos(-$orientation),
+ $points[3][1] + $this->topOffset - sin($orientation),
+ );
+ $newPoints = implode(' ', $newPoints);
+ $attributes = array();
+ $attributes['points'] = $newPoints;
+ $attributes['fill'] = $color;
+
+ // SVG passes a rect in as the first call to drawPolygon, we'll need to intercept
+ // this and set transparency if necessary.
+ if (!$this->drawPolygonExecuted) {
+ if ($this->transparentBackground) {
+ $attributes['fill-opacity'] = '0';
+ }
+ $this->drawPolygonExecuted = true;
+ }
+
+ $this->appendRootElement('polygon', $attributes);
+ }
+
+ /**
+ * Draw a polygon in the svg resource
+ *
+ * @param string $text
+ * @param float $size
+ * @param array $position
+ * @param string $font
+ * @param int $color
+ * @param string $alignment
+ * @param float $orientation
+ */
+ protected function drawText($text, $size, $position, $font, $color, $alignment = 'center', $orientation = 0)
+ {
+ $color = 'rgb(' . implode(', ', array(($color & 0xFF0000) >> 16,
+ ($color & 0x00FF00) >> 8,
+ ($color & 0x0000FF))) . ')';
+ $attributes = array();
+ $attributes['x'] = $position[0] + $this->leftOffset;
+ $attributes['y'] = $position[1] + $this->topOffset;
+ //$attributes['font-family'] = $font;
+ $attributes['color'] = $color;
+ $attributes['font-size'] = $size * 1.2;
+ switch ($alignment) {
+ case 'left':
+ $textAnchor = 'start';
+ break;
+ case 'right':
+ $textAnchor = 'end';
+ break;
+ case 'center':
+ default:
+ $textAnchor = 'middle';
+ }
+ $attributes['style'] = 'text-anchor: ' . $textAnchor;
+ $attributes['transform'] = 'rotate('
+ . (- $orientation)
+ . ', '
+ . ($position[0] + $this->leftOffset)
+ . ', ' . ($position[1] + $this->topOffset)
+ . ')';
+ $this->appendRootElement('text', $attributes, $text);
+ }
+}
diff --git a/library/Zend/Barcode/RendererPluginManager.php b/library/Zend/Barcode/RendererPluginManager.php
new file mode 100755
index 0000000000..0c8c2f8aee
--- /dev/null
+++ b/library/Zend/Barcode/RendererPluginManager.php
@@ -0,0 +1,62 @@
+ 'Zend\Barcode\Renderer\Image',
+ 'pdf' => 'Zend\Barcode\Renderer\Pdf',
+ 'svg' => 'Zend\Barcode\Renderer\Svg'
+ );
+
+ /**
+ * Validate the plugin
+ *
+ * Checks that the barcode parser loaded is an instance
+ * of Renderer\AbstractRenderer.
+ *
+ * @param mixed $plugin
+ * @return void
+ * @throws Exception\InvalidArgumentException if invalid
+ */
+ public function validatePlugin($plugin)
+ {
+ if ($plugin instanceof Renderer\AbstractRenderer) {
+ // we're okay
+ return;
+ }
+
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Plugin of type %s is invalid; must extend %s\Renderer\AbstractRenderer',
+ (is_object($plugin) ? get_class($plugin) : gettype($plugin)),
+ __NAMESPACE__
+ ));
+ }
+}
diff --git a/library/Zend/Barcode/composer.json b/library/Zend/Barcode/composer.json
new file mode 100755
index 0000000000..f351302ebb
--- /dev/null
+++ b/library/Zend/Barcode/composer.json
@@ -0,0 +1,35 @@
+{
+ "name": "zendframework/zend-barcode",
+ "description": "provides a generic way to generate barcodes",
+ "license": "BSD-3-Clause",
+ "keywords": [
+ "zf2",
+ "barcode"
+ ],
+ "homepage": "https://github.com/zendframework/zf2",
+ "autoload": {
+ "psr-0": {
+ "Zend\\Barcode\\": ""
+ }
+ },
+ "target-dir": "Zend/Barcode",
+ "require": {
+ "php": ">=5.3.23",
+ "zendframework/zend-stdlib": "self.version",
+ "zendframework/zend-validator": "self.version"
+ },
+ "require-dev": {
+ "zendframework/zend-servicemanager": "self.version",
+ "zendframework/zendpdf": "*"
+ },
+ "suggest": {
+ "zendframework/zend-servicemanager": "Zend\\ServiceManager component, required when using the factory methods of Zend\\Barcode.",
+ "zendframework/zendpdf": "ZendPdf component"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev",
+ "dev-develop": "2.4-dev"
+ }
+ }
+}
diff --git a/library/Zend/Cache/CONTRIBUTING.md b/library/Zend/Cache/CONTRIBUTING.md
new file mode 100755
index 0000000000..e77f5d2d5b
--- /dev/null
+++ b/library/Zend/Cache/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# CONTRIBUTING
+
+Please don't open pull requests against this repository, please use https://github.com/zendframework/zf2.
\ No newline at end of file
diff --git a/library/Zend/Cache/Exception/BadMethodCallException.php b/library/Zend/Cache/Exception/BadMethodCallException.php
new file mode 100755
index 0000000000..dc8af1fc5f
--- /dev/null
+++ b/library/Zend/Cache/Exception/BadMethodCallException.php
@@ -0,0 +1,15 @@
+options = $options;
+ return $this;
+ }
+
+ /**
+ * Get all pattern options
+ *
+ * @return PatternOptions
+ */
+ public function getOptions()
+ {
+ if (null === $this->options) {
+ $this->setOptions(new PatternOptions());
+ }
+ return $this->options;
+ }
+}
diff --git a/library/Zend/Cache/Pattern/CallbackCache.php b/library/Zend/Cache/Pattern/CallbackCache.php
new file mode 100755
index 0000000000..a26a00746a
--- /dev/null
+++ b/library/Zend/Cache/Pattern/CallbackCache.php
@@ -0,0 +1,200 @@
+getStorage()) {
+ throw new Exception\InvalidArgumentException("Missing option 'storage'");
+ }
+ return $this;
+ }
+
+ /**
+ * Call the specified callback or get the result from cache
+ *
+ * @param callable $callback A valid callback
+ * @param array $args Callback arguments
+ * @return mixed Result
+ * @throws Exception\RuntimeException if invalid cached data
+ * @throws \Exception
+ */
+ public function call($callback, array $args = array())
+ {
+ $options = $this->getOptions();
+ $storage = $options->getStorage();
+ $success = null;
+ $key = $this->generateCallbackKey($callback, $args);
+ $result = $storage->getItem($key, $success);
+ if ($success) {
+ if (!array_key_exists(0, $result)) {
+ throw new Exception\RuntimeException("Invalid cached data for key '{$key}'");
+ }
+
+ echo isset($result[1]) ? $result[1] : '';
+ return $result[0];
+ }
+
+ $cacheOutput = $options->getCacheOutput();
+ if ($cacheOutput) {
+ ob_start();
+ ob_implicit_flush(false);
+ }
+
+ // TODO: do not cache on errors using [set|restore]_error_handler
+
+ try {
+ if ($args) {
+ $ret = call_user_func_array($callback, $args);
+ } else {
+ $ret = call_user_func($callback);
+ }
+ } catch (\Exception $e) {
+ if ($cacheOutput) {
+ ob_end_flush();
+ }
+ throw $e;
+ }
+
+ if ($cacheOutput) {
+ $data = array($ret, ob_get_flush());
+ } else {
+ $data = array($ret);
+ }
+
+ $storage->setItem($key, $data);
+
+ return $ret;
+ }
+
+ /**
+ * function call handler
+ *
+ * @param string $function Function name to call
+ * @param array $args Function arguments
+ * @return mixed
+ * @throws Exception\RuntimeException
+ * @throws \Exception
+ */
+ public function __call($function, array $args)
+ {
+ return $this->call($function, $args);
+ }
+
+ /**
+ * Generate a unique key in base of a key representing the callback part
+ * and a key representing the arguments part.
+ *
+ * @param callable $callback A valid callback
+ * @param array $args Callback arguments
+ * @return string
+ * @throws Exception\RuntimeException
+ * @throws Exception\InvalidArgumentException
+ */
+ public function generateKey($callback, array $args = array())
+ {
+ return $this->generateCallbackKey($callback, $args);
+ }
+
+ /**
+ * Generate a unique key in base of a key representing the callback part
+ * and a key representing the arguments part.
+ *
+ * @param callable $callback A valid callback
+ * @param array $args Callback arguments
+ * @throws Exception\RuntimeException if callback not serializable
+ * @throws Exception\InvalidArgumentException if invalid callback
+ * @return string
+ */
+ protected function generateCallbackKey($callback, array $args)
+ {
+ if (!is_callable($callback, false, $callbackKey)) {
+ throw new Exception\InvalidArgumentException('Invalid callback');
+ }
+
+ // functions, methods and classnames are case-insensitive
+ $callbackKey = strtolower($callbackKey);
+
+ // generate a unique key of object callbacks
+ if (is_object($callback)) { // Closures & __invoke
+ $object = $callback;
+ } elseif (isset($callback[0])) { // array($object, 'method')
+ $object = $callback[0];
+ }
+ if (isset($object)) {
+ ErrorHandler::start();
+ try {
+ $serializedObject = serialize($object);
+ } catch (\Exception $e) {
+ ErrorHandler::stop();
+ throw new Exception\RuntimeException("Can't serialize callback: see previous exception", 0, $e);
+ }
+ $error = ErrorHandler::stop();
+
+ if (!$serializedObject) {
+ throw new Exception\RuntimeException(
+ sprintf('Cannot serialize callback%s', ($error ? ': ' . $error->getMessage() : '')),
+ 0,
+ $error
+ );
+ }
+ $callbackKey.= $serializedObject;
+ }
+
+ return md5($callbackKey) . $this->generateArgumentsKey($args);
+ }
+
+ /**
+ * Generate a unique key of the argument part.
+ *
+ * @param array $args
+ * @throws Exception\RuntimeException
+ * @return string
+ */
+ protected function generateArgumentsKey(array $args)
+ {
+ if (!$args) {
+ return '';
+ }
+
+ ErrorHandler::start();
+ try {
+ $serializedArgs = serialize(array_values($args));
+ } catch (\Exception $e) {
+ ErrorHandler::stop();
+ throw new Exception\RuntimeException("Can't serialize arguments: see previous exception", 0, $e);
+ }
+ $error = ErrorHandler::stop();
+
+ if (!$serializedArgs) {
+ throw new Exception\RuntimeException(
+ sprintf('Cannot serialize arguments%s', ($error ? ': ' . $error->getMessage() : '')),
+ 0,
+ $error
+ );
+ }
+
+ return md5($serializedArgs);
+ }
+}
diff --git a/library/Zend/Cache/Pattern/CaptureCache.php b/library/Zend/Cache/Pattern/CaptureCache.php
new file mode 100755
index 0000000000..7d5cec6981
--- /dev/null
+++ b/library/Zend/Cache/Pattern/CaptureCache.php
@@ -0,0 +1,388 @@
+detectPageId();
+ }
+
+ $that = $this;
+ ob_start(function ($content) use ($that, $pageId) {
+ $that->set($content, $pageId);
+
+ // http://php.net/manual/function.ob-start.php
+ // -> If output_callback returns FALSE original input is sent to the browser.
+ return false;
+ });
+
+ ob_implicit_flush(false);
+ }
+
+ /**
+ * Write content to page identity
+ *
+ * @param string $content
+ * @param null|string $pageId
+ * @throws Exception\LogicException
+ */
+ public function set($content, $pageId = null)
+ {
+ $publicDir = $this->getOptions()->getPublicDir();
+ if ($publicDir === null) {
+ throw new Exception\LogicException("Option 'public_dir' no set");
+ }
+
+ if ($pageId === null) {
+ $pageId = $this->detectPageId();
+ }
+
+ $path = $this->pageId2Path($pageId);
+ $file = $path . DIRECTORY_SEPARATOR . $this->pageId2Filename($pageId);
+
+ $this->createDirectoryStructure($publicDir . DIRECTORY_SEPARATOR . $path);
+ $this->putFileContent($publicDir . DIRECTORY_SEPARATOR . $file, $content);
+ }
+
+ /**
+ * Get from cache
+ *
+ * @param null|string $pageId
+ * @return string|null
+ * @throws Exception\LogicException
+ * @throws Exception\RuntimeException
+ */
+ public function get($pageId = null)
+ {
+ $publicDir = $this->getOptions()->getPublicDir();
+ if ($publicDir === null) {
+ throw new Exception\LogicException("Option 'public_dir' no set");
+ }
+
+ if ($pageId === null) {
+ $pageId = $this->detectPageId();
+ }
+
+ $file = $publicDir
+ . DIRECTORY_SEPARATOR . $this->pageId2Path($pageId)
+ . DIRECTORY_SEPARATOR . $this->pageId2Filename($pageId);
+
+ if (file_exists($file)) {
+ ErrorHandler::start();
+ $content = file_get_contents($file);
+ $error = ErrorHandler::stop();
+ if ($content === false) {
+ throw new Exception\RuntimeException("Failed to read cached pageId '{$pageId}'", 0, $error);
+ }
+ return $content;
+ }
+ }
+
+ /**
+ * Checks if a cache with given id exists
+ *
+ * @param null|string $pageId
+ * @throws Exception\LogicException
+ * @return bool
+ */
+ public function has($pageId = null)
+ {
+ $publicDir = $this->getOptions()->getPublicDir();
+ if ($publicDir === null) {
+ throw new Exception\LogicException("Option 'public_dir' no set");
+ }
+
+ if ($pageId === null) {
+ $pageId = $this->detectPageId();
+ }
+
+ $file = $publicDir
+ . DIRECTORY_SEPARATOR . $this->pageId2Path($pageId)
+ . DIRECTORY_SEPARATOR . $this->pageId2Filename($pageId);
+
+ return file_exists($file);
+ }
+
+ /**
+ * Remove from cache
+ *
+ * @param null|string $pageId
+ * @throws Exception\LogicException
+ * @throws Exception\RuntimeException
+ * @return bool
+ */
+ public function remove($pageId = null)
+ {
+ $publicDir = $this->getOptions()->getPublicDir();
+ if ($publicDir === null) {
+ throw new Exception\LogicException("Option 'public_dir' no set");
+ }
+
+ if ($pageId === null) {
+ $pageId = $this->detectPageId();
+ }
+
+ $file = $publicDir
+ . DIRECTORY_SEPARATOR . $this->pageId2Path($pageId)
+ . DIRECTORY_SEPARATOR . $this->pageId2Filename($pageId);
+
+ if (file_exists($file)) {
+ ErrorHandler::start();
+ $res = unlink($file);
+ $err = ErrorHandler::stop();
+ if (!$res) {
+ throw new Exception\RuntimeException("Failed to remove cached pageId '{$pageId}'", 0, $err);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Clear cached pages matching glob pattern
+ *
+ * @param string $pattern
+ * @throws Exception\LogicException
+ */
+ public function clearByGlob($pattern = '**')
+ {
+ $publicDir = $this->getOptions()->getPublicDir();
+ if ($publicDir === null) {
+ throw new Exception\LogicException("Option 'public_dir' no set");
+ }
+
+ $it = new \GlobIterator(
+ $publicDir . '/' . $pattern,
+ \GlobIterator::CURRENT_AS_SELF | \GlobIterator::SKIP_DOTS | \GlobIterator::UNIX_PATHS
+ );
+ foreach ($it as $pathname => $entry) {
+ if ($entry->isFile()) {
+ unlink($pathname);
+ }
+ }
+ }
+
+ /**
+ * Determine the page to save from the request
+ *
+ * @throws Exception\RuntimeException
+ * @return string
+ */
+ protected function detectPageId()
+ {
+ if (!isset($_SERVER['REQUEST_URI'])) {
+ throw new Exception\RuntimeException("Can't auto-detect current page identity");
+ }
+
+ return $_SERVER['REQUEST_URI'];
+ }
+
+ /**
+ * Get filename for page id
+ *
+ * @param string $pageId
+ * @return string
+ */
+ protected function pageId2Filename($pageId)
+ {
+ if (substr($pageId, -1) === '/') {
+ return $this->getOptions()->getIndexFilename();
+ }
+
+ return basename($pageId);
+ }
+
+ /**
+ * Get path for page id
+ *
+ * @param string $pageId
+ * @return string
+ */
+ protected function pageId2Path($pageId)
+ {
+ if (substr($pageId, -1) == '/') {
+ $path = rtrim($pageId, '/');
+ } else {
+ $path = dirname($pageId);
+ }
+
+ // convert requested "/" to the valid local directory separator
+ if ('/' != DIRECTORY_SEPARATOR) {
+ $path = str_replace('/', DIRECTORY_SEPARATOR, $path);
+ }
+
+ return $path;
+ }
+
+ /**
+ * Write content to a file
+ *
+ * @param string $file File complete path
+ * @param string $data Data to write
+ * @return void
+ * @throws Exception\RuntimeException
+ */
+ protected function putFileContent($file, $data)
+ {
+ $options = $this->getOptions();
+ $locking = $options->getFileLocking();
+ $perm = $options->getFilePermission();
+ $umask = $options->getUmask();
+ if ($umask !== false && $perm !== false) {
+ $perm = $perm & ~$umask;
+ }
+
+ ErrorHandler::start();
+
+ $umask = ($umask !== false) ? umask($umask) : false;
+ $rs = file_put_contents($file, $data, $locking ? LOCK_EX : 0);
+ if ($umask) {
+ umask($umask);
+ }
+
+ if ($rs === false) {
+ $err = ErrorHandler::stop();
+ throw new Exception\RuntimeException("Error writing file '{$file}'", 0, $err);
+ }
+
+ if ($perm !== false && !chmod($file, $perm)) {
+ $oct = decoct($perm);
+ $err = ErrorHandler::stop();
+ throw new Exception\RuntimeException("chmod('{$file}', 0{$oct}) failed", 0, $err);
+ }
+
+ ErrorHandler::stop();
+ }
+
+ /**
+ * Creates directory if not already done.
+ *
+ * @param string $pathname
+ * @return void
+ * @throws Exception\RuntimeException
+ */
+ protected function createDirectoryStructure($pathname)
+ {
+ // Directory structure already exists
+ if (file_exists($pathname)) {
+ return;
+ }
+
+ $options = $this->getOptions();
+ $perm = $options->getDirPermission();
+ $umask = $options->getUmask();
+ if ($umask !== false && $perm !== false) {
+ $perm = $perm & ~$umask;
+ }
+
+ ErrorHandler::start();
+
+ if ($perm === false) {
+ // build-in mkdir function is enough
+
+ $umask = ($umask !== false) ? umask($umask) : false;
+ $res = mkdir($pathname, ($perm !== false) ? $perm : 0777, true);
+
+ if ($umask !== false) {
+ umask($umask);
+ }
+
+ if (!$res) {
+ $oct = ($perm === false) ? '777' : decoct($perm);
+ $err = ErrorHandler::stop();
+ throw new Exception\RuntimeException("mkdir('{$pathname}', 0{$oct}, true) failed", 0, $err);
+ }
+
+ if ($perm !== false && !chmod($pathname, $perm)) {
+ $oct = decoct($perm);
+ $err = ErrorHandler::stop();
+ throw new Exception\RuntimeException("chmod('{$pathname}', 0{$oct}) failed", 0, $err);
+ }
+ } else {
+ // build-in mkdir function sets permission together with current umask
+ // which doesn't work well on multo threaded webservers
+ // -> create directories one by one and set permissions
+
+ // find existing path and missing path parts
+ $parts = array();
+ $path = $pathname;
+ while (!file_exists($path)) {
+ array_unshift($parts, basename($path));
+ $nextPath = dirname($path);
+ if ($nextPath === $path) {
+ break;
+ }
+ $path = $nextPath;
+ }
+
+ // make all missing path parts
+ foreach ($parts as $part) {
+ $path.= DIRECTORY_SEPARATOR . $part;
+
+ // create a single directory, set and reset umask immediately
+ $umask = ($umask !== false) ? umask($umask) : false;
+ $res = mkdir($path, ($perm === false) ? 0777 : $perm, false);
+ if ($umask !== false) {
+ umask($umask);
+ }
+
+ if (!$res) {
+ $oct = ($perm === false) ? '777' : decoct($perm);
+ ErrorHandler::stop();
+ throw new Exception\RuntimeException(
+ "mkdir('{$path}', 0{$oct}, false) failed"
+ );
+ }
+
+ if ($perm !== false && !chmod($path, $perm)) {
+ $oct = decoct($perm);
+ ErrorHandler::stop();
+ throw new Exception\RuntimeException(
+ "chmod('{$path}', 0{$oct}) failed"
+ );
+ }
+ }
+ }
+
+ ErrorHandler::stop();
+ }
+
+ /**
+ * Returns the generated file name.
+ *
+ * @param null|string $pageId
+ * @return string
+ */
+ public function getFilename($pageId = null)
+ {
+ if ($pageId === null) {
+ $pageId = $this->detectPageId();
+ }
+
+ $publicDir = $this->getOptions()->getPublicDir();
+ $path = $this->pageId2Path($pageId);
+ $file = $path . DIRECTORY_SEPARATOR . $this->pageId2Filename($pageId);
+
+ return $publicDir . $file;
+ }
+}
diff --git a/library/Zend/Cache/Pattern/ClassCache.php b/library/Zend/Cache/Pattern/ClassCache.php
new file mode 100755
index 0000000000..238feed5ef
--- /dev/null
+++ b/library/Zend/Cache/Pattern/ClassCache.php
@@ -0,0 +1,167 @@
+getClass()) {
+ throw new Exception\InvalidArgumentException("Missing option 'class'");
+ } elseif (!$options->getStorage()) {
+ throw new Exception\InvalidArgumentException("Missing option 'storage'");
+ }
+ return $this;
+ }
+
+ /**
+ * Call and cache a class method
+ *
+ * @param string $method Method name to call
+ * @param array $args Method arguments
+ * @return mixed
+ * @throws Exception\RuntimeException
+ * @throws \Exception
+ */
+ public function call($method, array $args = array())
+ {
+ $options = $this->getOptions();
+ $classname = $options->getClass();
+ $method = strtolower($method);
+ $callback = $classname . '::' . $method;
+
+ $cache = $options->getCacheByDefault();
+ if ($cache) {
+ $cache = !in_array($method, $options->getClassNonCacheMethods());
+ } else {
+ $cache = in_array($method, $options->getClassCacheMethods());
+ }
+
+ if (!$cache) {
+ if ($args) {
+ return call_user_func_array($callback, $args);
+ } else {
+ return $classname::$method();
+ }
+ }
+
+ return parent::call($callback, $args);
+ }
+
+ /**
+ * Generate a unique key in base of a key representing the callback part
+ * and a key representing the arguments part.
+ *
+ * @param string $method The method
+ * @param array $args Callback arguments
+ * @return string
+ * @throws Exception\RuntimeException
+ */
+ public function generateKey($method, array $args = array())
+ {
+ return $this->generateCallbackKey(
+ $this->getOptions()->getClass() . '::' . $method,
+ $args
+ );
+ }
+
+ /**
+ * Generate a unique key in base of a key representing the callback part
+ * and a key representing the arguments part.
+ *
+ * @param callable $callback A valid callback
+ * @param array $args Callback arguments
+ * @return string
+ * @throws Exception\RuntimeException
+ */
+ protected function generateCallbackKey($callback, array $args)
+ {
+ $callbackKey = md5(strtolower($callback));
+ $argumentKey = $this->generateArgumentsKey($args);
+ return $callbackKey . $argumentKey;
+ }
+
+ /**
+ * Calling a method of the entity.
+ *
+ * @param string $method Method name to call
+ * @param array $args Method arguments
+ * @return mixed
+ * @throws Exception\RuntimeException
+ * @throws \Exception
+ */
+ public function __call($method, array $args)
+ {
+ return $this->call($method, $args);
+ }
+
+ /**
+ * Set a static property
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return void
+ * @see http://php.net/manual/language.oop5.overloading.php#language.oop5.overloading.members
+ */
+ public function __set($name, $value)
+ {
+ $class = $this->getOptions()->getClass();
+ $class::$name = $value;
+ }
+
+ /**
+ * Get a static property
+ *
+ * @param string $name
+ * @return mixed
+ * @see http://php.net/manual/language.oop5.overloading.php#language.oop5.overloading.members
+ */
+ public function __get($name)
+ {
+ $class = $this->getOptions()->getClass();
+ return $class::$name;
+ }
+
+ /**
+ * Is a static property exists.
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ $class = $this->getOptions()->getClass();
+ return isset($class::$name);
+ }
+
+ /**
+ * Unset a static property
+ *
+ * @param string $name
+ * @return void
+ */
+ public function __unset($name)
+ {
+ $class = $this->getOptions()->getClass();
+ unset($class::$name);
+ }
+}
diff --git a/library/Zend/Cache/Pattern/ObjectCache.php b/library/Zend/Cache/Pattern/ObjectCache.php
new file mode 100755
index 0000000000..9ef2a24142
--- /dev/null
+++ b/library/Zend/Cache/Pattern/ObjectCache.php
@@ -0,0 +1,284 @@
+getObject()) {
+ throw new Exception\InvalidArgumentException("Missing option 'object'");
+ } elseif (!$options->getStorage()) {
+ throw new Exception\InvalidArgumentException("Missing option 'storage'");
+ }
+ }
+
+ /**
+ * Call and cache a class method
+ *
+ * @param string $method Method name to call
+ * @param array $args Method arguments
+ * @return mixed
+ * @throws Exception\RuntimeException
+ * @throws \Exception
+ */
+ public function call($method, array $args = array())
+ {
+ $options = $this->getOptions();
+ $object = $options->getObject();
+ $method = strtolower($method);
+
+ // handle magic methods
+ switch ($method) {
+ case '__set':
+ $property = array_shift($args);
+ $value = array_shift($args);
+
+ $object->{$property} = $value;
+
+ if (!$options->getObjectCacheMagicProperties()
+ || property_exists($object, $property)
+ ) {
+ // no caching if property isn't magic
+ // or caching magic properties is disabled
+ return;
+ }
+
+ // remove cached __get and __isset
+ $removeKeys = null;
+ if (method_exists($object, '__get')) {
+ $removeKeys[] = $this->generateKey('__get', array($property));
+ }
+ if (method_exists($object, '__isset')) {
+ $removeKeys[] = $this->generateKey('__isset', array($property));
+ }
+ if ($removeKeys) {
+ $options->getStorage()->removeItems($removeKeys);
+ }
+ return;
+
+ case '__get':
+ $property = array_shift($args);
+
+ if (!$options->getObjectCacheMagicProperties()
+ || property_exists($object, $property)
+ ) {
+ // no caching if property isn't magic
+ // or caching magic properties is disabled
+ return $object->{$property};
+ }
+
+ array_unshift($args, $property);
+ return parent::call(array($object, '__get'), $args);
+
+ case '__isset':
+ $property = array_shift($args);
+
+ if (!$options->getObjectCacheMagicProperties()
+ || property_exists($object, $property)
+ ) {
+ // no caching if property isn't magic
+ // or caching magic properties is disabled
+ return isset($object->{$property});
+ }
+
+ return parent::call(array($object, '__isset'), array($property));
+
+ case '__unset':
+ $property = array_shift($args);
+
+ unset($object->{$property});
+
+ if (!$options->getObjectCacheMagicProperties()
+ || property_exists($object, $property)
+ ) {
+ // no caching if property isn't magic
+ // or caching magic properties is disabled
+ return;
+ }
+
+ // remove previous cached __get and __isset calls
+ $removeKeys = null;
+ if (method_exists($object, '__get')) {
+ $removeKeys[] = $this->generateKey('__get', array($property));
+ }
+ if (method_exists($object, '__isset')) {
+ $removeKeys[] = $this->generateKey('__isset', array($property));
+ }
+ if ($removeKeys) {
+ $options->getStorage()->removeItems($removeKeys);
+ }
+ return;
+ }
+
+ $cache = $options->getCacheByDefault();
+ if ($cache) {
+ $cache = !in_array($method, $options->getObjectNonCacheMethods());
+ } else {
+ $cache = in_array($method, $options->getObjectCacheMethods());
+ }
+
+ if (!$cache) {
+ if ($args) {
+ return call_user_func_array(array($object, $method), $args);
+ }
+ return $object->{$method}();
+ }
+
+ return parent::call(array($object, $method), $args);
+ }
+
+ /**
+ * Generate a unique key in base of a key representing the callback part
+ * and a key representing the arguments part.
+ *
+ * @param string $method The method
+ * @param array $args Callback arguments
+ * @return string
+ * @throws Exception\RuntimeException
+ */
+ public function generateKey($method, array $args = array())
+ {
+ return $this->generateCallbackKey(
+ array($this->getOptions()->getObject(), $method),
+ $args
+ );
+ }
+
+ /**
+ * Generate a unique key in base of a key representing the callback part
+ * and a key representing the arguments part.
+ *
+ * @param callable $callback A valid callback
+ * @param array $args Callback arguments
+ * @return string
+ * @throws Exception\RuntimeException
+ */
+ protected function generateCallbackKey($callback, array $args = array())
+ {
+ $callbackKey = md5($this->getOptions()->getObjectKey() . '::' . strtolower($callback[1]));
+ $argumentKey = $this->generateArgumentsKey($args);
+ return $callbackKey . $argumentKey;
+ }
+
+ /**
+ * Class method call handler
+ *
+ * @param string $method Method name to call
+ * @param array $args Method arguments
+ * @return mixed
+ * @throws Exception\RuntimeException
+ * @throws \Exception
+ */
+ public function __call($method, array $args)
+ {
+ return $this->call($method, $args);
+ }
+
+ /**
+ * Writing data to properties.
+ *
+ * NOTE:
+ * Magic properties will be cached too if the option cacheMagicProperties
+ * is enabled and the property doesn't exist in real. If so it calls __set
+ * and removes cached data of previous __get and __isset calls.
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return void
+ * @see http://php.net/manual/language.oop5.overloading.php#language.oop5.overloading.members
+ */
+ public function __set($name, $value)
+ {
+ return $this->call('__set', array($name, $value));
+ }
+
+ /**
+ * Reading data from properties.
+ *
+ * NOTE:
+ * Magic properties will be cached too if the option cacheMagicProperties
+ * is enabled and the property doesn't exist in real. If so it calls __get.
+ *
+ * @param string $name
+ * @return mixed
+ * @see http://php.net/manual/language.oop5.overloading.php#language.oop5.overloading.members
+ */
+ public function __get($name)
+ {
+ return $this->call('__get', array($name));
+ }
+
+ /**
+ * Checking existing properties.
+ *
+ * NOTE:
+ * Magic properties will be cached too if the option cacheMagicProperties
+ * is enabled and the property doesn't exist in real. If so it calls __get.
+ *
+ * @param string $name
+ * @return bool
+ * @see http://php.net/manual/language.oop5.overloading.php#language.oop5.overloading.members
+ */
+ public function __isset($name)
+ {
+ return $this->call('__isset', array($name));
+ }
+
+ /**
+ * Unseting a property.
+ *
+ * NOTE:
+ * Magic properties will be cached too if the option cacheMagicProperties
+ * is enabled and the property doesn't exist in real. If so it removes
+ * previous cached __isset and __get calls.
+ *
+ * @param string $name
+ * @return void
+ * @see http://php.net/manual/language.oop5.overloading.php#language.oop5.overloading.members
+ */
+ public function __unset($name)
+ {
+ return $this->call('__unset', array($name));
+ }
+
+ /**
+ * Handle casting to string
+ *
+ * @return string
+ * @see http://php.net/manual/language.oop5.magic.php#language.oop5.magic.tostring
+ */
+ public function __toString()
+ {
+ return $this->call('__toString');
+ }
+
+ /**
+ * Handle invoke calls
+ *
+ * @return mixed
+ * @see http://php.net/manual/language.oop5.magic.php#language.oop5.magic.invoke
+ */
+ public function __invoke()
+ {
+ return $this->call('__invoke', func_get_args());
+ }
+}
diff --git a/library/Zend/Cache/Pattern/OutputCache.php b/library/Zend/Cache/Pattern/OutputCache.php
new file mode 100755
index 0000000000..549e17122b
--- /dev/null
+++ b/library/Zend/Cache/Pattern/OutputCache.php
@@ -0,0 +1,89 @@
+getStorage()) {
+ throw new Exception\InvalidArgumentException("Missing option 'storage'");
+ }
+
+ return $this;
+ }
+
+ /**
+ * if there is a cached item with the given key display it's data and return true
+ * else start buffering output until end() is called or the script ends.
+ *
+ * @param string $key Key
+ * @throws Exception\MissingKeyException if key is missing
+ * @return bool
+ */
+ public function start($key)
+ {
+ if (($key = (string) $key) === '') {
+ throw new Exception\MissingKeyException('Missing key to read/write output from cache');
+ }
+
+ $success = null;
+ $data = $this->getOptions()->getStorage()->getItem($key, $success);
+ if ($success) {
+ echo $data;
+ return true;
+ }
+
+ ob_start();
+ ob_implicit_flush(false);
+ $this->keyStack[] = $key;
+ return false;
+ }
+
+ /**
+ * Stops buffering output, write buffered data to cache using the given key on start()
+ * and displays the buffer.
+ *
+ * @throws Exception\RuntimeException if output cache not started or buffering not active
+ * @return bool TRUE on success, FALSE on failure writing to cache
+ */
+ public function end()
+ {
+ $key = array_pop($this->keyStack);
+ if ($key === null) {
+ throw new Exception\RuntimeException('Output cache not started');
+ }
+
+ $output = ob_get_flush();
+ if ($output === false) {
+ throw new Exception\RuntimeException('Output buffering not active');
+ }
+
+ return $this->getOptions()->getStorage()->setItem($key, $output);
+ }
+}
diff --git a/library/Zend/Cache/Pattern/PatternInterface.php b/library/Zend/Cache/Pattern/PatternInterface.php
new file mode 100755
index 0000000000..a79bf51883
--- /dev/null
+++ b/library/Zend/Cache/Pattern/PatternInterface.php
@@ -0,0 +1,28 @@
+filePermission = false;
+ $this->dirPermission = false;
+ }
+
+ parent::__construct($options);
+ }
+
+ /**
+ * Set flag indicating whether or not to cache by default
+ *
+ * Used by:
+ * - ClassCache
+ * - ObjectCache
+ *
+ * @param bool $cacheByDefault
+ * @return PatternOptions
+ */
+ public function setCacheByDefault($cacheByDefault)
+ {
+ $this->cacheByDefault = $cacheByDefault;
+ return $this;
+ }
+
+ /**
+ * Do we cache by default?
+ *
+ * Used by:
+ * - ClassCache
+ * - ObjectCache
+ *
+ * @return bool
+ */
+ public function getCacheByDefault()
+ {
+ return $this->cacheByDefault;
+ }
+
+ /**
+ * Set whether or not to cache output
+ *
+ * Used by:
+ * - CallbackCache
+ * - ClassCache
+ * - ObjectCache
+ *
+ * @param bool $cacheOutput
+ * @return PatternOptions
+ */
+ public function setCacheOutput($cacheOutput)
+ {
+ $this->cacheOutput = (bool) $cacheOutput;
+ return $this;
+ }
+
+ /**
+ * Will we cache output?
+ *
+ * Used by:
+ * - CallbackCache
+ * - ClassCache
+ * - ObjectCache
+ *
+ * @return bool
+ */
+ public function getCacheOutput()
+ {
+ return $this->cacheOutput;
+ }
+
+ /**
+ * Set class name
+ *
+ * Used by:
+ * - ClassCache
+ *
+ * @param string $class
+ * @throws Exception\InvalidArgumentException
+ * @return PatternOptions
+ */
+ public function setClass($class)
+ {
+ if (!is_string($class)) {
+ throw new Exception\InvalidArgumentException('Invalid classname provided; must be a string');
+ }
+ $this->class = $class;
+ return $this;
+ }
+
+ /**
+ * Get class name
+ *
+ * Used by:
+ * - ClassCache
+ *
+ * @return null|string
+ */
+ public function getClass()
+ {
+ return $this->class;
+ }
+
+ /**
+ * Set list of method return values to cache
+ *
+ * Used by:
+ * - ClassCache
+ *
+ * @param array $classCacheMethods
+ * @return PatternOptions
+ */
+ public function setClassCacheMethods(array $classCacheMethods)
+ {
+ $this->classCacheMethods = $this->recursiveStrtolower($classCacheMethods);
+ return $this;
+ }
+
+ /**
+ * Get list of methods from which to cache return values
+ *
+ * Used by:
+ * - ClassCache
+ *
+ * @return array
+ */
+ public function getClassCacheMethods()
+ {
+ return $this->classCacheMethods;
+ }
+
+ /**
+ * Set list of method return values NOT to cache
+ *
+ * Used by:
+ * - ClassCache
+ *
+ * @param array $classNonCacheMethods
+ * @return PatternOptions
+ */
+ public function setClassNonCacheMethods(array $classNonCacheMethods)
+ {
+ $this->classNonCacheMethods = $this->recursiveStrtolower($classNonCacheMethods);
+ return $this;
+ }
+
+ /**
+ * Get list of methods from which NOT to cache return values
+ *
+ * Used by:
+ * - ClassCache
+ *
+ * @return array
+ */
+ public function getClassNonCacheMethods()
+ {
+ return $this->classNonCacheMethods;
+ }
+
+ /**
+ * Set directory permission
+ *
+ * @param false|int $dirPermission
+ * @throws Exception\InvalidArgumentException
+ * @return PatternOptions
+ */
+ public function setDirPermission($dirPermission)
+ {
+ if ($dirPermission !== false) {
+ if (is_string($dirPermission)) {
+ $dirPermission = octdec($dirPermission);
+ } else {
+ $dirPermission = (int) $dirPermission;
+ }
+
+ // validate
+ if (($dirPermission & 0700) != 0700) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid directory permission: need permission to execute, read and write by owner'
+ );
+ }
+ }
+
+ $this->dirPermission = $dirPermission;
+ return $this;
+ }
+
+ /**
+ * Gets directory permission
+ *
+ * @return false|int
+ */
+ public function getDirPermission()
+ {
+ return $this->dirPermission;
+ }
+
+ /**
+ * Set umask
+ *
+ * Used by:
+ * - CaptureCache
+ *
+ * @param false|int $umask
+ * @throws Exception\InvalidArgumentException
+ * @return PatternOptions
+ */
+ public function setUmask($umask)
+ {
+ if ($umask !== false) {
+ if (is_string($umask)) {
+ $umask = octdec($umask);
+ } else {
+ $umask = (int) $umask;
+ }
+
+ // validate
+ if ($umask & 0700) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid umask: need permission to execute, read and write by owner'
+ );
+ }
+
+ // normalize
+ $umask = $umask & 0777;
+ }
+
+ $this->umask = $umask;
+ return $this;
+ }
+
+ /**
+ * Get umask
+ *
+ * Used by:
+ * - CaptureCache
+ *
+ * @return false|int
+ */
+ public function getUmask()
+ {
+ return $this->umask;
+ }
+
+ /**
+ * Set whether or not file locking should be used
+ *
+ * Used by:
+ * - CaptureCache
+ *
+ * @param bool $fileLocking
+ * @return PatternOptions
+ */
+ public function setFileLocking($fileLocking)
+ {
+ $this->fileLocking = (bool) $fileLocking;
+ return $this;
+ }
+
+ /**
+ * Is file locking enabled?
+ *
+ * Used by:
+ * - CaptureCache
+ *
+ * @return bool
+ */
+ public function getFileLocking()
+ {
+ return $this->fileLocking;
+ }
+
+ /**
+ * Set file permission
+ *
+ * @param false|int $filePermission
+ * @throws Exception\InvalidArgumentException
+ * @return PatternOptions
+ */
+ public function setFilePermission($filePermission)
+ {
+ if ($filePermission !== false) {
+ if (is_string($filePermission)) {
+ $filePermission = octdec($filePermission);
+ } else {
+ $filePermission = (int) $filePermission;
+ }
+
+ // validate
+ if (($filePermission & 0600) != 0600) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid file permission: need permission to read and write by owner'
+ );
+ } elseif ($filePermission & 0111) {
+ throw new Exception\InvalidArgumentException(
+ "Invalid file permission: Files shoudn't be executable"
+ );
+ }
+ }
+
+ $this->filePermission = $filePermission;
+ return $this;
+ }
+
+ /**
+ * Gets file permission
+ *
+ * @return false|int
+ */
+ public function getFilePermission()
+ {
+ return $this->filePermission;
+ }
+
+ /**
+ * Set value for index filename
+ *
+ * @param string $indexFilename
+ * @return PatternOptions
+ */
+ public function setIndexFilename($indexFilename)
+ {
+ $this->indexFilename = (string) $indexFilename;
+ return $this;
+ }
+
+ /**
+ * Get value for index filename
+ *
+ * @return string
+ */
+ public function getIndexFilename()
+ {
+ return $this->indexFilename;
+ }
+
+ /**
+ * Set object to cache
+ *
+ * @param mixed $object
+ * @throws Exception\InvalidArgumentException
+ * @return PatternOptions
+ */
+ public function setObject($object)
+ {
+ if (!is_object($object)) {
+ throw new Exception\InvalidArgumentException(
+ sprintf('%s expects an object; received "%s"', __METHOD__, gettype($object))
+ );
+ }
+ $this->object = $object;
+ return $this;
+ }
+
+ /**
+ * Get object to cache
+ *
+ * @return null|object
+ */
+ public function getObject()
+ {
+ return $this->object;
+ }
+
+ /**
+ * Set flag indicating whether or not to cache magic properties
+ *
+ * Used by:
+ * - ObjectCache
+ *
+ * @param bool $objectCacheMagicProperties
+ * @return PatternOptions
+ */
+ public function setObjectCacheMagicProperties($objectCacheMagicProperties)
+ {
+ $this->objectCacheMagicProperties = (bool) $objectCacheMagicProperties;
+ return $this;
+ }
+
+ /**
+ * Should we cache magic properties?
+ *
+ * Used by:
+ * - ObjectCache
+ *
+ * @return bool
+ */
+ public function getObjectCacheMagicProperties()
+ {
+ return $this->objectCacheMagicProperties;
+ }
+
+ /**
+ * Set list of object methods for which to cache return values
+ *
+ * @param array $objectCacheMethods
+ * @return PatternOptions
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setObjectCacheMethods(array $objectCacheMethods)
+ {
+ $this->objectCacheMethods = $this->normalizeObjectMethods($objectCacheMethods);
+ return $this;
+ }
+
+ /**
+ * Get list of object methods for which to cache return values
+ *
+ * @return array
+ */
+ public function getObjectCacheMethods()
+ {
+ return $this->objectCacheMethods;
+ }
+
+ /**
+ * Set the object key part.
+ *
+ * Used to generate a callback key in order to speed up key generation.
+ *
+ * Used by:
+ * - ObjectCache
+ *
+ * @param mixed $objectKey
+ * @return PatternOptions
+ */
+ public function setObjectKey($objectKey)
+ {
+ if ($objectKey !== null) {
+ $this->objectKey = (string) $objectKey;
+ } else {
+ $this->objectKey = null;
+ }
+ return $this;
+ }
+
+ /**
+ * Get object key
+ *
+ * Used by:
+ * - ObjectCache
+ *
+ * @return string
+ */
+ public function getObjectKey()
+ {
+ if (!$this->objectKey) {
+ return get_class($this->getObject());
+ }
+ return $this->objectKey;
+ }
+
+ /**
+ * Set list of object methods for which NOT to cache return values
+ *
+ * @param array $objectNonCacheMethods
+ * @return PatternOptions
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setObjectNonCacheMethods(array $objectNonCacheMethods)
+ {
+ $this->objectNonCacheMethods = $this->normalizeObjectMethods($objectNonCacheMethods);
+ return $this;
+ }
+
+ /**
+ * Get list of object methods for which NOT to cache return values
+ *
+ * @return array
+ */
+ public function getObjectNonCacheMethods()
+ {
+ return $this->objectNonCacheMethods;
+ }
+
+ /**
+ * Set location of public directory
+ *
+ * Used by:
+ * - CaptureCache
+ *
+ * @param string $publicDir
+ * @throws Exception\InvalidArgumentException
+ * @return PatternOptions
+ */
+ public function setPublicDir($publicDir)
+ {
+ $publicDir = (string) $publicDir;
+
+ if (!is_dir($publicDir)) {
+ throw new Exception\InvalidArgumentException(
+ "Public directory '{$publicDir}' not found or not a directory"
+ );
+ } elseif (!is_writable($publicDir)) {
+ throw new Exception\InvalidArgumentException(
+ "Public directory '{$publicDir}' not writable"
+ );
+ } elseif (!is_readable($publicDir)) {
+ throw new Exception\InvalidArgumentException(
+ "Public directory '{$publicDir}' not readable"
+ );
+ }
+
+ $this->publicDir = rtrim(realpath($publicDir), DIRECTORY_SEPARATOR);
+ return $this;
+ }
+
+ /**
+ * Get location of public directory
+ *
+ * Used by:
+ * - CaptureCache
+ *
+ * @return null|string
+ */
+ public function getPublicDir()
+ {
+ return $this->publicDir;
+ }
+
+ /**
+ * Set storage adapter
+ *
+ * Required for the following Pattern classes:
+ * - CallbackCache
+ * - ClassCache
+ * - ObjectCache
+ * - OutputCache
+ *
+ * @param string|array|Storage $storage
+ * @return PatternOptions
+ */
+ public function setStorage($storage)
+ {
+ $this->storage = $this->storageFactory($storage);
+ return $this;
+ }
+
+ /**
+ * Get storage adapter
+ *
+ * Used by:
+ * - CallbackCache
+ * - ClassCache
+ * - ObjectCache
+ * - OutputCache
+ *
+ * @return null|Storage
+ */
+ public function getStorage()
+ {
+ return $this->storage;
+ }
+
+ /**
+ * Recursively apply strtolower on all values of an array, and return as a
+ * list of unique values
+ *
+ * @param array $array
+ * @return array
+ */
+ protected function recursiveStrtolower(array $array)
+ {
+ return array_values(array_unique(array_map('strtolower', $array)));
+ }
+
+ /**
+ * Normalize object methods
+ *
+ * Recursively casts values to lowercase, then determines if any are in a
+ * list of methods not handled, raising an exception if so.
+ *
+ * @param array $methods
+ * @return array
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function normalizeObjectMethods(array $methods)
+ {
+ $methods = $this->recursiveStrtolower($methods);
+ $intersect = array_intersect(array('__set', '__get', '__unset', '__isset'), $methods);
+ if (!empty($intersect)) {
+ throw new Exception\InvalidArgumentException(
+ "Magic properties are handled by option 'cache_magic_properties'"
+ );
+ }
+ return $methods;
+ }
+
+ /**
+ * Create a storage object from a given specification
+ *
+ * @param array|string|Storage $storage
+ * @throws Exception\InvalidArgumentException
+ * @return Storage
+ */
+ protected function storageFactory($storage)
+ {
+ if (is_array($storage)) {
+ $storage = StorageFactory::factory($storage);
+ } elseif (is_string($storage)) {
+ $storage = StorageFactory::adapterFactory($storage);
+ } elseif (!($storage instanceof Storage)) {
+ throw new Exception\InvalidArgumentException(
+ 'The storage must be an instanceof Zend\Cache\Storage\StorageInterface '
+ . 'or an array passed to Zend\Cache\Storage::factory '
+ . 'or simply the name of the storage adapter'
+ );
+ }
+
+ return $storage;
+ }
+}
diff --git a/library/Zend/Cache/PatternFactory.php b/library/Zend/Cache/PatternFactory.php
new file mode 100755
index 0000000000..dceed0032f
--- /dev/null
+++ b/library/Zend/Cache/PatternFactory.php
@@ -0,0 +1,92 @@
+setOptions($options);
+ return $patternName;
+ }
+
+ $pattern = static::getPluginManager()->get($patternName);
+ $pattern->setOptions($options);
+ return $pattern;
+ }
+
+ /**
+ * Get the pattern plugin manager
+ *
+ * @return PatternPluginManager
+ */
+ public static function getPluginManager()
+ {
+ if (static::$plugins === null) {
+ static::$plugins = new PatternPluginManager();
+ }
+
+ return static::$plugins;
+ }
+
+ /**
+ * Set the pattern plugin manager
+ *
+ * @param PatternPluginManager $plugins
+ * @return void
+ */
+ public static function setPluginManager(PatternPluginManager $plugins)
+ {
+ static::$plugins = $plugins;
+ }
+
+ /**
+ * Reset pattern plugin manager to default
+ *
+ * @return void
+ */
+ public static function resetPluginManager()
+ {
+ static::$plugins = null;
+ }
+}
diff --git a/library/Zend/Cache/PatternPluginManager.php b/library/Zend/Cache/PatternPluginManager.php
new file mode 100755
index 0000000000..7d5d0e1a69
--- /dev/null
+++ b/library/Zend/Cache/PatternPluginManager.php
@@ -0,0 +1,66 @@
+ 'Zend\Cache\Pattern\CallbackCache',
+ 'capture' => 'Zend\Cache\Pattern\CaptureCache',
+ 'class' => 'Zend\Cache\Pattern\ClassCache',
+ 'object' => 'Zend\Cache\Pattern\ObjectCache',
+ 'output' => 'Zend\Cache\Pattern\OutputCache',
+ 'page' => 'Zend\Cache\Pattern\PageCache',
+ );
+
+ /**
+ * Don't share by default
+ *
+ * @var array
+ */
+ protected $shareByDefault = false;
+
+ /**
+ * Validate the plugin
+ *
+ * Checks that the pattern adapter loaded is an instance of Pattern\PatternInterface.
+ *
+ * @param mixed $plugin
+ * @return void
+ * @throws Exception\RuntimeException if invalid
+ */
+ public function validatePlugin($plugin)
+ {
+ if ($plugin instanceof Pattern\PatternInterface) {
+ // we're okay
+ return;
+ }
+
+ throw new Exception\RuntimeException(sprintf(
+ 'Plugin of type %s is invalid; must implement %s\Pattern\PatternInterface',
+ (is_object($plugin) ? get_class($plugin) : gettype($plugin)),
+ __NAMESPACE__
+ ));
+ }
+}
diff --git a/library/Zend/Cache/README.md b/library/Zend/Cache/README.md
new file mode 100755
index 0000000000..62aa9e0e0c
--- /dev/null
+++ b/library/Zend/Cache/README.md
@@ -0,0 +1,14 @@
+Cache Component from ZF2
+========================
+
+This is the Cache component for ZF2.
+
+- File issues at https://github.com/zendframework/zf2/issues
+- Create pull requests against https://github.com/zendframework/zf2
+- Documentation is at http://framework.zend.com/docs
+
+LICENSE
+-------
+
+The files in this archive are released under the [Zend Framework
+license](http://framework.zend.com/license), which is a 3-clause BSD license.
diff --git a/library/Zend/Cache/Service/StorageCacheAbstractServiceFactory.php b/library/Zend/Cache/Service/StorageCacheAbstractServiceFactory.php
new file mode 100755
index 0000000000..66eb6505e5
--- /dev/null
+++ b/library/Zend/Cache/Service/StorageCacheAbstractServiceFactory.php
@@ -0,0 +1,88 @@
+getConfig($services);
+ if (empty($config)) {
+ return false;
+ }
+
+ return (isset($config[$requestedName]) && is_array($config[$requestedName]));
+ }
+
+ /**
+ * @param ServiceLocatorInterface $services
+ * @param string $name
+ * @param string $requestedName
+ * @return \Zend\Cache\Storage\StorageInterface
+ */
+ public function createServiceWithName(ServiceLocatorInterface $services, $name, $requestedName)
+ {
+ $config = $this->getConfig($services);
+ $config = $config[$requestedName];
+ return StorageFactory::factory($config);
+ }
+
+ /**
+ * Retrieve cache configuration, if any
+ *
+ * @param ServiceLocatorInterface $services
+ * @return array
+ */
+ protected function getConfig(ServiceLocatorInterface $services)
+ {
+ if ($this->config !== null) {
+ return $this->config;
+ }
+
+ if (!$services->has('Config')) {
+ $this->config = array();
+ return $this->config;
+ }
+
+ $config = $services->get('Config');
+ if (!isset($config[$this->configKey])) {
+ $this->config = array();
+ return $this->config;
+ }
+
+ $this->config = $config[$this->configKey];
+ return $this->config;
+ }
+}
diff --git a/library/Zend/Cache/Service/StorageCacheFactory.php b/library/Zend/Cache/Service/StorageCacheFactory.php
new file mode 100755
index 0000000000..f2c2049936
--- /dev/null
+++ b/library/Zend/Cache/Service/StorageCacheFactory.php
@@ -0,0 +1,30 @@
+get('Config');
+ $cacheConfig = isset($config['cache']) ? $config['cache'] : array();
+ $cache = StorageFactory::factory($cacheConfig);
+
+ return $cache;
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/AbstractAdapter.php b/library/Zend/Cache/Storage/Adapter/AbstractAdapter.php
new file mode 100755
index 0000000000..71edf763e8
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/AbstractAdapter.php
@@ -0,0 +1,1580 @@
+setOptions($options);
+ }
+ }
+
+ /**
+ * Destructor
+ *
+ * detach all registered plugins to free
+ * event handles of event manager
+ *
+ * @return void
+ */
+ public function __destruct()
+ {
+ foreach ($this->getPluginRegistry() as $plugin) {
+ $this->removePlugin($plugin);
+ }
+
+ if ($this->eventHandles) {
+ $events = $this->getEventManager();
+ foreach ($this->eventHandles as $handle) {
+ $events->detach($handle);
+ }
+ }
+ }
+
+ /* configuration */
+
+ /**
+ * Set options.
+ *
+ * @param array|Traversable|AdapterOptions $options
+ * @return AbstractAdapter
+ * @see getOptions()
+ */
+ public function setOptions($options)
+ {
+ if ($this->options !== $options) {
+ if (!$options instanceof AdapterOptions) {
+ $options = new AdapterOptions($options);
+ }
+
+ if ($this->options) {
+ $this->options->setAdapter(null);
+ }
+ $options->setAdapter($this);
+ $this->options = $options;
+
+ $event = new Event('option', $this, new ArrayObject($options->toArray()));
+ $this->getEventManager()->trigger($event);
+ }
+ return $this;
+ }
+
+ /**
+ * Get options.
+ *
+ * @return AdapterOptions
+ * @see setOptions()
+ */
+ public function getOptions()
+ {
+ if (!$this->options) {
+ $this->setOptions(new AdapterOptions());
+ }
+ return $this->options;
+ }
+
+ /**
+ * Enable/Disable caching.
+ *
+ * Alias of setWritable and setReadable.
+ *
+ * @see setWritable()
+ * @see setReadable()
+ * @param bool $flag
+ * @return AbstractAdapter
+ */
+ public function setCaching($flag)
+ {
+ $flag = (bool) $flag;
+ $options = $this->getOptions();
+ $options->setWritable($flag);
+ $options->setReadable($flag);
+ return $this;
+ }
+
+ /**
+ * Get caching enabled.
+ *
+ * Alias of getWritable and getReadable.
+ *
+ * @see getWritable()
+ * @see getReadable()
+ * @return bool
+ */
+ public function getCaching()
+ {
+ $options = $this->getOptions();
+ return ($options->getWritable() && $options->getReadable());
+ }
+
+ /* Event/Plugin handling */
+
+ /**
+ * Get the event manager
+ *
+ * @return EventManagerInterface
+ */
+ public function getEventManager()
+ {
+ if ($this->events === null) {
+ $this->events = new EventManager(array(__CLASS__, get_class($this)));
+ }
+ return $this->events;
+ }
+
+ /**
+ * Trigger a pre event and return the event response collection
+ *
+ * @param string $eventName
+ * @param ArrayObject $args
+ * @return \Zend\EventManager\ResponseCollection All handler return values
+ */
+ protected function triggerPre($eventName, ArrayObject $args)
+ {
+ return $this->getEventManager()->trigger(new Event($eventName . '.pre', $this, $args));
+ }
+
+ /**
+ * Triggers the PostEvent and return the result value.
+ *
+ * @param string $eventName
+ * @param ArrayObject $args
+ * @param mixed $result
+ * @return mixed
+ */
+ protected function triggerPost($eventName, ArrayObject $args, & $result)
+ {
+ $postEvent = new PostEvent($eventName . '.post', $this, $args, $result);
+ $eventRs = $this->getEventManager()->trigger($postEvent);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ return $postEvent->getResult();
+ }
+
+ /**
+ * Trigger an exception event
+ *
+ * If the ExceptionEvent has the flag "throwException" enabled throw the
+ * exception after trigger else return the result.
+ *
+ * @param string $eventName
+ * @param ArrayObject $args
+ * @param mixed $result
+ * @param \Exception $exception
+ * @throws Exception\ExceptionInterface
+ * @return mixed
+ */
+ protected function triggerException($eventName, ArrayObject $args, & $result, \Exception $exception)
+ {
+ $exceptionEvent = new ExceptionEvent($eventName . '.exception', $this, $args, $result, $exception);
+ $eventRs = $this->getEventManager()->trigger($exceptionEvent);
+
+ if ($exceptionEvent->getThrowException()) {
+ throw $exceptionEvent->getException();
+ }
+
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ return $exceptionEvent->getResult();
+ }
+
+ /**
+ * Check if a plugin is registered
+ *
+ * @param Plugin\PluginInterface $plugin
+ * @return bool
+ */
+ public function hasPlugin(Plugin\PluginInterface $plugin)
+ {
+ $registry = $this->getPluginRegistry();
+ return $registry->contains($plugin);
+ }
+
+ /**
+ * Register a plugin
+ *
+ * @param Plugin\PluginInterface $plugin
+ * @param int $priority
+ * @return AbstractAdapter Fluent interface
+ * @throws Exception\LogicException
+ */
+ public function addPlugin(Plugin\PluginInterface $plugin, $priority = 1)
+ {
+ $registry = $this->getPluginRegistry();
+ if ($registry->contains($plugin)) {
+ throw new Exception\LogicException(sprintf(
+ 'Plugin of type "%s" already registered',
+ get_class($plugin)
+ ));
+ }
+
+ $plugin->attach($this->getEventManager(), $priority);
+ $registry->attach($plugin);
+
+ return $this;
+ }
+
+ /**
+ * Unregister an already registered plugin
+ *
+ * @param Plugin\PluginInterface $plugin
+ * @return AbstractAdapter Fluent interface
+ * @throws Exception\LogicException
+ */
+ public function removePlugin(Plugin\PluginInterface $plugin)
+ {
+ $registry = $this->getPluginRegistry();
+ if ($registry->contains($plugin)) {
+ $plugin->detach($this->getEventManager());
+ $registry->detach($plugin);
+ }
+ return $this;
+ }
+
+ /**
+ * Return registry of plugins
+ *
+ * @return SplObjectStorage
+ */
+ public function getPluginRegistry()
+ {
+ if (!$this->pluginRegistry instanceof SplObjectStorage) {
+ $this->pluginRegistry = new SplObjectStorage();
+ }
+ return $this->pluginRegistry;
+ }
+
+ /* reading */
+
+ /**
+ * Get an item.
+ *
+ * @param string $key
+ * @param bool $success
+ * @param mixed $casToken
+ * @return mixed Data on success, null on failure
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers getItem.pre(PreEvent)
+ * @triggers getItem.post(PostEvent)
+ * @triggers getItem.exception(ExceptionEvent)
+ */
+ public function getItem($key, & $success = null, & $casToken = null)
+ {
+ if (!$this->getOptions()->getReadable()) {
+ $success = false;
+ return null;
+ }
+
+ $this->normalizeKey($key);
+
+ $argn = func_num_args();
+ $args = array(
+ 'key' => & $key,
+ );
+ if ($argn > 1) {
+ $args['success'] = & $success;
+ }
+ if ($argn > 2) {
+ $args['casToken'] = & $casToken;
+ }
+ $args = new ArrayObject($args);
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ if ($args->offsetExists('success') && $args->offsetExists('casToken')) {
+ $result = $this->internalGetItem($args['key'], $args['success'], $args['casToken']);
+ } elseif ($args->offsetExists('success')) {
+ $result = $this->internalGetItem($args['key'], $args['success']);
+ } else {
+ $result = $this->internalGetItem($args['key']);
+ }
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ $result = false;
+ return $this->triggerException(__FUNCTION__, $args, $result, $e);
+ }
+ }
+
+ /**
+ * Internal method to get an item.
+ *
+ * @param string $normalizedKey
+ * @param bool $success
+ * @param mixed $casToken
+ * @return mixed Data on success, null on failure
+ * @throws Exception\ExceptionInterface
+ */
+ abstract protected function internalGetItem(& $normalizedKey, & $success = null, & $casToken = null);
+
+ /**
+ * Get multiple items.
+ *
+ * @param array $keys
+ * @return array Associative array of keys and values
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers getItems.pre(PreEvent)
+ * @triggers getItems.post(PostEvent)
+ * @triggers getItems.exception(ExceptionEvent)
+ */
+ public function getItems(array $keys)
+ {
+ if (!$this->getOptions()->getReadable()) {
+ return array();
+ }
+
+ $this->normalizeKeys($keys);
+ $args = new ArrayObject(array(
+ 'keys' => & $keys,
+ ));
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalGetItems($args['keys']);
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ $result = array();
+ return $this->triggerException(__FUNCTION__, $args, $result, $e);
+ }
+ }
+
+ /**
+ * Internal method to get multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Associative array of keys and values
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetItems(array & $normalizedKeys)
+ {
+ $success = null;
+ $result = array();
+ foreach ($normalizedKeys as $normalizedKey) {
+ $value = $this->internalGetItem($normalizedKey, $success);
+ if ($success) {
+ $result[$normalizedKey] = $value;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Test if an item exists.
+ *
+ * @param string $key
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers hasItem.pre(PreEvent)
+ * @triggers hasItem.post(PostEvent)
+ * @triggers hasItem.exception(ExceptionEvent)
+ */
+ public function hasItem($key)
+ {
+ if (!$this->getOptions()->getReadable()) {
+ return false;
+ }
+
+ $this->normalizeKey($key);
+ $args = new ArrayObject(array(
+ 'key' => & $key,
+ ));
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalHasItem($args['key']);
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ $result = false;
+ return $this->triggerException(__FUNCTION__, $args, $result, $e);
+ }
+ }
+
+ /**
+ * Internal method to test if an item exists.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalHasItem(& $normalizedKey)
+ {
+ $success = null;
+ $this->internalGetItem($normalizedKey, $success);
+ return $success;
+ }
+
+ /**
+ * Test multiple items.
+ *
+ * @param array $keys
+ * @return array Array of found keys
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers hasItems.pre(PreEvent)
+ * @triggers hasItems.post(PostEvent)
+ * @triggers hasItems.exception(ExceptionEvent)
+ */
+ public function hasItems(array $keys)
+ {
+ if (!$this->getOptions()->getReadable()) {
+ return array();
+ }
+
+ $this->normalizeKeys($keys);
+ $args = new ArrayObject(array(
+ 'keys' => & $keys,
+ ));
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalHasItems($args['keys']);
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ $result = array();
+ return $this->triggerException(__FUNCTION__, $args, $result, $e);
+ }
+ }
+
+ /**
+ * Internal method to test multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Array of found keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalHasItems(array & $normalizedKeys)
+ {
+ $result = array();
+ foreach ($normalizedKeys as $normalizedKey) {
+ if ($this->internalHasItem($normalizedKey)) {
+ $result[] = $normalizedKey;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Get metadata of an item.
+ *
+ * @param string $key
+ * @return array|bool Metadata on success, false on failure
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers getMetadata.pre(PreEvent)
+ * @triggers getMetadata.post(PostEvent)
+ * @triggers getMetadata.exception(ExceptionEvent)
+ */
+ public function getMetadata($key)
+ {
+ if (!$this->getOptions()->getReadable()) {
+ return false;
+ }
+
+ $this->normalizeKey($key);
+ $args = new ArrayObject(array(
+ 'key' => & $key,
+ ));
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalGetMetadata($args['key']);
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ $result = false;
+ return $this->triggerException(__FUNCTION__, $args, $result, $e);
+ }
+ }
+
+ /**
+ * Internal method to get metadata of an item.
+ *
+ * @param string $normalizedKey
+ * @return array|bool Metadata on success, false on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetMetadata(& $normalizedKey)
+ {
+ if (!$this->internalHasItem($normalizedKey)) {
+ return false;
+ }
+
+ return array();
+ }
+
+ /**
+ * Get multiple metadata
+ *
+ * @param array $keys
+ * @return array Associative array of keys and metadata
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers getMetadatas.pre(PreEvent)
+ * @triggers getMetadatas.post(PostEvent)
+ * @triggers getMetadatas.exception(ExceptionEvent)
+ */
+ public function getMetadatas(array $keys)
+ {
+ if (!$this->getOptions()->getReadable()) {
+ return array();
+ }
+
+ $this->normalizeKeys($keys);
+ $args = new ArrayObject(array(
+ 'keys' => & $keys,
+ ));
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalGetMetadatas($args['keys']);
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ $result = array();
+ return $this->triggerException(__FUNCTION__, $args, $result, $e);
+ }
+ }
+
+ /**
+ * Internal method to get multiple metadata
+ *
+ * @param array $normalizedKeys
+ * @return array Associative array of keys and metadata
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetMetadatas(array & $normalizedKeys)
+ {
+ $result = array();
+ foreach ($normalizedKeys as $normalizedKey) {
+ $metadata = $this->internalGetMetadata($normalizedKey);
+ if ($metadata !== false) {
+ $result[$normalizedKey] = $metadata;
+ }
+ }
+ return $result;
+ }
+
+ /* writing */
+
+ /**
+ * Store an item.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers setItem.pre(PreEvent)
+ * @triggers setItem.post(PostEvent)
+ * @triggers setItem.exception(ExceptionEvent)
+ */
+ public function setItem($key, $value)
+ {
+ if (!$this->getOptions()->getWritable()) {
+ return false;
+ }
+
+ $this->normalizeKey($key);
+ $args = new ArrayObject(array(
+ 'key' => & $key,
+ 'value' => & $value,
+ ));
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalSetItem($args['key'], $args['value']);
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ $result = false;
+ return $this->triggerException(__FUNCTION__, $args, $result, $e);
+ }
+ }
+
+ /**
+ * Internal method to store an item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ abstract protected function internalSetItem(& $normalizedKey, & $value);
+
+ /**
+ * Store multiple items.
+ *
+ * @param array $keyValuePairs
+ * @return array Array of not stored keys
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers setItems.pre(PreEvent)
+ * @triggers setItems.post(PostEvent)
+ * @triggers setItems.exception(ExceptionEvent)
+ */
+ public function setItems(array $keyValuePairs)
+ {
+ if (!$this->getOptions()->getWritable()) {
+ return array_keys($keyValuePairs);
+ }
+
+ $this->normalizeKeyValuePairs($keyValuePairs);
+ $args = new ArrayObject(array(
+ 'keyValuePairs' => & $keyValuePairs,
+ ));
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalSetItems($args['keyValuePairs']);
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ $result = array_keys($keyValuePairs);
+ return $this->triggerException(__FUNCTION__, $args, $result, $e);
+ }
+ }
+
+ /**
+ * Internal method to store multiple items.
+ *
+ * @param array $normalizedKeyValuePairs
+ * @return array Array of not stored keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalSetItems(array & $normalizedKeyValuePairs)
+ {
+ $failedKeys = array();
+ foreach ($normalizedKeyValuePairs as $normalizedKey => $value) {
+ if (!$this->internalSetItem($normalizedKey, $value)) {
+ $failedKeys[] = $normalizedKey;
+ }
+ }
+ return $failedKeys;
+ }
+
+ /**
+ * Add an item.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers addItem.pre(PreEvent)
+ * @triggers addItem.post(PostEvent)
+ * @triggers addItem.exception(ExceptionEvent)
+ */
+ public function addItem($key, $value)
+ {
+ if (!$this->getOptions()->getWritable()) {
+ return false;
+ }
+
+ $this->normalizeKey($key);
+ $args = new ArrayObject(array(
+ 'key' => & $key,
+ 'value' => & $value,
+ ));
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalAddItem($args['key'], $args['value']);
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ $result = false;
+ return $this->triggerException(__FUNCTION__, $args, $result, $e);
+ }
+ }
+
+ /**
+ * Internal method to add an item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalAddItem(& $normalizedKey, & $value)
+ {
+ if ($this->internalHasItem($normalizedKey)) {
+ return false;
+ }
+ return $this->internalSetItem($normalizedKey, $value);
+ }
+
+ /**
+ * Add multiple items.
+ *
+ * @param array $keyValuePairs
+ * @return array Array of not stored keys
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers addItems.pre(PreEvent)
+ * @triggers addItems.post(PostEvent)
+ * @triggers addItems.exception(ExceptionEvent)
+ */
+ public function addItems(array $keyValuePairs)
+ {
+ if (!$this->getOptions()->getWritable()) {
+ return array_keys($keyValuePairs);
+ }
+
+ $this->normalizeKeyValuePairs($keyValuePairs);
+ $args = new ArrayObject(array(
+ 'keyValuePairs' => & $keyValuePairs,
+ ));
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalAddItems($args['keyValuePairs']);
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ $result = array_keys($keyValuePairs);
+ return $this->triggerException(__FUNCTION__, $args, $result, $e);
+ }
+ }
+
+ /**
+ * Internal method to add multiple items.
+ *
+ * @param array $normalizedKeyValuePairs
+ * @return array Array of not stored keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalAddItems(array & $normalizedKeyValuePairs)
+ {
+ $result = array();
+ foreach ($normalizedKeyValuePairs as $normalizedKey => $value) {
+ if (!$this->internalAddItem($normalizedKey, $value)) {
+ $result[] = $normalizedKey;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Replace an existing item.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers replaceItem.pre(PreEvent)
+ * @triggers replaceItem.post(PostEvent)
+ * @triggers replaceItem.exception(ExceptionEvent)
+ */
+ public function replaceItem($key, $value)
+ {
+ if (!$this->getOptions()->getWritable()) {
+ return false;
+ }
+
+ $this->normalizeKey($key);
+ $args = new ArrayObject(array(
+ 'key' => & $key,
+ 'value' => & $value,
+ ));
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalReplaceItem($args['key'], $args['value']);
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ $result = false;
+ return $this->triggerException(__FUNCTION__, $args, $result, $e);
+ }
+ }
+
+ /**
+ * Internal method to replace an existing item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalReplaceItem(& $normalizedKey, & $value)
+ {
+ if (!$this->internalhasItem($normalizedKey)) {
+ return false;
+ }
+
+ return $this->internalSetItem($normalizedKey, $value);
+ }
+
+ /**
+ * Replace multiple existing items.
+ *
+ * @param array $keyValuePairs
+ * @return array Array of not stored keys
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers replaceItems.pre(PreEvent)
+ * @triggers replaceItems.post(PostEvent)
+ * @triggers replaceItems.exception(ExceptionEvent)
+ */
+ public function replaceItems(array $keyValuePairs)
+ {
+ if (!$this->getOptions()->getWritable()) {
+ return array_keys($keyValuePairs);
+ }
+
+ $this->normalizeKeyValuePairs($keyValuePairs);
+ $args = new ArrayObject(array(
+ 'keyValuePairs' => & $keyValuePairs,
+ ));
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalReplaceItems($args['keyValuePairs']);
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ $result = array_keys($keyValuePairs);
+ return $this->triggerException(__FUNCTION__, $args, $result, $e);
+ }
+ }
+
+ /**
+ * Internal method to replace multiple existing items.
+ *
+ * @param array $normalizedKeyValuePairs
+ * @return array Array of not stored keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalReplaceItems(array & $normalizedKeyValuePairs)
+ {
+ $result = array();
+ foreach ($normalizedKeyValuePairs as $normalizedKey => $value) {
+ if (!$this->internalReplaceItem($normalizedKey, $value)) {
+ $result[] = $normalizedKey;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Set an item only if token matches
+ *
+ * It uses the token received from getItem() to check if the item has
+ * changed before overwriting it.
+ *
+ * @param mixed $token
+ * @param string $key
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ * @see getItem()
+ * @see setItem()
+ */
+ public function checkAndSetItem($token, $key, $value)
+ {
+ if (!$this->getOptions()->getWritable()) {
+ return false;
+ }
+
+ $this->normalizeKey($key);
+ $args = new ArrayObject(array(
+ 'token' => & $token,
+ 'key' => & $key,
+ 'value' => & $value,
+ ));
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalCheckAndSetItem($args['token'], $args['key'], $args['value']);
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ $result = false;
+ return $this->triggerException(__FUNCTION__, $args, $result, $e);
+ }
+ }
+
+ /**
+ * Internal method to set an item only if token matches
+ *
+ * @param mixed $token
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ * @see getItem()
+ * @see setItem()
+ */
+ protected function internalCheckAndSetItem(& $token, & $normalizedKey, & $value)
+ {
+ $oldValue = $this->internalGetItem($normalizedKey);
+ if ($oldValue !== $token) {
+ return false;
+ }
+
+ return $this->internalSetItem($normalizedKey, $value);
+ }
+
+ /**
+ * Reset lifetime of an item
+ *
+ * @param string $key
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers touchItem.pre(PreEvent)
+ * @triggers touchItem.post(PostEvent)
+ * @triggers touchItem.exception(ExceptionEvent)
+ */
+ public function touchItem($key)
+ {
+ if (!$this->getOptions()->getWritable()) {
+ return false;
+ }
+
+ $this->normalizeKey($key);
+ $args = new ArrayObject(array(
+ 'key' => & $key,
+ ));
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalTouchItem($args['key']);
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ $result = false;
+ return $this->triggerException(__FUNCTION__, $args, $result, $e);
+ }
+ }
+
+ /**
+ * Internal method to reset lifetime of an item
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalTouchItem(& $normalizedKey)
+ {
+ $success = null;
+ $value = $this->internalGetItem($normalizedKey, $success);
+ if (!$success) {
+ return false;
+ }
+
+ return $this->internalReplaceItem($normalizedKey, $value);
+ }
+
+ /**
+ * Reset lifetime of multiple items.
+ *
+ * @param array $keys
+ * @return array Array of not updated keys
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers touchItems.pre(PreEvent)
+ * @triggers touchItems.post(PostEvent)
+ * @triggers touchItems.exception(ExceptionEvent)
+ */
+ public function touchItems(array $keys)
+ {
+ if (!$this->getOptions()->getWritable()) {
+ return $keys;
+ }
+
+ $this->normalizeKeys($keys);
+ $args = new ArrayObject(array(
+ 'keys' => & $keys,
+ ));
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalTouchItems($args['keys']);
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ return $this->triggerException(__FUNCTION__, $args, $keys, $e);
+ }
+ }
+
+ /**
+ * Internal method to reset lifetime of multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Array of not updated keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalTouchItems(array & $normalizedKeys)
+ {
+ $result = array();
+ foreach ($normalizedKeys as $normalizedKey) {
+ if (!$this->internalTouchItem($normalizedKey)) {
+ $result[] = $normalizedKey;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Remove an item.
+ *
+ * @param string $key
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers removeItem.pre(PreEvent)
+ * @triggers removeItem.post(PostEvent)
+ * @triggers removeItem.exception(ExceptionEvent)
+ */
+ public function removeItem($key)
+ {
+ if (!$this->getOptions()->getWritable()) {
+ return false;
+ }
+
+ $this->normalizeKey($key);
+ $args = new ArrayObject(array(
+ 'key' => & $key,
+ ));
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalRemoveItem($args['key']);
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ $result = false;
+ return $this->triggerException(__FUNCTION__, $args, $result, $e);
+ }
+ }
+
+ /**
+ * Internal method to remove an item.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ abstract protected function internalRemoveItem(& $normalizedKey);
+
+ /**
+ * Remove multiple items.
+ *
+ * @param array $keys
+ * @return array Array of not removed keys
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers removeItems.pre(PreEvent)
+ * @triggers removeItems.post(PostEvent)
+ * @triggers removeItems.exception(ExceptionEvent)
+ */
+ public function removeItems(array $keys)
+ {
+ if (!$this->getOptions()->getWritable()) {
+ return $keys;
+ }
+
+ $this->normalizeKeys($keys);
+ $args = new ArrayObject(array(
+ 'keys' => & $keys,
+ ));
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalRemoveItems($args['keys']);
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ return $this->triggerException(__FUNCTION__, $args, $keys, $e);
+ }
+ }
+
+ /**
+ * Internal method to remove multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Array of not removed keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalRemoveItems(array & $normalizedKeys)
+ {
+ $result = array();
+ foreach ($normalizedKeys as $normalizedKey) {
+ if (!$this->internalRemoveItem($normalizedKey)) {
+ $result[] = $normalizedKey;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Increment an item.
+ *
+ * @param string $key
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers incrementItem.pre(PreEvent)
+ * @triggers incrementItem.post(PostEvent)
+ * @triggers incrementItem.exception(ExceptionEvent)
+ */
+ public function incrementItem($key, $value)
+ {
+ if (!$this->getOptions()->getWritable()) {
+ return false;
+ }
+
+ $this->normalizeKey($key);
+ $args = new ArrayObject(array(
+ 'key' => & $key,
+ 'value' => & $value,
+ ));
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalIncrementItem($args['key'], $args['value']);
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ $result = false;
+ return $this->triggerException(__FUNCTION__, $args, $result, $e);
+ }
+ }
+
+ /**
+ * Internal method to increment an item.
+ *
+ * @param string $normalizedKey
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalIncrementItem(& $normalizedKey, & $value)
+ {
+ $success = null;
+ $value = (int) $value;
+ $get = (int) $this->internalGetItem($normalizedKey, $success);
+ $newValue = $get + $value;
+
+ if ($success) {
+ $this->internalReplaceItem($normalizedKey, $newValue);
+ } else {
+ $this->internalAddItem($normalizedKey, $newValue);
+ }
+
+ return $newValue;
+ }
+
+ /**
+ * Increment multiple items.
+ *
+ * @param array $keyValuePairs
+ * @return array Associative array of keys and new values
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers incrementItems.pre(PreEvent)
+ * @triggers incrementItems.post(PostEvent)
+ * @triggers incrementItems.exception(ExceptionEvent)
+ */
+ public function incrementItems(array $keyValuePairs)
+ {
+ if (!$this->getOptions()->getWritable()) {
+ return array();
+ }
+
+ $this->normalizeKeyValuePairs($keyValuePairs);
+ $args = new ArrayObject(array(
+ 'keyValuePairs' => & $keyValuePairs,
+ ));
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalIncrementItems($args['keyValuePairs']);
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ $result = array();
+ return $this->triggerException(__FUNCTION__, $args, $result, $e);
+ }
+ }
+
+ /**
+ * Internal method to increment multiple items.
+ *
+ * @param array $normalizedKeyValuePairs
+ * @return array Associative array of keys and new values
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalIncrementItems(array & $normalizedKeyValuePairs)
+ {
+ $result = array();
+ foreach ($normalizedKeyValuePairs as $normalizedKey => $value) {
+ $newValue = $this->internalIncrementItem($normalizedKey, $value);
+ if ($newValue !== false) {
+ $result[$normalizedKey] = $newValue;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Decrement an item.
+ *
+ * @param string $key
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers decrementItem.pre(PreEvent)
+ * @triggers decrementItem.post(PostEvent)
+ * @triggers decrementItem.exception(ExceptionEvent)
+ */
+ public function decrementItem($key, $value)
+ {
+ if (!$this->getOptions()->getWritable()) {
+ return false;
+ }
+
+ $this->normalizeKey($key);
+ $args = new ArrayObject(array(
+ 'key' => & $key,
+ 'value' => & $value,
+ ));
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalDecrementItem($args['key'], $args['value']);
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ $result = false;
+ return $this->triggerException(__FUNCTION__, $args, $result, $e);
+ }
+ }
+
+ /**
+ * Internal method to decrement an item.
+ *
+ * @param string $normalizedKey
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalDecrementItem(& $normalizedKey, & $value)
+ {
+ $success = null;
+ $value = (int) $value;
+ $get = (int) $this->internalGetItem($normalizedKey, $success);
+ $newValue = $get - $value;
+
+ if ($success) {
+ $this->internalReplaceItem($normalizedKey, $newValue);
+ } else {
+ $this->internalAddItem($normalizedKey, $newValue);
+ }
+
+ return $newValue;
+ }
+
+ /**
+ * Decrement multiple items.
+ *
+ * @param array $keyValuePairs
+ * @return array Associative array of keys and new values
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers incrementItems.pre(PreEvent)
+ * @triggers incrementItems.post(PostEvent)
+ * @triggers incrementItems.exception(ExceptionEvent)
+ */
+ public function decrementItems(array $keyValuePairs)
+ {
+ if (!$this->getOptions()->getWritable()) {
+ return array();
+ }
+
+ $this->normalizeKeyValuePairs($keyValuePairs);
+ $args = new ArrayObject(array(
+ 'keyValuePairs' => & $keyValuePairs,
+ ));
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalDecrementItems($args['keyValuePairs']);
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ $result = array();
+ return $this->triggerException(__FUNCTION__, $args, $result, $e);
+ }
+ }
+
+ /**
+ * Internal method to decrement multiple items.
+ *
+ * @param array $normalizedKeyValuePairs
+ * @return array Associative array of keys and new values
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalDecrementItems(array & $normalizedKeyValuePairs)
+ {
+ $result = array();
+ foreach ($normalizedKeyValuePairs as $normalizedKey => $value) {
+ $newValue = $this->decrementItem($normalizedKey, $value);
+ if ($newValue !== false) {
+ $result[$normalizedKey] = $newValue;
+ }
+ }
+ return $result;
+ }
+
+ /* status */
+
+ /**
+ * Get capabilities of this adapter
+ *
+ * @return Capabilities
+ * @triggers getCapabilities.pre(PreEvent)
+ * @triggers getCapabilities.post(PostEvent)
+ * @triggers getCapabilities.exception(ExceptionEvent)
+ */
+ public function getCapabilities()
+ {
+ $args = new ArrayObject();
+
+ try {
+ $eventRs = $this->triggerPre(__FUNCTION__, $args);
+ if ($eventRs->stopped()) {
+ return $eventRs->last();
+ }
+
+ $result = $this->internalGetCapabilities();
+ return $this->triggerPost(__FUNCTION__, $args, $result);
+ } catch (\Exception $e) {
+ $result = false;
+ return $this->triggerException(__FUNCTION__, $args, $result, $e);
+ }
+ }
+
+ /**
+ * Internal method to get capabilities of this adapter
+ *
+ * @return Capabilities
+ */
+ protected function internalGetCapabilities()
+ {
+ if ($this->capabilities === null) {
+ $this->capabilityMarker = new stdClass();
+ $this->capabilities = new Capabilities($this, $this->capabilityMarker);
+ }
+ return $this->capabilities;
+ }
+
+ /* internal */
+
+ /**
+ * Validates and normalizes a key
+ *
+ * @param string $key
+ * @return void
+ * @throws Exception\InvalidArgumentException On an invalid key
+ */
+ protected function normalizeKey(& $key)
+ {
+ $key = (string) $key;
+
+ if ($key === '') {
+ throw new Exception\InvalidArgumentException(
+ "An empty key isn't allowed"
+ );
+ } elseif (($p = $this->getOptions()->getKeyPattern()) && !preg_match($p, $key)) {
+ throw new Exception\InvalidArgumentException(
+ "The key '{$key}' doesn't match against pattern '{$p}'"
+ );
+ }
+ }
+
+ /**
+ * Validates and normalizes multiple keys
+ *
+ * @param array $keys
+ * @return void
+ * @throws Exception\InvalidArgumentException On an invalid key
+ */
+ protected function normalizeKeys(array & $keys)
+ {
+ if (!$keys) {
+ throw new Exception\InvalidArgumentException(
+ "An empty list of keys isn't allowed"
+ );
+ }
+
+ array_walk($keys, array($this, 'normalizeKey'));
+ $keys = array_values(array_unique($keys));
+ }
+
+ /**
+ * Validates and normalizes an array of key-value pairs
+ *
+ * @param array $keyValuePairs
+ * @return void
+ * @throws Exception\InvalidArgumentException On an invalid key
+ */
+ protected function normalizeKeyValuePairs(array & $keyValuePairs)
+ {
+ $normalizedKeyValuePairs = array();
+ foreach ($keyValuePairs as $key => $value) {
+ $this->normalizeKey($key);
+ $normalizedKeyValuePairs[$key] = $value;
+ }
+ $keyValuePairs = $normalizedKeyValuePairs;
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/AbstractZendServer.php b/library/Zend/Cache/Storage/Adapter/AbstractZendServer.php
new file mode 100755
index 0000000000..22933beb0c
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/AbstractZendServer.php
@@ -0,0 +1,273 @@
+getOptions()->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . self::NAMESPACE_SEPARATOR;
+
+ $result = $this->zdcFetch($prefix . $normalizedKey);
+ if ($result === null) {
+ $success = false;
+ } else {
+ $success = true;
+ $casToken = $result;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to get multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Associative array of keys and values
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetItems(array & $normalizedKeys)
+ {
+ $namespace = $this->getOptions()->getNamespace();
+ if ($namespace === '') {
+ return $this->zdcFetchMulti($normalizedKeys);
+ }
+
+ $prefix = $namespace . self::NAMESPACE_SEPARATOR;
+ $internalKeys = array();
+ foreach ($normalizedKeys as $normalizedKey) {
+ $internalKeys[] = $prefix . $normalizedKey;
+ }
+
+ $fetch = $this->zdcFetchMulti($internalKeys);
+ $result = array();
+ $prefixL = strlen($prefix);
+ foreach ($fetch as $k => & $v) {
+ $result[substr($k, $prefixL)] = $v;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to test if an item exists.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalHasItem(& $normalizedKey)
+ {
+ $namespace = $this->getOptions()->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . self::NAMESPACE_SEPARATOR;
+ return ($this->zdcFetch($prefix . $normalizedKey) !== false);
+ }
+
+ /**
+ * Internal method to test multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Array of found keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalHasItems(array & $normalizedKeys)
+ {
+ $namespace = $this->getOptions()->getNamespace();
+ if ($namespace === '') {
+ return array_keys($this->zdcFetchMulti($normalizedKeys));
+ }
+
+ $prefix = $namespace . self::NAMESPACE_SEPARATOR;
+ $internalKeys = array();
+ foreach ($normalizedKeys as $normalizedKey) {
+ $internalKeys[] = $prefix . $normalizedKey;
+ }
+
+ $fetch = $this->zdcFetchMulti($internalKeys);
+ $result = array();
+ $prefixL = strlen($prefix);
+ foreach ($fetch as $internalKey => & $value) {
+ $result[] = substr($internalKey, $prefixL);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get metadata for multiple items
+ *
+ * @param array $normalizedKeys
+ * @return array Associative array of keys and metadata
+ *
+ * @triggers getMetadatas.pre(PreEvent)
+ * @triggers getMetadatas.post(PostEvent)
+ * @triggers getMetadatas.exception(ExceptionEvent)
+ */
+ protected function internalGetMetadatas(array & $normalizedKeys)
+ {
+ $namespace = $this->getOptions()->getNamespace();
+ if ($namespace === '') {
+ $result = $this->zdcFetchMulti($normalizedKeys);
+ return array_fill_keys(array_keys($result), array());
+ }
+
+ $prefix = $namespace . self::NAMESPACE_SEPARATOR;
+ $internalKeys = array();
+ foreach ($normalizedKeys as $normalizedKey) {
+ $internalKeys[] = $prefix . $normalizedKey;
+ }
+
+ $fetch = $this->zdcFetchMulti($internalKeys);
+ $result = array();
+ $prefixL = strlen($prefix);
+ foreach ($fetch as $internalKey => $value) {
+ $result[substr($internalKey, $prefixL)] = array();
+ }
+
+ return $result;
+ }
+
+ /* writing */
+
+ /**
+ * Internal method to store an item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalSetItem(& $normalizedKey, & $value)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . self::NAMESPACE_SEPARATOR;
+ $this->zdcStore($prefix . $normalizedKey, $value, $options->getTtl());
+ return true;
+ }
+
+ /**
+ * Internal method to remove an item.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalRemoveItem(& $normalizedKey)
+ {
+ $namespace = $this->getOptions()->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . self::NAMESPACE_SEPARATOR;
+ return $this->zdcDelete($prefix . $normalizedKey);
+ }
+
+ /* status */
+
+ /**
+ * Internal method to get capabilities of this adapter
+ *
+ * @return Capabilities
+ */
+ protected function internalGetCapabilities()
+ {
+ if ($this->capabilities === null) {
+ $this->capabilityMarker = new stdClass();
+ $this->capabilities = new Capabilities(
+ $this,
+ $this->capabilityMarker,
+ array(
+ 'supportedDatatypes' => array(
+ 'NULL' => true,
+ 'boolean' => true,
+ 'integer' => true,
+ 'double' => true,
+ 'string' => true,
+ 'array' => true,
+ 'object' => 'object',
+ 'resource' => false,
+ ),
+ 'supportedMetadata' => array(),
+ 'maxTtl' => 0,
+ 'staticTtl' => true,
+ 'ttlPrecision' => 1,
+ 'useRequestTime' => false,
+ 'expiredRead' => false,
+ 'maxKeyLength' => 0,
+ 'namespaceIsPrefix' => true,
+ 'namespaceSeparator' => self::NAMESPACE_SEPARATOR,
+ )
+ );
+ }
+
+ return $this->capabilities;
+ }
+
+ /* internal wrapper of zend_[disk|shm]_cache_* functions */
+
+ /**
+ * Store data into Zend Data Cache (zdc)
+ *
+ * @param string $internalKey
+ * @param mixed $value
+ * @param int $ttl
+ * @return void
+ * @throws Exception\RuntimeException
+ */
+ abstract protected function zdcStore($internalKey, $value, $ttl);
+
+ /**
+ * Fetch a single item from Zend Data Cache (zdc)
+ *
+ * @param string $internalKey
+ * @return mixed The stored value or FALSE if item wasn't found
+ * @throws Exception\RuntimeException
+ */
+ abstract protected function zdcFetch($internalKey);
+
+ /**
+ * Fetch multiple items from Zend Data Cache (zdc)
+ *
+ * @param array $internalKeys
+ * @return array All found items
+ * @throws Exception\RuntimeException
+ */
+ abstract protected function zdcFetchMulti(array $internalKeys);
+
+ /**
+ * Delete data from Zend Data Cache (zdc)
+ *
+ * @param string $internalKey
+ * @return bool
+ * @throws Exception\RuntimeException
+ */
+ abstract protected function zdcDelete($internalKey);
+}
diff --git a/library/Zend/Cache/Storage/Adapter/AdapterOptions.php b/library/Zend/Cache/Storage/Adapter/AdapterOptions.php
new file mode 100755
index 0000000000..b3669ffa4a
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/AdapterOptions.php
@@ -0,0 +1,264 @@
+adapter = $adapter;
+ return $this;
+ }
+
+ /**
+ * Set key pattern
+ *
+ * @param null|string $keyPattern
+ * @throws Exception\InvalidArgumentException
+ * @return AdapterOptions
+ */
+ public function setKeyPattern($keyPattern)
+ {
+ $keyPattern = (string) $keyPattern;
+ if ($this->keyPattern !== $keyPattern) {
+ // validate pattern
+ if ($keyPattern !== '') {
+ ErrorHandler::start(E_WARNING);
+ $result = preg_match($keyPattern, '');
+ $error = ErrorHandler::stop();
+ if ($result === false) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Invalid pattern "%s"%s',
+ $keyPattern,
+ ($error ? ': ' . $error->getMessage() : '')
+ ), 0, $error);
+ }
+ }
+
+ $this->triggerOptionEvent('key_pattern', $keyPattern);
+ $this->keyPattern = $keyPattern;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get key pattern
+ *
+ * @return string
+ */
+ public function getKeyPattern()
+ {
+ return $this->keyPattern;
+ }
+
+ /**
+ * Set namespace.
+ *
+ * @param string $namespace
+ * @return AdapterOptions
+ */
+ public function setNamespace($namespace)
+ {
+ $namespace = (string) $namespace;
+ if ($this->namespace !== $namespace) {
+ $this->triggerOptionEvent('namespace', $namespace);
+ $this->namespace = $namespace;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get namespace
+ *
+ * @return string
+ */
+ public function getNamespace()
+ {
+ return $this->namespace;
+ }
+
+ /**
+ * Enable/Disable reading data from cache.
+ *
+ * @param bool $readable
+ * @return AbstractAdapter
+ */
+ public function setReadable($readable)
+ {
+ $readable = (bool) $readable;
+ if ($this->readable !== $readable) {
+ $this->triggerOptionEvent('readable', $readable);
+ $this->readable = $readable;
+ }
+ return $this;
+ }
+
+ /**
+ * If reading data from cache enabled.
+ *
+ * @return bool
+ */
+ public function getReadable()
+ {
+ return $this->readable;
+ }
+
+ /**
+ * Set time to live.
+ *
+ * @param int|float $ttl
+ * @return AdapterOptions
+ */
+ public function setTtl($ttl)
+ {
+ $this->normalizeTtl($ttl);
+ if ($this->ttl !== $ttl) {
+ $this->triggerOptionEvent('ttl', $ttl);
+ $this->ttl = $ttl;
+ }
+ return $this;
+ }
+
+ /**
+ * Get time to live.
+ *
+ * @return float
+ */
+ public function getTtl()
+ {
+ return $this->ttl;
+ }
+
+ /**
+ * Enable/Disable writing data to cache.
+ *
+ * @param bool $writable
+ * @return AdapterOptions
+ */
+ public function setWritable($writable)
+ {
+ $writable = (bool) $writable;
+ if ($this->writable !== $writable) {
+ $this->triggerOptionEvent('writable', $writable);
+ $this->writable = $writable;
+ }
+ return $this;
+ }
+
+ /**
+ * If writing data to cache enabled.
+ *
+ * @return bool
+ */
+ public function getWritable()
+ {
+ return $this->writable;
+ }
+
+ /**
+ * Triggers an option event if this options instance has a connection to
+ * an adapter implements EventsCapableInterface.
+ *
+ * @param string $optionName
+ * @param mixed $optionValue
+ * @return void
+ */
+ protected function triggerOptionEvent($optionName, $optionValue)
+ {
+ if ($this->adapter instanceof EventsCapableInterface) {
+ $event = new Event('option', $this->adapter, new ArrayObject(array($optionName => $optionValue)));
+ $this->adapter->getEventManager()->trigger($event);
+ }
+ }
+
+ /**
+ * Validates and normalize a TTL.
+ *
+ * @param int|float $ttl
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ protected function normalizeTtl(&$ttl)
+ {
+ if (!is_int($ttl)) {
+ $ttl = (float) $ttl;
+
+ // convert to int if possible
+ if ($ttl === (float) (int) $ttl) {
+ $ttl = (int) $ttl;
+ }
+ }
+
+ if ($ttl < 0) {
+ throw new Exception\InvalidArgumentException("TTL can't be negative");
+ }
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/Apc.php b/library/Zend/Cache/Storage/Adapter/Apc.php
new file mode 100755
index 0000000000..c2336e96ac
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/Apc.php
@@ -0,0 +1,751 @@
+ 0) {
+ throw new Exception\ExtensionNotLoadedException("Missing ext/apc >= 3.1.6");
+ }
+
+ $enabled = ini_get('apc.enabled');
+ if (PHP_SAPI == 'cli') {
+ $enabled = $enabled && (bool) ini_get('apc.enable_cli');
+ }
+
+ if (!$enabled) {
+ throw new Exception\ExtensionNotLoadedException(
+ "ext/apc is disabled - see 'apc.enabled' and 'apc.enable_cli'"
+ );
+ }
+
+ parent::__construct($options);
+ }
+
+ /* options */
+
+ /**
+ * Set options.
+ *
+ * @param array|Traversable|ApcOptions $options
+ * @return Apc
+ * @see getOptions()
+ */
+ public function setOptions($options)
+ {
+ if (!$options instanceof ApcOptions) {
+ $options = new ApcOptions($options);
+ }
+
+ return parent::setOptions($options);
+ }
+
+ /**
+ * Get options.
+ *
+ * @return ApcOptions
+ * @see setOptions()
+ */
+ public function getOptions()
+ {
+ if (!$this->options) {
+ $this->setOptions(new ApcOptions());
+ }
+ return $this->options;
+ }
+
+ /* TotalSpaceCapableInterface */
+
+ /**
+ * Get total space in bytes
+ *
+ * @return int|float
+ */
+ public function getTotalSpace()
+ {
+ if ($this->totalSpace === null) {
+ $smaInfo = apc_sma_info(true);
+ $this->totalSpace = $smaInfo['num_seg'] * $smaInfo['seg_size'];
+ }
+
+ return $this->totalSpace;
+ }
+
+ /* AvailableSpaceCapableInterface */
+
+ /**
+ * Get available space in bytes
+ *
+ * @return int|float
+ */
+ public function getAvailableSpace()
+ {
+ $smaInfo = apc_sma_info(true);
+ return $smaInfo['avail_mem'];
+ }
+
+ /* IterableInterface */
+
+ /**
+ * Get the storage iterator
+ *
+ * @return ApcIterator
+ */
+ public function getIterator()
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = '';
+ $pattern = null;
+ if ($namespace !== '') {
+ $prefix = $namespace . $options->getNamespaceSeparator();
+ $pattern = '/^' . preg_quote($prefix, '/') . '/';
+ }
+
+ $baseIt = new BaseApcIterator('user', $pattern, 0, 1, APC_LIST_ACTIVE);
+ return new ApcIterator($this, $baseIt, $prefix);
+ }
+
+ /* FlushableInterface */
+
+ /**
+ * Flush the whole storage
+ *
+ * @return bool
+ */
+ public function flush()
+ {
+ return apc_clear_cache('user');
+ }
+
+ /* ClearByNamespaceInterface */
+
+ /**
+ * Remove items by given namespace
+ *
+ * @param string $namespace
+ * @return bool
+ */
+ public function clearByNamespace($namespace)
+ {
+ $namespace = (string) $namespace;
+ if ($namespace === '') {
+ throw new Exception\InvalidArgumentException('No namespace given');
+ }
+
+ $options = $this->getOptions();
+ $prefix = $namespace . $options->getNamespaceSeparator();
+ $pattern = '/^' . preg_quote($prefix, '/') . '/';
+ return apc_delete(new BaseApcIterator('user', $pattern, 0, 1, APC_LIST_ACTIVE));
+ }
+
+ /* ClearByPrefixInterface */
+
+ /**
+ * Remove items matching given prefix
+ *
+ * @param string $prefix
+ * @return bool
+ */
+ public function clearByPrefix($prefix)
+ {
+ $prefix = (string) $prefix;
+ if ($prefix === '') {
+ throw new Exception\InvalidArgumentException('No prefix given');
+ }
+
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $nsPrefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $pattern = '/^' . preg_quote($nsPrefix . $prefix, '/') . '/';
+ return apc_delete(new BaseApcIterator('user', $pattern, 0, 1, APC_LIST_ACTIVE));
+ }
+
+ /* reading */
+
+ /**
+ * Internal method to get an item.
+ *
+ * @param string $normalizedKey
+ * @param bool $success
+ * @param mixed $casToken
+ * @return mixed Data on success, null on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetItem(& $normalizedKey, & $success = null, & $casToken = null)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+ $result = apc_fetch($internalKey, $success);
+
+ if (!$success) {
+ return null;
+ }
+
+ $casToken = $result;
+ return $result;
+ }
+
+ /**
+ * Internal method to get multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Associative array of keys and values
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetItems(array & $normalizedKeys)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ if ($namespace === '') {
+ return apc_fetch($normalizedKeys);
+ }
+
+ $prefix = $namespace . $options->getNamespaceSeparator();
+ $internalKeys = array();
+ foreach ($normalizedKeys as $normalizedKey) {
+ $internalKeys[] = $prefix . $normalizedKey;
+ }
+
+ $fetch = apc_fetch($internalKeys);
+
+ // remove namespace prefix
+ $prefixL = strlen($prefix);
+ $result = array();
+ foreach ($fetch as $internalKey => & $value) {
+ $result[substr($internalKey, $prefixL)] = $value;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to test if an item exists.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalHasItem(& $normalizedKey)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ return apc_exists($prefix . $normalizedKey);
+ }
+
+ /**
+ * Internal method to test multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Array of found keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalHasItems(array & $normalizedKeys)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ if ($namespace === '') {
+ // array_filter with no callback will remove entries equal to FALSE
+ return array_keys(array_filter(apc_exists($normalizedKeys)));
+ }
+
+ $prefix = $namespace . $options->getNamespaceSeparator();
+ $internalKeys = array();
+ foreach ($normalizedKeys as $normalizedKey) {
+ $internalKeys[] = $prefix . $normalizedKey;
+ }
+
+ $exists = apc_exists($internalKeys);
+ $result = array();
+ $prefixL = strlen($prefix);
+ foreach ($exists as $internalKey => $bool) {
+ if ($bool === true) {
+ $result[] = substr($internalKey, $prefixL);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get metadata of an item.
+ *
+ * @param string $normalizedKey
+ * @return array|bool Metadata on success, false on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetMetadata(& $normalizedKey)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+
+ // @see http://pecl.php.net/bugs/bug.php?id=22564
+ if (!apc_exists($internalKey)) {
+ $metadata = false;
+ } else {
+ $format = APC_ITER_ALL ^ APC_ITER_VALUE ^ APC_ITER_TYPE ^ APC_ITER_REFCOUNT;
+ $regexp = '/^' . preg_quote($internalKey, '/') . '$/';
+ $it = new BaseApcIterator('user', $regexp, $format, 100, APC_LIST_ACTIVE);
+ $metadata = $it->current();
+ }
+
+ if (!$metadata) {
+ return false;
+ }
+
+ $this->normalizeMetadata($metadata);
+ return $metadata;
+ }
+
+ /**
+ * Get metadata of multiple items
+ *
+ * @param array $normalizedKeys
+ * @return array Associative array of keys and metadata
+ *
+ * @triggers getMetadatas.pre(PreEvent)
+ * @triggers getMetadatas.post(PostEvent)
+ * @triggers getMetadatas.exception(ExceptionEvent)
+ */
+ protected function internalGetMetadatas(array & $normalizedKeys)
+ {
+ $keysRegExp = array();
+ foreach ($normalizedKeys as $normalizedKey) {
+ $keysRegExp[] = preg_quote($normalizedKey, '/');
+ }
+
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ if ($namespace === '') {
+ $pattern = '/^(' . implode('|', $keysRegExp) . ')' . '$/';
+ } else {
+ $prefix = $namespace . $options->getNamespaceSeparator();
+ $pattern = '/^' . preg_quote($prefix, '/') . '(' . implode('|', $keysRegExp) . ')' . '$/';
+ }
+ $format = APC_ITER_ALL ^ APC_ITER_VALUE ^ APC_ITER_TYPE ^ APC_ITER_REFCOUNT;
+ $it = new BaseApcIterator('user', $pattern, $format, 100, APC_LIST_ACTIVE);
+ $result = array();
+ $prefixL = strlen($prefix);
+ foreach ($it as $internalKey => $metadata) {
+ // @see http://pecl.php.net/bugs/bug.php?id=22564
+ if (!apc_exists($internalKey)) {
+ continue;
+ }
+
+ $this->normalizeMetadata($metadata);
+ $result[substr($internalKey, $prefixL)] = & $metadata;
+ }
+
+ return $result;
+ }
+
+ /* writing */
+
+ /**
+ * Internal method to store an item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalSetItem(& $normalizedKey, & $value)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+ $ttl = $options->getTtl();
+
+ if (!apc_store($internalKey, $value, $ttl)) {
+ $type = is_object($value) ? get_class($value) : gettype($value);
+ throw new Exception\RuntimeException(
+ "apc_store('{$internalKey}', <{$type}>, {$ttl}) failed"
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Internal method to store multiple items.
+ *
+ * @param array $normalizedKeyValuePairs
+ * @return array Array of not stored keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalSetItems(array & $normalizedKeyValuePairs)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ if ($namespace === '') {
+ return array_keys(apc_store($normalizedKeyValuePairs, null, $options->getTtl()));
+ }
+
+ $prefix = $namespace . $options->getNamespaceSeparator();
+ $internalKeyValuePairs = array();
+ foreach ($normalizedKeyValuePairs as $normalizedKey => &$value) {
+ $internalKey = $prefix . $normalizedKey;
+ $internalKeyValuePairs[$internalKey] = &$value;
+ }
+
+ $failedKeys = apc_store($internalKeyValuePairs, null, $options->getTtl());
+ $failedKeys = array_keys($failedKeys);
+
+ // remove prefix
+ $prefixL = strlen($prefix);
+ foreach ($failedKeys as & $key) {
+ $key = substr($key, $prefixL);
+ }
+
+ return $failedKeys;
+ }
+
+ /**
+ * Add an item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalAddItem(& $normalizedKey, & $value)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+ $ttl = $options->getTtl();
+
+ if (!apc_add($internalKey, $value, $ttl)) {
+ if (apc_exists($internalKey)) {
+ return false;
+ }
+
+ $type = is_object($value) ? get_class($value) : gettype($value);
+ throw new Exception\RuntimeException(
+ "apc_add('{$internalKey}', <{$type}>, {$ttl}) failed"
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Internal method to add multiple items.
+ *
+ * @param array $normalizedKeyValuePairs
+ * @return array Array of not stored keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalAddItems(array & $normalizedKeyValuePairs)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ if ($namespace === '') {
+ return array_keys(apc_add($normalizedKeyValuePairs, null, $options->getTtl()));
+ }
+
+ $prefix = $namespace . $options->getNamespaceSeparator();
+ $internalKeyValuePairs = array();
+ foreach ($normalizedKeyValuePairs as $normalizedKey => $value) {
+ $internalKey = $prefix . $normalizedKey;
+ $internalKeyValuePairs[$internalKey] = $value;
+ }
+
+ $failedKeys = apc_add($internalKeyValuePairs, null, $options->getTtl());
+ $failedKeys = array_keys($failedKeys);
+
+ // remove prefix
+ $prefixL = strlen($prefix);
+ foreach ($failedKeys as & $key) {
+ $key = substr($key, $prefixL);
+ }
+
+ return $failedKeys;
+ }
+
+ /**
+ * Internal method to replace an existing item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalReplaceItem(& $normalizedKey, & $value)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+
+ if (!apc_exists($internalKey)) {
+ return false;
+ }
+
+ $ttl = $options->getTtl();
+ if (!apc_store($internalKey, $value, $ttl)) {
+ $type = is_object($value) ? get_class($value) : gettype($value);
+ throw new Exception\RuntimeException(
+ "apc_store('{$internalKey}', <{$type}>, {$ttl}) failed"
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Internal method to remove an item.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalRemoveItem(& $normalizedKey)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ return apc_delete($prefix . $normalizedKey);
+ }
+
+ /**
+ * Internal method to remove multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Array of not removed keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalRemoveItems(array & $normalizedKeys)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ if ($namespace === '') {
+ return apc_delete($normalizedKeys);
+ }
+
+ $prefix = $namespace . $options->getNamespaceSeparator();
+ $internalKeys = array();
+ foreach ($normalizedKeys as $normalizedKey) {
+ $internalKeys[] = $prefix . $normalizedKey;
+ }
+
+ $failedKeys = apc_delete($internalKeys);
+
+ // remove prefix
+ $prefixL = strlen($prefix);
+ foreach ($failedKeys as & $key) {
+ $key = substr($key, $prefixL);
+ }
+
+ return $failedKeys;
+ }
+
+ /**
+ * Internal method to increment an item.
+ *
+ * @param string $normalizedKey
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalIncrementItem(& $normalizedKey, & $value)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+ $value = (int) $value;
+ $newValue = apc_inc($internalKey, $value);
+
+ // initial value
+ if ($newValue === false) {
+ $ttl = $options->getTtl();
+ $newValue = $value;
+ if (!apc_add($internalKey, $newValue, $ttl)) {
+ throw new Exception\RuntimeException(
+ "apc_add('{$internalKey}', {$newValue}, {$ttl}) failed"
+ );
+ }
+ }
+
+ return $newValue;
+ }
+
+ /**
+ * Internal method to decrement an item.
+ *
+ * @param string $normalizedKey
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalDecrementItem(& $normalizedKey, & $value)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+ $value = (int) $value;
+ $newValue = apc_dec($internalKey, $value);
+
+ // initial value
+ if ($newValue === false) {
+ $ttl = $options->getTtl();
+ $newValue = -$value;
+ if (!apc_add($internalKey, $newValue, $ttl)) {
+ throw new Exception\RuntimeException(
+ "apc_add('{$internalKey}', {$newValue}, {$ttl}) failed"
+ );
+ }
+ }
+
+ return $newValue;
+ }
+
+ /* status */
+
+ /**
+ * Internal method to get capabilities of this adapter
+ *
+ * @return Capabilities
+ */
+ protected function internalGetCapabilities()
+ {
+ if ($this->capabilities === null) {
+ $marker = new stdClass();
+ $capabilities = new Capabilities(
+ $this,
+ $marker,
+ array(
+ 'supportedDatatypes' => array(
+ 'NULL' => true,
+ 'boolean' => true,
+ 'integer' => true,
+ 'double' => true,
+ 'string' => true,
+ 'array' => true,
+ 'object' => 'object',
+ 'resource' => false,
+ ),
+ 'supportedMetadata' => array(
+ 'internal_key',
+ 'atime', 'ctime', 'mtime', 'rtime',
+ 'size', 'hits', 'ttl',
+ ),
+ 'minTtl' => 1,
+ 'maxTtl' => 0,
+ 'staticTtl' => true,
+ 'ttlPrecision' => 1,
+ 'useRequestTime' => (bool) ini_get('apc.use_request_time'),
+ 'expiredRead' => false,
+ 'maxKeyLength' => 5182,
+ 'namespaceIsPrefix' => true,
+ 'namespaceSeparator' => $this->getOptions()->getNamespaceSeparator(),
+ )
+ );
+
+ // update namespace separator on change option
+ $this->getEventManager()->attach('option', function ($event) use ($capabilities, $marker) {
+ $params = $event->getParams();
+
+ if (isset($params['namespace_separator'])) {
+ $capabilities->setNamespaceSeparator($marker, $params['namespace_separator']);
+ }
+ });
+
+ $this->capabilities = $capabilities;
+ $this->capabilityMarker = $marker;
+ }
+
+ return $this->capabilities;
+ }
+
+ /* internal */
+
+ /**
+ * Normalize metadata to work with APC
+ *
+ * @param array $metadata
+ * @return void
+ */
+ protected function normalizeMetadata(array & $metadata)
+ {
+ $metadata['internal_key'] = $metadata['key'];
+ $metadata['ctime'] = $metadata['creation_time'];
+ $metadata['atime'] = $metadata['access_time'];
+ $metadata['rtime'] = $metadata['deletion_time'];
+ $metadata['size'] = $metadata['mem_size'];
+ $metadata['hits'] = $metadata['num_hits'];
+
+ unset(
+ $metadata['key'],
+ $metadata['creation_time'],
+ $metadata['access_time'],
+ $metadata['deletion_time'],
+ $metadata['mem_size'],
+ $metadata['num_hits']
+ );
+ }
+
+ /**
+ * Internal method to set an item only if token matches
+ *
+ * @param mixed $token
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @see getItem()
+ * @see setItem()
+ */
+ protected function internalCheckAndSetItem(& $token, & $normalizedKey, & $value)
+ {
+ return apc_cas($normalizedKey, $token, $value);
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/ApcIterator.php b/library/Zend/Cache/Storage/Adapter/ApcIterator.php
new file mode 100755
index 0000000000..3cdcbf1c55
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/ApcIterator.php
@@ -0,0 +1,157 @@
+storage = $storage;
+ $this->baseIterator = $baseIterator;
+ $this->prefixLength = strlen($prefix);
+ }
+
+ /**
+ * Get storage instance
+ *
+ * @return Apc
+ */
+ public function getStorage()
+ {
+ return $this->storage;
+ }
+
+ /**
+ * Get iterator mode
+ *
+ * @return int Value of IteratorInterface::CURRENT_AS_*
+ */
+ public function getMode()
+ {
+ return $this->mode;
+ }
+
+ /**
+ * Set iterator mode
+ *
+ * @param int $mode
+ * @return ApcIterator Fluent interface
+ */
+ public function setMode($mode)
+ {
+ $this->mode = (int) $mode;
+ return $this;
+ }
+
+ /* Iterator */
+
+ /**
+ * Get current key, value or metadata.
+ *
+ * @return mixed
+ */
+ public function current()
+ {
+ if ($this->mode == IteratorInterface::CURRENT_AS_SELF) {
+ return $this;
+ }
+
+ $key = $this->key();
+
+ if ($this->mode == IteratorInterface::CURRENT_AS_VALUE) {
+ return $this->storage->getItem($key);
+ } elseif ($this->mode == IteratorInterface::CURRENT_AS_METADATA) {
+ return $this->storage->getMetadata($key);
+ }
+
+ return $key;
+ }
+
+ /**
+ * Get current key
+ *
+ * @return string
+ */
+ public function key()
+ {
+ $key = $this->baseIterator->key();
+
+ // remove namespace prefix
+ return substr($key, $this->prefixLength);
+ }
+
+ /**
+ * Move forward to next element
+ *
+ * @return void
+ */
+ public function next()
+ {
+ $this->baseIterator->next();
+ }
+
+ /**
+ * Checks if current position is valid
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return $this->baseIterator->valid();
+ }
+
+ /**
+ * Rewind the Iterator to the first element.
+ *
+ * @return void
+ */
+ public function rewind()
+ {
+ return $this->baseIterator->rewind();
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/ApcOptions.php b/library/Zend/Cache/Storage/Adapter/ApcOptions.php
new file mode 100755
index 0000000000..0299d9446c
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/ApcOptions.php
@@ -0,0 +1,47 @@
+triggerOptionEvent('namespace_separator', $namespaceSeparator);
+ $this->namespaceSeparator = $namespaceSeparator;
+ return $this;
+ }
+
+ /**
+ * Get namespace separator
+ *
+ * @return string
+ */
+ public function getNamespaceSeparator()
+ {
+ return $this->namespaceSeparator;
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/BlackHole.php b/library/Zend/Cache/Storage/Adapter/BlackHole.php
new file mode 100755
index 0000000000..2938cfdeed
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/BlackHole.php
@@ -0,0 +1,502 @@
+setOptions($options);
+ }
+ }
+
+ /**
+ * Set options.
+ *
+ * @param array|\Traversable|AdapterOptions $options
+ * @return StorageInterface Fluent interface
+ */
+ public function setOptions($options)
+ {
+ if ($this->options !== $options) {
+ if (!$options instanceof AdapterOptions) {
+ $options = new AdapterOptions($options);
+ }
+
+ if ($this->options) {
+ $this->options->setAdapter(null);
+ }
+ $options->setAdapter($this);
+ $this->options = $options;
+ }
+ return $this;
+ }
+
+ /**
+ * Get options
+ *
+ * @return AdapterOptions
+ */
+ public function getOptions()
+ {
+ if (!$this->options) {
+ $this->setOptions(new AdapterOptions());
+ }
+ return $this->options;
+ }
+
+ /**
+ * Get an item.
+ *
+ * @param string $key
+ * @param bool $success
+ * @param mixed $casToken
+ * @return mixed Data on success, null on failure
+ */
+ public function getItem($key, & $success = null, & $casToken = null)
+ {
+ $success = false;
+ return null;
+ }
+
+ /**
+ * Get multiple items.
+ *
+ * @param array $keys
+ * @return array Associative array of keys and values
+ */
+ public function getItems(array $keys)
+ {
+ return array();
+ }
+
+ /**
+ * Test if an item exists.
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function hasItem($key)
+ {
+ return false;
+ }
+
+ /**
+ * Test multiple items.
+ *
+ * @param array $keys
+ * @return array Array of found keys
+ */
+ public function hasItems(array $keys)
+ {
+ return array();
+ }
+
+ /**
+ * Get metadata of an item.
+ *
+ * @param string $key
+ * @return array|bool Metadata on success, false on failure
+ */
+ public function getMetadata($key)
+ {
+ return false;
+ }
+
+ /**
+ * Get multiple metadata
+ *
+ * @param array $keys
+ * @return array Associative array of keys and metadata
+ */
+ public function getMetadatas(array $keys)
+ {
+ return array();
+ }
+
+ /**
+ * Store an item.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return bool
+ */
+ public function setItem($key, $value)
+ {
+ return false;
+ }
+
+ /**
+ * Store multiple items.
+ *
+ * @param array $keyValuePairs
+ * @return array Array of not stored keys
+ */
+ public function setItems(array $keyValuePairs)
+ {
+ return array_keys($keyValuePairs);
+ }
+
+ /**
+ * Add an item.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return bool
+ */
+ public function addItem($key, $value)
+ {
+ return false;
+ }
+
+ /**
+ * Add multiple items.
+ *
+ * @param array $keyValuePairs
+ * @return array Array of not stored keys
+ */
+ public function addItems(array $keyValuePairs)
+ {
+ return array_keys($keyValuePairs);
+ }
+
+ /**
+ * Replace an existing item.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return bool
+ */
+ public function replaceItem($key, $value)
+ {
+ return false;
+ }
+
+ /**
+ * Replace multiple existing items.
+ *
+ * @param array $keyValuePairs
+ * @return array Array of not stored keys
+ */
+ public function replaceItems(array $keyValuePairs)
+ {
+ return array_keys($keyValuePairs);
+ }
+
+ /**
+ * Set an item only if token matches
+ *
+ * It uses the token received from getItem() to check if the item has
+ * changed before overwriting it.
+ *
+ * @param mixed $token
+ * @param string $key
+ * @param mixed $value
+ * @return bool
+ */
+ public function checkAndSetItem($token, $key, $value)
+ {
+ return false;
+ }
+
+ /**
+ * Reset lifetime of an item
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function touchItem($key)
+ {
+ return false;
+ }
+
+ /**
+ * Reset lifetime of multiple items.
+ *
+ * @param array $keys
+ * @return array Array of not updated keys
+ */
+ public function touchItems(array $keys)
+ {
+ return $keys;
+ }
+
+ /**
+ * Remove an item.
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function removeItem($key)
+ {
+ return false;
+ }
+
+ /**
+ * Remove multiple items.
+ *
+ * @param array $keys
+ * @return array Array of not removed keys
+ */
+ public function removeItems(array $keys)
+ {
+ return $keys;
+ }
+
+ /**
+ * Increment an item.
+ *
+ * @param string $key
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ */
+ public function incrementItem($key, $value)
+ {
+ return false;
+ }
+
+ /**
+ * Increment multiple items.
+ *
+ * @param array $keyValuePairs
+ * @return array Associative array of keys and new values
+ */
+ public function incrementItems(array $keyValuePairs)
+ {
+ return array();
+ }
+
+ /**
+ * Decrement an item.
+ *
+ * @param string $key
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ */
+ public function decrementItem($key, $value)
+ {
+ return false;
+ }
+
+ /**
+ * Decrement multiple items.
+ *
+ * @param array $keyValuePairs
+ * @return array Associative array of keys and new values
+ */
+ public function decrementItems(array $keyValuePairs)
+ {
+ return array();
+ }
+
+ /**
+ * Capabilities of this storage
+ *
+ * @return Capabilities
+ */
+ public function getCapabilities()
+ {
+ if ($this->capabilities === null) {
+ // use default capabilities only
+ $this->capabilityMarker = new stdClass();
+ $this->capabilities = new Capabilities($this, $this->capabilityMarker);
+ }
+ return $this->capabilities;
+ }
+
+ /* AvailableSpaceCapableInterface */
+
+ /**
+ * Get available space in bytes
+ *
+ * @return int|float
+ */
+ public function getAvailableSpace()
+ {
+ return 0;
+ }
+
+ /* ClearByNamespaceInterface */
+
+ /**
+ * Remove items of given namespace
+ *
+ * @param string $namespace
+ * @return bool
+ */
+ public function clearByNamespace($namespace)
+ {
+ return false;
+ }
+
+ /* ClearByPrefixInterface */
+
+ /**
+ * Remove items matching given prefix
+ *
+ * @param string $prefix
+ * @return bool
+ */
+ public function clearByPrefix($prefix)
+ {
+ return false;
+ }
+
+ /* ClearExpiredInterface */
+
+ /**
+ * Remove expired items
+ *
+ * @return bool
+ */
+ public function clearExpired()
+ {
+ return false;
+ }
+
+ /* FlushableInterface */
+
+ /**
+ * Flush the whole storage
+ *
+ * @return bool
+ */
+ public function flush()
+ {
+ return false;
+ }
+
+ /* IterableInterface */
+
+ /**
+ * Get the storage iterator
+ *
+ * @return KeyListIterator
+ */
+ public function getIterator()
+ {
+ return new KeyListIterator($this, array());
+ }
+
+ /* OptimizableInterface */
+
+ /**
+ * Optimize the storage
+ *
+ * @return bool
+ */
+ public function optimize()
+ {
+ return false;
+ }
+
+ /* TaggableInterface */
+
+ /**
+ * Set tags to an item by given key.
+ * An empty array will remove all tags.
+ *
+ * @param string $key
+ * @param string[] $tags
+ * @return bool
+ */
+ public function setTags($key, array $tags)
+ {
+ return false;
+ }
+
+ /**
+ * Get tags of an item by given key
+ *
+ * @param string $key
+ * @return string[]|FALSE
+ */
+ public function getTags($key)
+ {
+ return false;
+ }
+
+ /**
+ * Remove items matching given tags.
+ *
+ * If $disjunction only one of the given tags must match
+ * else all given tags must match.
+ *
+ * @param string[] $tags
+ * @param bool $disjunction
+ * @return bool
+ */
+ public function clearByTags(array $tags, $disjunction = false)
+ {
+ return false;
+ }
+
+ /* TotalSpaceCapableInterface */
+
+ /**
+ * Get total space in bytes
+ *
+ * @return int|float
+ */
+ public function getTotalSpace()
+ {
+ return 0;
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/Dba.php b/library/Zend/Cache/Storage/Adapter/Dba.php
new file mode 100755
index 0000000000..bb2860d7c0
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/Dba.php
@@ -0,0 +1,541 @@
+_close();
+
+ parent::__destruct();
+ }
+
+ /* options */
+
+ /**
+ * Set options.
+ *
+ * @param array|Traversable|DbaOptions $options
+ * @return Apc
+ * @see getOptions()
+ */
+ public function setOptions($options)
+ {
+ if (!$options instanceof DbaOptions) {
+ $options = new DbaOptions($options);
+ }
+
+ return parent::setOptions($options);
+ }
+
+ /**
+ * Get options.
+ *
+ * @return DbaOptions
+ * @see setOptions()
+ */
+ public function getOptions()
+ {
+ if (!$this->options) {
+ $this->setOptions(new DbaOptions());
+ }
+ return $this->options;
+ }
+
+ /* TotalSpaceCapableInterface */
+
+ /**
+ * Get total space in bytes
+ *
+ * @return int|float
+ */
+ public function getTotalSpace()
+ {
+ if ($this->totalSpace === null) {
+ $pathname = $this->getOptions()->getPathname();
+
+ if ($pathname === '') {
+ throw new Exception\LogicException('No pathname to database file');
+ }
+
+ ErrorHandler::start();
+ $total = disk_total_space(dirname($pathname));
+ $error = ErrorHandler::stop();
+ if ($total === false) {
+ throw new Exception\RuntimeException("Can't detect total space of '{$pathname}'", 0, $error);
+ }
+ $this->totalSpace = $total;
+
+ // clean total space buffer on change pathname
+ $events = $this->getEventManager();
+ $handle = null;
+ $totalSpace = & $this->totalSpace;
+ $callback = function ($event) use (& $events, & $handle, & $totalSpace) {
+ $params = $event->getParams();
+ if (isset($params['pathname'])) {
+ $totalSpace = null;
+ $events->detach($handle);
+ }
+ };
+ $events->attach('option', $callback);
+ }
+
+ return $this->totalSpace;
+ }
+
+ /* AvailableSpaceCapableInterface */
+
+ /**
+ * Get available space in bytes
+ *
+ * @return int|float
+ */
+ public function getAvailableSpace()
+ {
+ $pathname = $this->getOptions()->getPathname();
+
+ if ($pathname === '') {
+ throw new Exception\LogicException('No pathname to database file');
+ }
+
+ ErrorHandler::start();
+ $avail = disk_free_space(dirname($pathname));
+ $error = ErrorHandler::stop();
+ if ($avail === false) {
+ throw new Exception\RuntimeException("Can't detect free space of '{$pathname}'", 0, $error);
+ }
+
+ return $avail;
+ }
+
+ /* FlushableInterface */
+
+ /**
+ * Flush the whole storage
+ *
+ * @return bool
+ */
+ public function flush()
+ {
+ $pathname = $this->getOptions()->getPathname();
+
+ if ($pathname === '') {
+ throw new Exception\LogicException('No pathname to database file');
+ }
+
+ if (file_exists($pathname)) {
+ // close the dba file before delete
+ // and reopen (create) on next use
+ $this->_close();
+
+ ErrorHandler::start();
+ $result = unlink($pathname);
+ $error = ErrorHandler::stop();
+ if (!$result) {
+ throw new Exception\RuntimeException("unlink('{$pathname}') failed", 0, $error);
+ }
+ }
+
+ return true;
+ }
+
+ /* ClearByNamespaceInterface */
+
+ /**
+ * Remove items by given namespace
+ *
+ * @param string $namespace
+ * @return bool
+ */
+ public function clearByNamespace($namespace)
+ {
+ $namespace = (string) $namespace;
+ if ($namespace === '') {
+ throw new Exception\InvalidArgumentException('No namespace given');
+ }
+
+ $prefix = $namespace . $this->getOptions()->getNamespaceSeparator();
+ $prefixl = strlen($prefix);
+ $result = true;
+
+ $this->_open();
+
+ do { // Workaround for PHP-Bug #62491 & #62492
+ $recheck = false;
+ $internalKey = dba_firstkey($this->handle);
+ while ($internalKey !== false && $internalKey !== null) {
+ if (substr($internalKey, 0, $prefixl) === $prefix) {
+ $result = dba_delete($internalKey, $this->handle) && $result;
+ }
+
+ $internalKey = dba_nextkey($this->handle);
+ }
+ } while ($recheck);
+
+ return $result;
+ }
+
+ /* ClearByPrefixInterface */
+
+ /**
+ * Remove items matching given prefix
+ *
+ * @param string $prefix
+ * @return bool
+ */
+ public function clearByPrefix($prefix)
+ {
+ $prefix = (string) $prefix;
+ if ($prefix === '') {
+ throw new Exception\InvalidArgumentException('No prefix given');
+ }
+
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator() . $prefix;
+ $prefixL = strlen($prefix);
+ $result = true;
+
+ $this->_open();
+
+ do { // Workaround for PHP-Bug #62491 & #62492
+ $recheck = false;
+ $internalKey = dba_firstkey($this->handle);
+ while ($internalKey !== false && $internalKey !== null) {
+ if (substr($internalKey, 0, $prefixL) === $prefix) {
+ $result = dba_delete($internalKey, $this->handle) && $result;
+ $recheck = true;
+ }
+
+ $internalKey = dba_nextkey($this->handle);
+ }
+ } while ($recheck);
+
+ return $result;
+ }
+
+ /* IterableInterface */
+
+ /**
+ * Get the storage iterator
+ *
+ * @return ApcIterator
+ */
+ public function getIterator()
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+
+ return new DbaIterator($this, $this->handle, $prefix);
+ }
+
+ /* OptimizableInterface */
+
+ /**
+ * Optimize the storage
+ *
+ * @return bool
+ * @return Exception\RuntimeException
+ */
+ public function optimize()
+ {
+ $this->_open();
+ if (!dba_optimize($this->handle)) {
+ throw new Exception\RuntimeException('dba_optimize failed');
+ }
+ return true;
+ }
+
+ /* reading */
+
+ /**
+ * Internal method to get an item.
+ *
+ * @param string $normalizedKey
+ * @param bool $success
+ * @param mixed $casToken
+ * @return mixed Data on success, null on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetItem(& $normalizedKey, & $success = null, & $casToken = null)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+
+ $this->_open();
+ $value = dba_fetch($prefix . $normalizedKey, $this->handle);
+
+ if ($value === false) {
+ $success = false;
+ return null;
+ }
+
+ $success = true;
+ $casToken = $value;
+ return $value;
+ }
+
+ /**
+ * Internal method to test if an item exists.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalHasItem(& $normalizedKey)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+
+ $this->_open();
+ return dba_exists($prefix . $normalizedKey, $this->handle);
+ }
+
+ /* writing */
+
+ /**
+ * Internal method to store an item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalSetItem(& $normalizedKey, & $value)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+
+ $this->_open();
+ if (!dba_replace($internalKey, $value, $this->handle)) {
+ throw new Exception\RuntimeException("dba_replace('{$internalKey}', ...) failed");
+ }
+
+ return true;
+ }
+
+ /**
+ * Add an item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalAddItem(& $normalizedKey, & $value)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+
+ $this->_open();
+
+ // Workaround for PHP-Bug #54242 & #62489
+ if (dba_exists($internalKey, $this->handle)) {
+ return false;
+ }
+
+ // Workaround for PHP-Bug #54242 & #62489
+ // dba_insert returns true if key already exists
+ ErrorHandler::start();
+ $result = dba_insert($internalKey, $value, $this->handle);
+ $error = ErrorHandler::stop();
+ if (!$result || $error) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Internal method to remove an item.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalRemoveItem(& $normalizedKey)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+
+ $this->_open();
+
+ // Workaround for PHP-Bug #62490
+ if (!dba_exists($internalKey, $this->handle)) {
+ return false;
+ }
+
+ return dba_delete($internalKey, $this->handle);
+ }
+
+ /* status */
+
+ /**
+ * Internal method to get capabilities of this adapter
+ *
+ * @return Capabilities
+ */
+ protected function internalGetCapabilities()
+ {
+ if ($this->capabilities === null) {
+ $marker = new stdClass();
+ $capabilities = new Capabilities(
+ $this,
+ $marker,
+ array(
+ 'supportedDatatypes' => array(
+ 'NULL' => 'string',
+ 'boolean' => 'string',
+ 'integer' => 'string',
+ 'double' => 'string',
+ 'string' => true,
+ 'array' => false,
+ 'object' => false,
+ 'resource' => false,
+ ),
+ 'minTtl' => 0,
+ 'supportedMetadata' => array(),
+ 'maxKeyLength' => 0, // TODO: maxKeyLength ????
+ 'namespaceIsPrefix' => true,
+ 'namespaceSeparator' => $this->getOptions()->getNamespaceSeparator(),
+ )
+ );
+
+ // update namespace separator on change option
+ $this->getEventManager()->attach('option', function ($event) use ($capabilities, $marker) {
+ $params = $event->getParams();
+
+ if (isset($params['namespace_separator'])) {
+ $capabilities->setNamespaceSeparator($marker, $params['namespace_separator']);
+ }
+ });
+
+ $this->capabilities = $capabilities;
+ $this->capabilityMarker = $marker;
+ }
+
+ return $this->capabilities;
+ }
+
+ /**
+ * Open the database if not already done.
+ *
+ * @return void
+ * @throws Exception\LogicException
+ * @throws Exception\RuntimeException
+ */
+ protected function _open()
+ {
+ if (!$this->handle) {
+ $options = $this->getOptions();
+ $pathname = $options->getPathname();
+ $mode = $options->getMode();
+ $handler = $options->getHandler();
+
+ if ($pathname === '') {
+ throw new Exception\LogicException('No pathname to database file');
+ }
+
+ ErrorHandler::start();
+ $dba = dba_open($pathname, $mode, $handler);
+ $err = ErrorHandler::stop();
+ if (!$dba) {
+ throw new Exception\RuntimeException(
+ "dba_open('{$pathname}', '{$mode}', '{$handler}') failed",
+ 0,
+ $err
+ );
+ }
+ $this->handle = $dba;
+ }
+ }
+
+ /**
+ * Close database file if opened
+ *
+ * @return void
+ */
+ protected function _close()
+ {
+ if ($this->handle) {
+ ErrorHandler::start(E_NOTICE);
+ dba_close($this->handle);
+ ErrorHandler::stop();
+ $this->handle = null;
+ }
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/DbaIterator.php b/library/Zend/Cache/Storage/Adapter/DbaIterator.php
new file mode 100755
index 0000000000..a895ce545b
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/DbaIterator.php
@@ -0,0 +1,190 @@
+storage = $storage;
+ $this->handle = $handle;
+ $this->prefixLength = strlen($prefix);
+
+ $this->rewind();
+ }
+
+ /**
+ * Get storage instance
+ *
+ * @return Dba
+ */
+ public function getStorage()
+ {
+ return $this->storage;
+ }
+
+ /**
+ * Get iterator mode
+ *
+ * @return int Value of IteratorInterface::CURRENT_AS_*
+ */
+ public function getMode()
+ {
+ return $this->mode;
+ }
+
+ /**
+ * Set iterator mode
+ *
+ * @param int $mode
+ * @return ApcIterator Fluent interface
+ */
+ public function setMode($mode)
+ {
+ $this->mode = (int) $mode;
+ return $this;
+ }
+
+ /* Iterator */
+
+ /**
+ * Get current key, value or metadata.
+ *
+ * @return mixed
+ * @throws Exception\RuntimeException
+ */
+ public function current()
+ {
+ if ($this->mode == IteratorInterface::CURRENT_AS_SELF) {
+ return $this;
+ }
+
+ $key = $this->key();
+
+ if ($this->mode == IteratorInterface::CURRENT_AS_VALUE) {
+ return $this->storage->getItem($key);
+ } elseif ($this->mode == IteratorInterface::CURRENT_AS_METADATA) {
+ return $this->storage->getMetadata($key);
+ }
+
+ return $key;
+ }
+
+ /**
+ * Get current key
+ *
+ * @return string
+ * @throws Exception\RuntimeException
+ */
+ public function key()
+ {
+ if ($this->currentInternalKey === false) {
+ throw new Exception\RuntimeException("Iterator is on an invalid state");
+ }
+
+ // remove namespace prefix
+ return substr($this->currentInternalKey, $this->prefixLength);
+ }
+
+ /**
+ * Move forward to next element
+ *
+ * @return void
+ * @throws Exception\RuntimeException
+ */
+ public function next()
+ {
+ if ($this->currentInternalKey === false) {
+ throw new Exception\RuntimeException("Iterator is on an invalid state");
+ }
+
+ $this->currentInternalKey = dba_nextkey($this->handle);
+
+ // Workaround for PHP-Bug #62492
+ if ($this->currentInternalKey === null) {
+ $this->currentInternalKey = false;
+ }
+ }
+
+ /**
+ * Checks if current position is valid
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return ($this->currentInternalKey !== false);
+ }
+
+ /**
+ * Rewind the Iterator to the first element.
+ *
+ * @return void
+ * @throws Exception\RuntimeException
+ */
+ public function rewind()
+ {
+ if ($this->currentInternalKey === false) {
+ throw new Exception\RuntimeException("Iterator is on an invalid state");
+ }
+
+ $this->currentInternalKey = dba_firstkey($this->handle);
+
+ // Workaround for PHP-Bug #62492
+ if ($this->currentInternalKey === null) {
+ $this->currentInternalKey = false;
+ }
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/DbaOptions.php b/library/Zend/Cache/Storage/Adapter/DbaOptions.php
new file mode 100755
index 0000000000..13172b7498
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/DbaOptions.php
@@ -0,0 +1,129 @@
+triggerOptionEvent('namespace_separator', $namespaceSeparator);
+ $this->namespaceSeparator = $namespaceSeparator;
+ return $this;
+ }
+
+ /**
+ * Get namespace separator
+ *
+ * @return string
+ */
+ public function getNamespaceSeparator()
+ {
+ return $this->namespaceSeparator;
+ }
+
+ /**
+ * Set pathname to database file
+ *
+ * @param string $pathname
+ * @return DbaOptions
+ */
+ public function setPathname($pathname)
+ {
+ $this->pathname = (string) $pathname;
+ $this->triggerOptionEvent('pathname', $pathname);
+ return $this;
+ }
+
+ /**
+ * Get pathname to database file
+ *
+ * @return string
+ */
+ public function getPathname()
+ {
+ return $this->pathname;
+ }
+
+ /**
+ *
+ *
+ * @param string $mode
+ * @return \Zend\Cache\Storage\Adapter\DbaOptions
+ */
+ public function setMode($mode)
+ {
+ $this->mode = (string) $mode;
+ $this->triggerOptionEvent('mode', $mode);
+ return $this;
+ }
+
+ public function getMode()
+ {
+ return $this->mode;
+ }
+
+ public function setHandler($handler)
+ {
+ $handler = (string) $handler;
+
+ if (!function_exists('dba_handlers') || !in_array($handler, dba_handlers())) {
+ throw new Exception\ExtensionNotLoadedException("DBA-Handler '{$handler}' not supported");
+ }
+
+ $this->triggerOptionEvent('handler', $handler);
+ $this->handler = $handler;
+ return $this;
+ }
+
+ public function getHandler()
+ {
+ return $this->handler;
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/Filesystem.php b/library/Zend/Cache/Storage/Adapter/Filesystem.php
new file mode 100755
index 0000000000..ef1c2a7dee
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/Filesystem.php
@@ -0,0 +1,1616 @@
+options) {
+ $this->setOptions(new FilesystemOptions());
+ }
+ return $this->options;
+ }
+
+ /* FlushableInterface */
+
+ /**
+ * Flush the whole storage
+ *
+ * @throws Exception\RuntimeException
+ * @return bool
+ */
+ public function flush()
+ {
+ $flags = GlobIterator::SKIP_DOTS | GlobIterator::CURRENT_AS_PATHNAME;
+ $dir = $this->getOptions()->getCacheDir();
+ $clearFolder = null;
+ $clearFolder = function ($dir) use (& $clearFolder, $flags) {
+ $it = new GlobIterator($dir . DIRECTORY_SEPARATOR . '*', $flags);
+ foreach ($it as $pathname) {
+ if ($it->isDir()) {
+ $clearFolder($pathname);
+ rmdir($pathname);
+ } else {
+ unlink($pathname);
+ }
+ }
+ };
+
+ ErrorHandler::start();
+ $clearFolder($dir);
+ $error = ErrorHandler::stop();
+ if ($error) {
+ throw new Exception\RuntimeException("Flushing directory '{$dir}' failed", 0, $error);
+ }
+
+ return true;
+ }
+
+ /* ClearExpiredInterface */
+
+ /**
+ * Remove expired items
+ *
+ * @return bool
+ *
+ * @triggers clearExpired.exception(ExceptionEvent)
+ */
+ public function clearExpired()
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+
+ $flags = GlobIterator::SKIP_DOTS | GlobIterator::CURRENT_AS_FILEINFO;
+ $path = $options->getCacheDir()
+ . str_repeat(DIRECTORY_SEPARATOR . $prefix . '*', $options->getDirLevel())
+ . DIRECTORY_SEPARATOR . $prefix . '*.dat';
+ $glob = new GlobIterator($path, $flags);
+ $time = time();
+ $ttl = $options->getTtl();
+
+ ErrorHandler::start();
+ foreach ($glob as $entry) {
+ $mtime = $entry->getMTime();
+ if ($time >= $mtime + $ttl) {
+ $pathname = $entry->getPathname();
+ unlink($pathname);
+
+ $tagPathname = substr($pathname, 0, -4) . '.tag';
+ if (file_exists($tagPathname)) {
+ unlink($tagPathname);
+ }
+ }
+ }
+ $error = ErrorHandler::stop();
+ if ($error) {
+ $result = false;
+ return $this->triggerException(
+ __FUNCTION__,
+ new ArrayObject(),
+ $result,
+ new Exception\RuntimeException('Failed to clear expired items', 0, $error)
+ );
+ }
+
+ return true;
+ }
+
+ /* ClearByNamespaceInterface */
+
+ /**
+ * Remove items by given namespace
+ *
+ * @param string $namespace
+ * @throws Exception\RuntimeException
+ * @return bool
+ */
+ public function clearByNamespace($namespace)
+ {
+ $namespace = (string) $namespace;
+ if ($namespace === '') {
+ throw new Exception\InvalidArgumentException('No namespace given');
+ }
+
+ $options = $this->getOptions();
+ $prefix = $namespace . $options->getNamespaceSeparator();
+
+ $flags = GlobIterator::SKIP_DOTS | GlobIterator::CURRENT_AS_PATHNAME;
+ $path = $options->getCacheDir()
+ . str_repeat(DIRECTORY_SEPARATOR . $prefix . '*', $options->getDirLevel())
+ . DIRECTORY_SEPARATOR . $prefix . '*.*';
+ $glob = new GlobIterator($path, $flags);
+
+ ErrorHandler::start();
+ foreach ($glob as $pathname) {
+ unlink($pathname);
+ }
+ $error = ErrorHandler::stop();
+ if ($error) {
+ throw new Exception\RuntimeException("Failed to remove files of '{$path}'", 0, $error);
+ }
+
+ return true;
+ }
+
+ /* ClearByPrefixInterface */
+
+ /**
+ * Remove items matching given prefix
+ *
+ * @param string $prefix
+ * @throws Exception\RuntimeException
+ * @return bool
+ */
+ public function clearByPrefix($prefix)
+ {
+ $prefix = (string) $prefix;
+ if ($prefix === '') {
+ throw new Exception\InvalidArgumentException('No prefix given');
+ }
+
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $nsPrefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+
+ $flags = GlobIterator::SKIP_DOTS | GlobIterator::CURRENT_AS_PATHNAME;
+ $path = $options->getCacheDir()
+ . str_repeat(DIRECTORY_SEPARATOR . $nsPrefix . '*', $options->getDirLevel())
+ . DIRECTORY_SEPARATOR . $nsPrefix . $prefix . '*.*';
+ $glob = new GlobIterator($path, $flags);
+
+ ErrorHandler::start();
+ foreach ($glob as $pathname) {
+ unlink($pathname);
+ }
+ $error = ErrorHandler::stop();
+ if ($error) {
+ throw new Exception\RuntimeException("Failed to remove files of '{$path}'", 0, $error);
+ }
+
+ return true;
+ }
+
+ /* TaggableInterface */
+
+ /**
+ * Set tags to an item by given key.
+ * An empty array will remove all tags.
+ *
+ * @param string $key
+ * @param string[] $tags
+ * @return bool
+ */
+ public function setTags($key, array $tags)
+ {
+ $this->normalizeKey($key);
+ if (!$this->internalHasItem($key)) {
+ return false;
+ }
+
+ $filespec = $this->getFileSpec($key);
+
+ if (!$tags) {
+ $this->unlink($filespec . '.tag');
+ return true;
+ }
+
+ $this->putFileContent($filespec . '.tag', implode("\n", $tags));
+ return true;
+ }
+
+ /**
+ * Get tags of an item by given key
+ *
+ * @param string $key
+ * @return string[]|FALSE
+ */
+ public function getTags($key)
+ {
+ $this->normalizeKey($key);
+ if (!$this->internalHasItem($key)) {
+ return false;
+ }
+
+ $filespec = $this->getFileSpec($key);
+ $tags = array();
+ if (file_exists($filespec . '.tag')) {
+ $tags = explode("\n", $this->getFileContent($filespec . '.tag'));
+ }
+
+ return $tags;
+ }
+
+ /**
+ * Remove items matching given tags.
+ *
+ * If $disjunction only one of the given tags must match
+ * else all given tags must match.
+ *
+ * @param string[] $tags
+ * @param bool $disjunction
+ * @return bool
+ */
+ public function clearByTags(array $tags, $disjunction = false)
+ {
+ if (!$tags) {
+ return true;
+ }
+
+ $tagCount = count($tags);
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+
+ $flags = GlobIterator::SKIP_DOTS | GlobIterator::CURRENT_AS_PATHNAME;
+ $path = $options->getCacheDir()
+ . str_repeat(DIRECTORY_SEPARATOR . $prefix . '*', $options->getDirLevel())
+ . DIRECTORY_SEPARATOR . $prefix . '*.tag';
+ $glob = new GlobIterator($path, $flags);
+
+ foreach ($glob as $pathname) {
+ $diff = array_diff($tags, explode("\n", $this->getFileContent($pathname)));
+
+ $rem = false;
+ if ($disjunction && count($diff) < $tagCount) {
+ $rem = true;
+ } elseif (!$disjunction && !$diff) {
+ $rem = true;
+ }
+
+ if ($rem) {
+ unlink($pathname);
+
+ $datPathname = substr($pathname, 0, -4) . '.dat';
+ if (file_exists($datPathname)) {
+ unlink($datPathname);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /* IterableInterface */
+
+ /**
+ * Get the storage iterator
+ *
+ * @return FilesystemIterator
+ */
+ public function getIterator()
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $path = $options->getCacheDir()
+ . str_repeat(DIRECTORY_SEPARATOR . $prefix . '*', $options->getDirLevel())
+ . DIRECTORY_SEPARATOR . $prefix . '*.dat';
+ return new FilesystemIterator($this, $path, $prefix);
+ }
+
+ /* OptimizableInterface */
+
+ /**
+ * Optimize the storage
+ *
+ * @return bool
+ * @return Exception\RuntimeException
+ */
+ public function optimize()
+ {
+ $options = $this->getOptions();
+ if ($options->getDirLevel()) {
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+
+ // removes only empty directories
+ $this->rmDir($options->getCacheDir(), $prefix);
+ }
+ return true;
+ }
+
+ /* TotalSpaceCapableInterface */
+
+ /**
+ * Get total space in bytes
+ *
+ * @throws Exception\RuntimeException
+ * @return int|float
+ */
+ public function getTotalSpace()
+ {
+ if ($this->totalSpace === null) {
+ $path = $this->getOptions()->getCacheDir();
+
+ ErrorHandler::start();
+ $total = disk_total_space($path);
+ $error = ErrorHandler::stop();
+ if ($total === false) {
+ throw new Exception\RuntimeException("Can't detect total space of '{$path}'", 0, $error);
+ }
+ $this->totalSpace = $total;
+
+ // clean total space buffer on change cache_dir
+ $events = $this->getEventManager();
+ $handle = null;
+ $totalSpace = & $this->totalSpace;
+ $callback = function ($event) use (& $events, & $handle, & $totalSpace) {
+ $params = $event->getParams();
+ if (isset($params['cache_dir'])) {
+ $totalSpace = null;
+ $events->detach($handle);
+ }
+ };
+ $events->attach('option', $callback);
+ }
+
+ return $this->totalSpace;
+ }
+
+ /* AvailableSpaceCapableInterface */
+
+ /**
+ * Get available space in bytes
+ *
+ * @throws Exception\RuntimeException
+ * @return int|float
+ */
+ public function getAvailableSpace()
+ {
+ $path = $this->getOptions()->getCacheDir();
+
+ ErrorHandler::start();
+ $avail = disk_free_space($path);
+ $error = ErrorHandler::stop();
+ if ($avail === false) {
+ throw new Exception\RuntimeException("Can't detect free space of '{$path}'", 0, $error);
+ }
+
+ return $avail;
+ }
+
+ /* reading */
+
+ /**
+ * Get an item.
+ *
+ * @param string $key
+ * @param bool $success
+ * @param mixed $casToken
+ * @return mixed Data on success, null on failure
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers getItem.pre(PreEvent)
+ * @triggers getItem.post(PostEvent)
+ * @triggers getItem.exception(ExceptionEvent)
+ */
+ public function getItem($key, & $success = null, & $casToken = null)
+ {
+ $options = $this->getOptions();
+ if ($options->getReadable() && $options->getClearStatCache()) {
+ clearstatcache();
+ }
+
+ $argn = func_num_args();
+ if ($argn > 2) {
+ return parent::getItem($key, $success, $casToken);
+ } elseif ($argn > 1) {
+ return parent::getItem($key, $success);
+ }
+
+ return parent::getItem($key);
+ }
+
+ /**
+ * Get multiple items.
+ *
+ * @param array $keys
+ * @return array Associative array of keys and values
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers getItems.pre(PreEvent)
+ * @triggers getItems.post(PostEvent)
+ * @triggers getItems.exception(ExceptionEvent)
+ */
+ public function getItems(array $keys)
+ {
+ $options = $this->getOptions();
+ if ($options->getReadable() && $options->getClearStatCache()) {
+ clearstatcache();
+ }
+
+ return parent::getItems($keys);
+ }
+
+ /**
+ * Internal method to get an item.
+ *
+ * @param string $normalizedKey
+ * @param bool $success
+ * @param mixed $casToken
+ * @return mixed Data on success, null on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetItem(& $normalizedKey, & $success = null, & $casToken = null)
+ {
+ if (!$this->internalHasItem($normalizedKey)) {
+ $success = false;
+ return null;
+ }
+
+ try {
+ $filespec = $this->getFileSpec($normalizedKey);
+ $data = $this->getFileContent($filespec . '.dat');
+
+ // use filemtime + filesize as CAS token
+ if (func_num_args() > 2) {
+ $casToken = filemtime($filespec . '.dat') . filesize($filespec . '.dat');
+ }
+ $success = true;
+ return $data;
+ } catch (BaseException $e) {
+ $success = false;
+ throw $e;
+ }
+ }
+
+ /**
+ * Internal method to get multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Associative array of keys and values
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetItems(array & $normalizedKeys)
+ {
+ $keys = $normalizedKeys; // Don't change argument passed by reference
+ $result = array();
+ while ($keys) {
+ // LOCK_NB if more than one items have to read
+ $nonBlocking = count($keys) > 1;
+ $wouldblock = null;
+
+ // read items
+ foreach ($keys as $i => $key) {
+ if (!$this->internalHasItem($key)) {
+ unset($keys[$i]);
+ continue;
+ }
+
+ $filespec = $this->getFileSpec($key);
+ $data = $this->getFileContent($filespec . '.dat', $nonBlocking, $wouldblock);
+ if ($nonBlocking && $wouldblock) {
+ continue;
+ } else {
+ unset($keys[$i]);
+ }
+
+ $result[$key] = $data;
+ }
+
+ // TODO: Don't check ttl after first iteration
+ // $options['ttl'] = 0;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Test if an item exists.
+ *
+ * @param string $key
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers hasItem.pre(PreEvent)
+ * @triggers hasItem.post(PostEvent)
+ * @triggers hasItem.exception(ExceptionEvent)
+ */
+ public function hasItem($key)
+ {
+ $options = $this->getOptions();
+ if ($options->getReadable() && $options->getClearStatCache()) {
+ clearstatcache();
+ }
+
+ return parent::hasItem($key);
+ }
+
+ /**
+ * Test multiple items.
+ *
+ * @param array $keys
+ * @return array Array of found keys
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers hasItems.pre(PreEvent)
+ * @triggers hasItems.post(PostEvent)
+ * @triggers hasItems.exception(ExceptionEvent)
+ */
+ public function hasItems(array $keys)
+ {
+ $options = $this->getOptions();
+ if ($options->getReadable() && $options->getClearStatCache()) {
+ clearstatcache();
+ }
+
+ return parent::hasItems($keys);
+ }
+
+ /**
+ * Internal method to test if an item exists.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalHasItem(& $normalizedKey)
+ {
+ $file = $this->getFileSpec($normalizedKey) . '.dat';
+ if (!file_exists($file)) {
+ return false;
+ }
+
+ $ttl = $this->getOptions()->getTtl();
+ if ($ttl) {
+ ErrorHandler::start();
+ $mtime = filemtime($file);
+ $error = ErrorHandler::stop();
+ if (!$mtime) {
+ throw new Exception\RuntimeException("Error getting mtime of file '{$file}'", 0, $error);
+ }
+
+ if (time() >= ($mtime + $ttl)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Get metadata
+ *
+ * @param string $key
+ * @return array|bool Metadata on success, false on failure
+ */
+ public function getMetadata($key)
+ {
+ $options = $this->getOptions();
+ if ($options->getReadable() && $options->getClearStatCache()) {
+ clearstatcache();
+ }
+
+ return parent::getMetadata($key);
+ }
+
+ /**
+ * Get metadatas
+ *
+ * @param array $keys
+ * @param array $options
+ * @return array Associative array of keys and metadata
+ */
+ public function getMetadatas(array $keys, array $options = array())
+ {
+ $options = $this->getOptions();
+ if ($options->getReadable() && $options->getClearStatCache()) {
+ clearstatcache();
+ }
+
+ return parent::getMetadatas($keys);
+ }
+
+ /**
+ * Get info by key
+ *
+ * @param string $normalizedKey
+ * @return array|bool Metadata on success, false on failure
+ */
+ protected function internalGetMetadata(& $normalizedKey)
+ {
+ if (!$this->internalHasItem($normalizedKey)) {
+ return false;
+ }
+
+ $options = $this->getOptions();
+ $filespec = $this->getFileSpec($normalizedKey);
+ $file = $filespec . '.dat';
+
+ $metadata = array(
+ 'filespec' => $filespec,
+ 'mtime' => filemtime($file)
+ );
+
+ if (!$options->getNoCtime()) {
+ $metadata['ctime'] = filectime($file);
+ }
+
+ if (!$options->getNoAtime()) {
+ $metadata['atime'] = fileatime($file);
+ }
+
+ return $metadata;
+ }
+
+ /**
+ * Internal method to get multiple metadata
+ *
+ * @param array $normalizedKeys
+ * @return array Associative array of keys and metadata
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetMetadatas(array & $normalizedKeys)
+ {
+ $options = $this->getOptions();
+ $result = array();
+
+ foreach ($normalizedKeys as $normalizedKey) {
+ $filespec = $this->getFileSpec($normalizedKey);
+ $file = $filespec . '.dat';
+
+ $metadata = array(
+ 'filespec' => $filespec,
+ 'mtime' => filemtime($file),
+ );
+
+ if (!$options->getNoCtime()) {
+ $metadata['ctime'] = filectime($file);
+ }
+
+ if (!$options->getNoAtime()) {
+ $metadata['atime'] = fileatime($file);
+ }
+
+ $result[$normalizedKey] = $metadata;
+ }
+
+ return $result;
+ }
+
+ /* writing */
+
+ /**
+ * Store an item.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers setItem.pre(PreEvent)
+ * @triggers setItem.post(PostEvent)
+ * @triggers setItem.exception(ExceptionEvent)
+ */
+ public function setItem($key, $value)
+ {
+ $options = $this->getOptions();
+ if ($options->getWritable() && $options->getClearStatCache()) {
+ clearstatcache();
+ }
+ return parent::setItem($key, $value);
+ }
+
+ /**
+ * Store multiple items.
+ *
+ * @param array $keyValuePairs
+ * @return array Array of not stored keys
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers setItems.pre(PreEvent)
+ * @triggers setItems.post(PostEvent)
+ * @triggers setItems.exception(ExceptionEvent)
+ */
+ public function setItems(array $keyValuePairs)
+ {
+ $options = $this->getOptions();
+ if ($options->getWritable() && $options->getClearStatCache()) {
+ clearstatcache();
+ }
+
+ return parent::setItems($keyValuePairs);
+ }
+
+ /**
+ * Add an item.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers addItem.pre(PreEvent)
+ * @triggers addItem.post(PostEvent)
+ * @triggers addItem.exception(ExceptionEvent)
+ */
+ public function addItem($key, $value)
+ {
+ $options = $this->getOptions();
+ if ($options->getWritable() && $options->getClearStatCache()) {
+ clearstatcache();
+ }
+
+ return parent::addItem($key, $value);
+ }
+
+ /**
+ * Add multiple items.
+ *
+ * @param array $keyValuePairs
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers addItems.pre(PreEvent)
+ * @triggers addItems.post(PostEvent)
+ * @triggers addItems.exception(ExceptionEvent)
+ */
+ public function addItems(array $keyValuePairs)
+ {
+ $options = $this->getOptions();
+ if ($options->getWritable() && $options->getClearStatCache()) {
+ clearstatcache();
+ }
+
+ return parent::addItems($keyValuePairs);
+ }
+
+ /**
+ * Replace an existing item.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers replaceItem.pre(PreEvent)
+ * @triggers replaceItem.post(PostEvent)
+ * @triggers replaceItem.exception(ExceptionEvent)
+ */
+ public function replaceItem($key, $value)
+ {
+ $options = $this->getOptions();
+ if ($options->getWritable() && $options->getClearStatCache()) {
+ clearstatcache();
+ }
+
+ return parent::replaceItem($key, $value);
+ }
+
+ /**
+ * Replace multiple existing items.
+ *
+ * @param array $keyValuePairs
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers replaceItems.pre(PreEvent)
+ * @triggers replaceItems.post(PostEvent)
+ * @triggers replaceItems.exception(ExceptionEvent)
+ */
+ public function replaceItems(array $keyValuePairs)
+ {
+ $options = $this->getOptions();
+ if ($options->getWritable() && $options->getClearStatCache()) {
+ clearstatcache();
+ }
+
+ return parent::replaceItems($keyValuePairs);
+ }
+
+ /**
+ * Internal method to store an item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalSetItem(& $normalizedKey, & $value)
+ {
+ $filespec = $this->getFileSpec($normalizedKey);
+ $this->prepareDirectoryStructure($filespec);
+
+ // write data in non-blocking mode
+ $wouldblock = null;
+ $this->putFileContent($filespec . '.dat', $value, true, $wouldblock);
+
+ // delete related tag file (if present)
+ $this->unlink($filespec . '.tag');
+
+ // Retry writing data in blocking mode if it was blocked before
+ if ($wouldblock) {
+ $this->putFileContent($filespec . '.dat', $value);
+ }
+
+ return true;
+ }
+
+ /**
+ * Internal method to store multiple items.
+ *
+ * @param array $normalizedKeyValuePairs
+ * @return array Array of not stored keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalSetItems(array & $normalizedKeyValuePairs)
+ {
+ $oldUmask = null;
+
+ // create an associated array of files and contents to write
+ $contents = array();
+ foreach ($normalizedKeyValuePairs as $key => & $value) {
+ $filespec = $this->getFileSpec($key);
+ $this->prepareDirectoryStructure($filespec);
+
+ // *.dat file
+ $contents[$filespec . '.dat'] = & $value;
+
+ // *.tag file
+ $this->unlink($filespec . '.tag');
+ }
+
+ // write to disk
+ while ($contents) {
+ $nonBlocking = count($contents) > 1;
+ $wouldblock = null;
+
+ foreach ($contents as $file => & $content) {
+ $this->putFileContent($file, $content, $nonBlocking, $wouldblock);
+ if (!$nonBlocking || !$wouldblock) {
+ unset($contents[$file]);
+ }
+ }
+ }
+
+ // return OK
+ return array();
+ }
+
+ /**
+ * Set an item only if token matches
+ *
+ * It uses the token received from getItem() to check if the item has
+ * changed before overwriting it.
+ *
+ * @param mixed $token
+ * @param string $key
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ * @see getItem()
+ * @see setItem()
+ */
+ public function checkAndSetItem($token, $key, $value)
+ {
+ $options = $this->getOptions();
+ if ($options->getWritable() && $options->getClearStatCache()) {
+ clearstatcache();
+ }
+
+ return parent::checkAndSetItem($token, $key, $value);
+ }
+
+ /**
+ * Internal method to set an item only if token matches
+ *
+ * @param mixed $token
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ * @see getItem()
+ * @see setItem()
+ */
+ protected function internalCheckAndSetItem(& $token, & $normalizedKey, & $value)
+ {
+ if (!$this->internalHasItem($normalizedKey)) {
+ return false;
+ }
+
+ // use filemtime + filesize as CAS token
+ $file = $this->getFileSpec($normalizedKey) . '.dat';
+ $check = filemtime($file) . filesize($file);
+ if ($token !== $check) {
+ return false;
+ }
+
+ return $this->internalSetItem($normalizedKey, $value);
+ }
+
+ /**
+ * Reset lifetime of an item
+ *
+ * @param string $key
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers touchItem.pre(PreEvent)
+ * @triggers touchItem.post(PostEvent)
+ * @triggers touchItem.exception(ExceptionEvent)
+ */
+ public function touchItem($key)
+ {
+ $options = $this->getOptions();
+ if ($options->getWritable() && $options->getClearStatCache()) {
+ clearstatcache();
+ }
+
+ return parent::touchItem($key);
+ }
+
+ /**
+ * Reset lifetime of multiple items.
+ *
+ * @param array $keys
+ * @return array Array of not updated keys
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers touchItems.pre(PreEvent)
+ * @triggers touchItems.post(PostEvent)
+ * @triggers touchItems.exception(ExceptionEvent)
+ */
+ public function touchItems(array $keys)
+ {
+ $options = $this->getOptions();
+ if ($options->getWritable() && $options->getClearStatCache()) {
+ clearstatcache();
+ }
+
+ return parent::touchItems($keys);
+ }
+
+ /**
+ * Internal method to reset lifetime of an item
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalTouchItem(& $normalizedKey)
+ {
+ if (!$this->internalHasItem($normalizedKey)) {
+ return false;
+ }
+
+ $filespec = $this->getFileSpec($normalizedKey);
+
+ ErrorHandler::start();
+ $touch = touch($filespec . '.dat');
+ $error = ErrorHandler::stop();
+ if (!$touch) {
+ throw new Exception\RuntimeException("Error touching file '{$filespec}.dat'", 0, $error);
+ }
+
+ return true;
+ }
+
+ /**
+ * Remove an item.
+ *
+ * @param string $key
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers removeItem.pre(PreEvent)
+ * @triggers removeItem.post(PostEvent)
+ * @triggers removeItem.exception(ExceptionEvent)
+ */
+ public function removeItem($key)
+ {
+ $options = $this->getOptions();
+ if ($options->getWritable() && $options->getClearStatCache()) {
+ clearstatcache();
+ }
+
+ return parent::removeItem($key);
+ }
+
+ /**
+ * Remove multiple items.
+ *
+ * @param array $keys
+ * @return array Array of not removed keys
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers removeItems.pre(PreEvent)
+ * @triggers removeItems.post(PostEvent)
+ * @triggers removeItems.exception(ExceptionEvent)
+ */
+ public function removeItems(array $keys)
+ {
+ $options = $this->getOptions();
+ if ($options->getWritable() && $options->getClearStatCache()) {
+ clearstatcache();
+ }
+
+ return parent::removeItems($keys);
+ }
+
+ /**
+ * Internal method to remove an item.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalRemoveItem(& $normalizedKey)
+ {
+ $filespec = $this->getFileSpec($normalizedKey);
+ if (!file_exists($filespec . '.dat')) {
+ return false;
+ } else {
+ $this->unlink($filespec . '.dat');
+ $this->unlink($filespec . '.tag');
+ }
+ return true;
+ }
+
+ /* status */
+
+ /**
+ * Internal method to get capabilities of this adapter
+ *
+ * @return Capabilities
+ */
+ protected function internalGetCapabilities()
+ {
+ if ($this->capabilities === null) {
+ $marker = new stdClass();
+ $options = $this->getOptions();
+
+ // detect metadata
+ $metadata = array('mtime', 'filespec');
+ if (!$options->getNoAtime()) {
+ $metadata[] = 'atime';
+ }
+ if (!$options->getNoCtime()) {
+ $metadata[] = 'ctime';
+ }
+
+ $capabilities = new Capabilities(
+ $this,
+ $marker,
+ array(
+ 'supportedDatatypes' => array(
+ 'NULL' => 'string',
+ 'boolean' => 'string',
+ 'integer' => 'string',
+ 'double' => 'string',
+ 'string' => true,
+ 'array' => false,
+ 'object' => false,
+ 'resource' => false,
+ ),
+ 'supportedMetadata' => $metadata,
+ 'minTtl' => 1,
+ 'maxTtl' => 0,
+ 'staticTtl' => false,
+ 'ttlPrecision' => 1,
+ 'expiredRead' => true,
+ 'maxKeyLength' => 251, // 255 - strlen(.dat | .tag)
+ 'namespaceIsPrefix' => true,
+ 'namespaceSeparator' => $options->getNamespaceSeparator(),
+ )
+ );
+
+ // update capabilities on change options
+ $this->getEventManager()->attach('option', function ($event) use ($capabilities, $marker) {
+ $params = $event->getParams();
+
+ if (isset($params['namespace_separator'])) {
+ $capabilities->setNamespaceSeparator($marker, $params['namespace_separator']);
+ }
+
+ if (isset($params['no_atime']) || isset($params['no_ctime'])) {
+ $metadata = $capabilities->getSupportedMetadata();
+
+ if (isset($params['no_atime']) && !$params['no_atime']) {
+ $metadata[] = 'atime';
+ } elseif (isset($params['no_atime']) && ($index = array_search('atime', $metadata)) !== false) {
+ unset($metadata[$index]);
+ }
+
+ if (isset($params['no_ctime']) && !$params['no_ctime']) {
+ $metadata[] = 'ctime';
+ } elseif (isset($params['no_ctime']) && ($index = array_search('ctime', $metadata)) !== false) {
+ unset($metadata[$index]);
+ }
+
+ $capabilities->setSupportedMetadata($marker, $metadata);
+ }
+ });
+
+ $this->capabilityMarker = $marker;
+ $this->capabilities = $capabilities;
+ }
+
+ return $this->capabilities;
+ }
+
+ /* internal */
+
+ /**
+ * Removes directories recursive by namespace
+ *
+ * @param string $dir Directory to delete
+ * @param string $prefix Namespace + Separator
+ * @return bool
+ */
+ protected function rmDir($dir, $prefix)
+ {
+ $glob = glob(
+ $dir . DIRECTORY_SEPARATOR . $prefix . '*',
+ GLOB_ONLYDIR | GLOB_NOESCAPE | GLOB_NOSORT
+ );
+ if (!$glob) {
+ // On some systems glob returns false even on empty result
+ return true;
+ }
+
+ $ret = true;
+ foreach ($glob as $subdir) {
+ // skip removing current directory if removing of sub-directory failed
+ if ($this->rmDir($subdir, $prefix)) {
+ // ignore not empty directories
+ ErrorHandler::start();
+ $ret = rmdir($subdir) && $ret;
+ ErrorHandler::stop();
+ } else {
+ $ret = false;
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Get file spec of the given key and namespace
+ *
+ * @param string $normalizedKey
+ * @return string
+ */
+ protected function getFileSpec($normalizedKey)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $path = $options->getCacheDir() . DIRECTORY_SEPARATOR;
+ $level = $options->getDirLevel();
+
+ $fileSpecId = $path . $prefix . $normalizedKey . '/' . $level;
+ if ($this->lastFileSpecId !== $fileSpecId) {
+ if ($level > 0) {
+ // create up to 256 directories per directory level
+ $hash = md5($normalizedKey);
+ for ($i = 0, $max = ($level * 2); $i < $max; $i+= 2) {
+ $path .= $prefix . $hash[$i] . $hash[$i+1] . DIRECTORY_SEPARATOR;
+ }
+ }
+
+ $this->lastFileSpecId = $fileSpecId;
+ $this->lastFileSpec = $path . $prefix . $normalizedKey;
+ }
+
+ return $this->lastFileSpec;
+ }
+
+ /**
+ * Read info file
+ *
+ * @param string $file
+ * @param bool $nonBlocking Don't block script if file is locked
+ * @param bool $wouldblock The optional argument is set to TRUE if the lock would block
+ * @return array|bool The info array or false if file wasn't found
+ * @throws Exception\RuntimeException
+ */
+ protected function readInfoFile($file, $nonBlocking = false, & $wouldblock = null)
+ {
+ if (!file_exists($file)) {
+ return false;
+ }
+
+ $content = $this->getFileContent($file, $nonBlocking, $wouldblock);
+ if ($nonBlocking && $wouldblock) {
+ return false;
+ }
+
+ ErrorHandler::start();
+ $ifo = unserialize($content);
+ $err = ErrorHandler::stop();
+ if (!is_array($ifo)) {
+ throw new Exception\RuntimeException("Corrupted info file '{$file}'", 0, $err);
+ }
+
+ return $ifo;
+ }
+
+ /**
+ * Read a complete file
+ *
+ * @param string $file File complete path
+ * @param bool $nonBlocking Don't block script if file is locked
+ * @param bool $wouldblock The optional argument is set to TRUE if the lock would block
+ * @return string
+ * @throws Exception\RuntimeException
+ */
+ protected function getFileContent($file, $nonBlocking = false, & $wouldblock = null)
+ {
+ $locking = $this->getOptions()->getFileLocking();
+ $wouldblock = null;
+
+ ErrorHandler::start();
+
+ // if file locking enabled -> file_get_contents can't be used
+ if ($locking) {
+ $fp = fopen($file, 'rb');
+ if ($fp === false) {
+ $err = ErrorHandler::stop();
+ throw new Exception\RuntimeException("Error opening file '{$file}'", 0, $err);
+ }
+
+ if ($nonBlocking) {
+ $lock = flock($fp, LOCK_SH | LOCK_NB, $wouldblock);
+ if ($wouldblock) {
+ fclose($fp);
+ ErrorHandler::stop();
+ return;
+ }
+ } else {
+ $lock = flock($fp, LOCK_SH);
+ }
+
+ if (!$lock) {
+ fclose($fp);
+ $err = ErrorHandler::stop();
+ throw new Exception\RuntimeException("Error locking file '{$file}'", 0, $err);
+ }
+
+ $res = stream_get_contents($fp);
+ if ($res === false) {
+ flock($fp, LOCK_UN);
+ fclose($fp);
+ $err = ErrorHandler::stop();
+ throw new Exception\RuntimeException('Error getting stream contents', 0, $err);
+ }
+
+ flock($fp, LOCK_UN);
+ fclose($fp);
+
+ // if file locking disabled -> file_get_contents can be used
+ } else {
+ $res = file_get_contents($file, false);
+ if ($res === false) {
+ $err = ErrorHandler::stop();
+ throw new Exception\RuntimeException("Error getting file contents for file '{$file}'", 0, $err);
+ }
+ }
+
+ ErrorHandler::stop();
+ return $res;
+ }
+
+ /**
+ * Prepares a directory structure for the given file(spec)
+ * using the configured directory level.
+ *
+ * @param string $file
+ * @return void
+ * @throws Exception\RuntimeException
+ */
+ protected function prepareDirectoryStructure($file)
+ {
+ $options = $this->getOptions();
+ $level = $options->getDirLevel();
+
+ // Directory structure is required only if directory level > 0
+ if (!$level) {
+ return;
+ }
+
+ // Directory structure already exists
+ $pathname = dirname($file);
+ if (file_exists($pathname)) {
+ return;
+ }
+
+ $perm = $options->getDirPermission();
+ $umask = $options->getUmask();
+ if ($umask !== false && $perm !== false) {
+ $perm = $perm & ~$umask;
+ }
+
+ ErrorHandler::start();
+
+ if ($perm === false || $level == 1) {
+ // build-in mkdir function is enough
+
+ $umask = ($umask !== false) ? umask($umask) : false;
+ $res = mkdir($pathname, ($perm !== false) ? $perm : 0777, true);
+
+ if ($umask !== false) {
+ umask($umask);
+ }
+
+ if (!$res) {
+ $err = ErrorHandler::stop();
+
+ // Issue 6435:
+ // mkdir could fail because of a race condition it was already created by another process
+ // after the first file_exists above
+ if (file_exists($pathname)) {
+ return;
+ }
+
+ $oct = ($perm === false) ? '777' : decoct($perm);
+ throw new Exception\RuntimeException("mkdir('{$pathname}', 0{$oct}, true) failed", 0, $err);
+ }
+
+ if ($perm !== false && !chmod($pathname, $perm)) {
+ $oct = decoct($perm);
+ $err = ErrorHandler::stop();
+ throw new Exception\RuntimeException("chmod('{$pathname}', 0{$oct}) failed", 0, $err);
+ }
+ } else {
+ // build-in mkdir function sets permission together with current umask
+ // which doesn't work well on multo threaded webservers
+ // -> create directories one by one and set permissions
+
+ // find existing path and missing path parts
+ $parts = array();
+ $path = $pathname;
+ while (!file_exists($path)) {
+ array_unshift($parts, basename($path));
+ $nextPath = dirname($path);
+ if ($nextPath === $path) {
+ break;
+ }
+ $path = $nextPath;
+ }
+
+ // make all missing path parts
+ foreach ($parts as $part) {
+ $path.= DIRECTORY_SEPARATOR . $part;
+
+ // create a single directory, set and reset umask immediately
+ $umask = ($umask !== false) ? umask($umask) : false;
+ $res = mkdir($path, ($perm === false) ? 0777 : $perm, false);
+ if ($umask !== false) {
+ umask($umask);
+ }
+
+ if (!$res) {
+ // Issue 6435:
+ // mkdir could fail because of a race condition it was already created by another process
+ // after the first file_exists above ... go to the next path part.
+ if (file_exists($path)) {
+ continue;
+ }
+
+ $oct = ($perm === false) ? '777' : decoct($perm);
+ ErrorHandler::stop();
+ throw new Exception\RuntimeException(
+ "mkdir('{$path}', 0{$oct}, false) failed"
+ );
+ }
+
+ if ($perm !== false && !chmod($path, $perm)) {
+ $oct = decoct($perm);
+ ErrorHandler::stop();
+ throw new Exception\RuntimeException(
+ "chmod('{$path}', 0{$oct}) failed"
+ );
+ }
+ }
+ }
+
+ ErrorHandler::stop();
+ }
+
+ /**
+ * Write content to a file
+ *
+ * @param string $file File complete path
+ * @param string $data Data to write
+ * @param bool $nonBlocking Don't block script if file is locked
+ * @param bool $wouldblock The optional argument is set to TRUE if the lock would block
+ * @return void
+ * @throws Exception\RuntimeException
+ */
+ protected function putFileContent($file, $data, $nonBlocking = false, & $wouldblock = null)
+ {
+ $options = $this->getOptions();
+ $locking = $options->getFileLocking();
+ $nonBlocking = $locking && $nonBlocking;
+ $wouldblock = null;
+
+ $umask = $options->getUmask();
+ $perm = $options->getFilePermission();
+ if ($umask !== false && $perm !== false) {
+ $perm = $perm & ~$umask;
+ }
+
+ ErrorHandler::start();
+
+ // if locking and non blocking is enabled -> file_put_contents can't used
+ if ($locking && $nonBlocking) {
+ $umask = ($umask !== false) ? umask($umask) : false;
+
+ $fp = fopen($file, 'cb');
+
+ if ($umask) {
+ umask($umask);
+ }
+
+ if (!$fp) {
+ $err = ErrorHandler::stop();
+ throw new Exception\RuntimeException("Error opening file '{$file}'", 0, $err);
+ }
+
+ if ($perm !== false && !chmod($file, $perm)) {
+ fclose($fp);
+ $oct = decoct($perm);
+ $err = ErrorHandler::stop();
+ throw new Exception\RuntimeException("chmod('{$file}', 0{$oct}) failed", 0, $err);
+ }
+
+ if (!flock($fp, LOCK_EX | LOCK_NB, $wouldblock)) {
+ fclose($fp);
+ $err = ErrorHandler::stop();
+ if ($wouldblock) {
+ return;
+ } else {
+ throw new Exception\RuntimeException("Error locking file '{$file}'", 0, $err);
+ }
+ }
+
+ if (fwrite($fp, $data) === false) {
+ flock($fp, LOCK_UN);
+ fclose($fp);
+ $err = ErrorHandler::stop();
+ throw new Exception\RuntimeException("Error writing file '{$file}'", 0, $err);
+ }
+
+ if (!ftruncate($fp, strlen($data))) {
+ flock($fp, LOCK_UN);
+ fclose($fp);
+ $err = ErrorHandler::stop();
+ throw new Exception\RuntimeException("Error truncating file '{$file}'", 0, $err);
+ }
+
+ flock($fp, LOCK_UN);
+ fclose($fp);
+
+ // else -> file_put_contents can be used
+ } else {
+ $flags = 0;
+ if ($locking) {
+ $flags = $flags | LOCK_EX;
+ }
+
+ $umask = ($umask !== false) ? umask($umask) : false;
+
+ $rs = file_put_contents($file, $data, $flags);
+
+ if ($umask) {
+ umask($umask);
+ }
+
+ if ($rs === false) {
+ $err = ErrorHandler::stop();
+ throw new Exception\RuntimeException("Error writing file '{$file}'", 0, $err);
+ }
+
+ if ($perm !== false && !chmod($file, $perm)) {
+ $oct = decoct($perm);
+ $err = ErrorHandler::stop();
+ throw new Exception\RuntimeException("chmod('{$file}', 0{$oct}) failed", 0, $err);
+ }
+ }
+
+ ErrorHandler::stop();
+ }
+
+ /**
+ * Unlink a file
+ *
+ * @param string $file
+ * @return void
+ * @throws Exception\RuntimeException
+ */
+ protected function unlink($file)
+ {
+ ErrorHandler::start();
+ $res = unlink($file);
+ $err = ErrorHandler::stop();
+
+ // only throw exception if file still exists after deleting
+ if (!$res && file_exists($file)) {
+ throw new Exception\RuntimeException(
+ "Error unlinking file '{$file}'; file still exists",
+ 0,
+ $err
+ );
+ }
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/FilesystemIterator.php b/library/Zend/Cache/Storage/Adapter/FilesystemIterator.php
new file mode 100755
index 0000000000..447cfb3dbf
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/FilesystemIterator.php
@@ -0,0 +1,179 @@
+storage = $storage;
+ $this->globIterator = new GlobIterator($path, GlobIterator::KEY_AS_FILENAME);
+ $this->prefix = $prefix;
+ $this->prefixLength = strlen($prefix);
+ }
+
+ /**
+ * Get storage instance
+ *
+ * @return Filesystem
+ */
+ public function getStorage()
+ {
+ return $this->storage;
+ }
+
+ /**
+ * Get iterator mode
+ *
+ * @return int Value of IteratorInterface::CURRENT_AS_*
+ */
+ public function getMode()
+ {
+ return $this->mode;
+ }
+
+ /**
+ * Set iterator mode
+ *
+ * @param int $mode
+ * @return FilesystemIterator Fluent interface
+ */
+ public function setMode($mode)
+ {
+ $this->mode = (int) $mode;
+ return $this;
+ }
+
+ /* Iterator */
+
+ /**
+ * Get current key, value or metadata.
+ *
+ * @return mixed
+ */
+ public function current()
+ {
+ if ($this->mode == IteratorInterface::CURRENT_AS_SELF) {
+ return $this;
+ }
+
+ $key = $this->key();
+
+ if ($this->mode == IteratorInterface::CURRENT_AS_VALUE) {
+ return $this->storage->getItem($key);
+ } elseif ($this->mode == IteratorInterface::CURRENT_AS_METADATA) {
+ return $this->storage->getMetadata($key);
+ }
+
+ return $key;
+ }
+
+ /**
+ * Get current key
+ *
+ * @return string
+ */
+ public function key()
+ {
+ $filename = $this->globIterator->key();
+
+ // return without namespace prefix and file suffix
+ return substr($filename, $this->prefixLength, -4);
+ }
+
+ /**
+ * Move forward to next element
+ *
+ * @return void
+ */
+ public function next()
+ {
+ $this->globIterator->next();
+ }
+
+ /**
+ * Checks if current position is valid
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ try {
+ return $this->globIterator->valid();
+ } catch (\LogicException $e) {
+ // @link https://bugs.php.net/bug.php?id=55701
+ // GlobIterator throws LogicException with message
+ // 'The parent constructor was not called: the object is in an invalid state'
+ return false;
+ }
+ }
+
+ /**
+ * Rewind the Iterator to the first element.
+ *
+ * @return bool false if the operation failed.
+ */
+ public function rewind()
+ {
+ try {
+ return $this->globIterator->rewind();
+ } catch (\LogicException $e) {
+ // @link https://bugs.php.net/bug.php?id=55701
+ // GlobIterator throws LogicException with message
+ // 'The parent constructor was not called: the object is in an invalid state'
+ return false;
+ }
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/FilesystemOptions.php b/library/Zend/Cache/Storage/Adapter/FilesystemOptions.php
new file mode 100755
index 0000000000..5eabbdf3f3
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/FilesystemOptions.php
@@ -0,0 +1,457 @@
+filePermission = false;
+ $this->dirPermission = false;
+ }
+
+ parent::__construct($options);
+ }
+
+ /**
+ * Set cache dir
+ *
+ * @param string $cacheDir
+ * @return FilesystemOptions
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setCacheDir($cacheDir)
+ {
+ if ($cacheDir !== null) {
+ if (!is_dir($cacheDir)) {
+ throw new Exception\InvalidArgumentException(
+ "Cache directory '{$cacheDir}' not found or not a directory"
+ );
+ } elseif (!is_writable($cacheDir)) {
+ throw new Exception\InvalidArgumentException(
+ "Cache directory '{$cacheDir}' not writable"
+ );
+ } elseif (!is_readable($cacheDir)) {
+ throw new Exception\InvalidArgumentException(
+ "Cache directory '{$cacheDir}' not readable"
+ );
+ }
+
+ $cacheDir = rtrim(realpath($cacheDir), DIRECTORY_SEPARATOR);
+ } else {
+ $cacheDir = sys_get_temp_dir();
+ }
+
+ $this->triggerOptionEvent('cache_dir', $cacheDir);
+ $this->cacheDir = $cacheDir;
+ return $this;
+ }
+
+ /**
+ * Get cache dir
+ *
+ * @return null|string
+ */
+ public function getCacheDir()
+ {
+ if ($this->cacheDir === null) {
+ $this->setCacheDir(null);
+ }
+
+ return $this->cacheDir;
+ }
+
+ /**
+ * Set clear stat cache
+ *
+ * @param bool $clearStatCache
+ * @return FilesystemOptions
+ */
+ public function setClearStatCache($clearStatCache)
+ {
+ $clearStatCache = (bool) $clearStatCache;
+ $this->triggerOptionEvent('clear_stat_cache', $clearStatCache);
+ $this->clearStatCache = $clearStatCache;
+ return $this;
+ }
+
+ /**
+ * Get clear stat cache
+ *
+ * @return bool
+ */
+ public function getClearStatCache()
+ {
+ return $this->clearStatCache;
+ }
+
+ /**
+ * Set dir level
+ *
+ * @param int $dirLevel
+ * @return FilesystemOptions
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setDirLevel($dirLevel)
+ {
+ $dirLevel = (int) $dirLevel;
+ if ($dirLevel < 0 || $dirLevel > 16) {
+ throw new Exception\InvalidArgumentException(
+ "Directory level '{$dirLevel}' must be between 0 and 16"
+ );
+ }
+ $this->triggerOptionEvent('dir_level', $dirLevel);
+ $this->dirLevel = $dirLevel;
+ return $this;
+ }
+
+ /**
+ * Get dir level
+ *
+ * @return int
+ */
+ public function getDirLevel()
+ {
+ return $this->dirLevel;
+ }
+
+ /**
+ * Set permission to create directories on unix systems
+ *
+ * @param false|string|int $dirPermission FALSE to disable explicit permission or an octal number
+ * @return FilesystemOptions
+ * @see setUmask
+ * @see setFilePermission
+ * @link http://php.net/manual/function.chmod.php
+ */
+ public function setDirPermission($dirPermission)
+ {
+ if ($dirPermission !== false) {
+ if (is_string($dirPermission)) {
+ $dirPermission = octdec($dirPermission);
+ } else {
+ $dirPermission = (int) $dirPermission;
+ }
+
+ // validate
+ if (($dirPermission & 0700) != 0700) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid directory permission: need permission to execute, read and write by owner'
+ );
+ }
+ }
+
+ if ($this->dirPermission !== $dirPermission) {
+ $this->triggerOptionEvent('dir_permission', $dirPermission);
+ $this->dirPermission = $dirPermission;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get permission to create directories on unix systems
+ *
+ * @return false|int
+ */
+ public function getDirPermission()
+ {
+ return $this->dirPermission;
+ }
+
+ /**
+ * Set file locking
+ *
+ * @param bool $fileLocking
+ * @return FilesystemOptions
+ */
+ public function setFileLocking($fileLocking)
+ {
+ $fileLocking = (bool) $fileLocking;
+ $this->triggerOptionEvent('file_locking', $fileLocking);
+ $this->fileLocking = $fileLocking;
+ return $this;
+ }
+
+ /**
+ * Get file locking
+ *
+ * @return bool
+ */
+ public function getFileLocking()
+ {
+ return $this->fileLocking;
+ }
+
+ /**
+ * Set permission to create files on unix systems
+ *
+ * @param false|string|int $filePermission FALSE to disable explicit permission or an octal number
+ * @return FilesystemOptions
+ * @see setUmask
+ * @see setDirPermission
+ * @link http://php.net/manual/function.chmod.php
+ */
+ public function setFilePermission($filePermission)
+ {
+ if ($filePermission !== false) {
+ if (is_string($filePermission)) {
+ $filePermission = octdec($filePermission);
+ } else {
+ $filePermission = (int) $filePermission;
+ }
+
+ // validate
+ if (($filePermission & 0600) != 0600) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid file permission: need permission to read and write by owner'
+ );
+ } elseif ($filePermission & 0111) {
+ throw new Exception\InvalidArgumentException(
+ "Invalid file permission: Cache files shoudn't be executable"
+ );
+ }
+ }
+
+ if ($this->filePermission !== $filePermission) {
+ $this->triggerOptionEvent('file_permission', $filePermission);
+ $this->filePermission = $filePermission;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get permission to create files on unix systems
+ *
+ * @return false|int
+ */
+ public function getFilePermission()
+ {
+ return $this->filePermission;
+ }
+
+ /**
+ * Set namespace separator
+ *
+ * @param string $namespaceSeparator
+ * @return FilesystemOptions
+ */
+ public function setNamespaceSeparator($namespaceSeparator)
+ {
+ $namespaceSeparator = (string) $namespaceSeparator;
+ $this->triggerOptionEvent('namespace_separator', $namespaceSeparator);
+ $this->namespaceSeparator = $namespaceSeparator;
+ return $this;
+ }
+
+ /**
+ * Get namespace separator
+ *
+ * @return string
+ */
+ public function getNamespaceSeparator()
+ {
+ return $this->namespaceSeparator;
+ }
+
+ /**
+ * Set no atime
+ *
+ * @param bool $noAtime
+ * @return FilesystemOptions
+ */
+ public function setNoAtime($noAtime)
+ {
+ $noAtime = (bool) $noAtime;
+ $this->triggerOptionEvent('no_atime', $noAtime);
+ $this->noAtime = $noAtime;
+ return $this;
+ }
+
+ /**
+ * Get no atime
+ *
+ * @return bool
+ */
+ public function getNoAtime()
+ {
+ return $this->noAtime;
+ }
+
+ /**
+ * Set no ctime
+ *
+ * @param bool $noCtime
+ * @return FilesystemOptions
+ */
+ public function setNoCtime($noCtime)
+ {
+ $noCtime = (bool) $noCtime;
+ $this->triggerOptionEvent('no_ctime', $noCtime);
+ $this->noCtime = $noCtime;
+ return $this;
+ }
+
+ /**
+ * Get no ctime
+ *
+ * @return bool
+ */
+ public function getNoCtime()
+ {
+ return $this->noCtime;
+ }
+
+ /**
+ * Set the umask to create files and directories on unix systems
+ *
+ * Note: On multithreaded webservers it's better to explicit set file and dir permission.
+ *
+ * @param false|string|int $umask FALSE to disable umask or an octal number
+ * @return FilesystemOptions
+ * @see setFilePermission
+ * @see setDirPermission
+ * @link http://php.net/manual/function.umask.php
+ * @link http://en.wikipedia.org/wiki/Umask
+ */
+ public function setUmask($umask)
+ {
+ if ($umask !== false) {
+ if (is_string($umask)) {
+ $umask = octdec($umask);
+ } else {
+ $umask = (int) $umask;
+ }
+
+ // validate
+ if ($umask & 0700) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid umask: need permission to execute, read and write by owner'
+ );
+ }
+
+ // normalize
+ $umask = $umask & 0777;
+ }
+
+ if ($this->umask !== $umask) {
+ $this->triggerOptionEvent('umask', $umask);
+ $this->umask = $umask;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the umask to create files and directories on unix systems
+ *
+ * @return false|int
+ */
+ public function getUmask()
+ {
+ return $this->umask;
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/KeyListIterator.php b/library/Zend/Cache/Storage/Adapter/KeyListIterator.php
new file mode 100755
index 0000000000..86bcbe1609
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/KeyListIterator.php
@@ -0,0 +1,169 @@
+storage = $storage;
+ $this->keys = $keys;
+ $this->count = count($keys);
+ }
+
+ /**
+ * Get storage instance
+ *
+ * @return StorageInterface
+ */
+ public function getStorage()
+ {
+ return $this->storage;
+ }
+
+ /**
+ * Get iterator mode
+ *
+ * @return int Value of IteratorInterface::CURRENT_AS_*
+ */
+ public function getMode()
+ {
+ return $this->mode;
+ }
+
+ /**
+ * Set iterator mode
+ *
+ * @param int $mode
+ * @return KeyListIterator Fluent interface
+ */
+ public function setMode($mode)
+ {
+ $this->mode = (int) $mode;
+ return $this;
+ }
+
+ /**
+ * Get current key, value or metadata.
+ *
+ * @return mixed
+ */
+ public function current()
+ {
+ if ($this->mode == IteratorInterface::CURRENT_AS_SELF) {
+ return $this;
+ }
+
+ $key = $this->key();
+
+ if ($this->mode == IteratorInterface::CURRENT_AS_METADATA) {
+ return $this->storage->getMetadata($key);
+ } elseif ($this->mode == IteratorInterface::CURRENT_AS_VALUE) {
+ return $this->storage->getItem($key);
+ }
+
+ return $key;
+ }
+
+ /**
+ * Get current key
+ *
+ * @return string
+ */
+ public function key()
+ {
+ return $this->keys[$this->position];
+ }
+
+ /**
+ * Checks if current position is valid
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return $this->position < $this->count;
+ }
+
+ /**
+ * Move forward to next element
+ *
+ * @return void
+ */
+ public function next()
+ {
+ $this->position++;
+ }
+
+ /**
+ * Rewind the Iterator to the first element.
+ *
+ * @return void
+ */
+ public function rewind()
+ {
+ $this->position = 0;
+ }
+
+ /**
+ * Count number of items
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return $this->count;
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/Memcache.php b/library/Zend/Cache/Storage/Adapter/Memcache.php
new file mode 100755
index 0000000000..953e52cdac
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/Memcache.php
@@ -0,0 +1,574 @@
+ 0) {
+ throw new Exception\ExtensionNotLoadedException("Missing ext/memcache version >= 2.0.0");
+ }
+
+ parent::__construct($options);
+
+ // reset initialized flag on update option(s)
+ $initialized = & $this->initialized;
+ $this->getEventManager()->attach('option', function ($event) use (& $initialized) {
+ $initialized = false;
+ });
+ }
+
+ /**
+ * Initialize the internal memcache resource
+ *
+ * @return MemcacheResource
+ */
+ protected function getMemcacheResource()
+ {
+ if ($this->initialized) {
+ return $this->resourceManager->getResource($this->resourceId);
+ }
+
+ $options = $this->getOptions();
+
+ // get resource manager and resource id
+ $this->resourceManager = $options->getResourceManager();
+ $this->resourceId = $options->getResourceId();
+
+ // init namespace prefix
+ $this->namespacePrefix = '';
+ $namespace = $options->getNamespace();
+ if ($namespace !== '') {
+ $this->namespacePrefix = $namespace . $options->getNamespaceSeparator();
+ }
+
+ // update initialized flag
+ $this->initialized = true;
+
+ return $this->resourceManager->getResource($this->resourceId);
+ }
+
+ /* options */
+
+ /**
+ * Set options.
+ *
+ * @param array|Traversable|MemcacheOptions $options
+ * @return Memcache
+ * @see getOptions()
+ */
+ public function setOptions($options)
+ {
+ if (!$options instanceof MemcacheOptions) {
+ $options = new MemcacheOptions($options);
+ }
+
+ return parent::setOptions($options);
+ }
+
+ /**
+ * Get options.
+ *
+ * @return MemcacheOptions
+ * @see setOptions()
+ */
+ public function getOptions()
+ {
+ if (!$this->options) {
+ $this->setOptions(new MemcacheOptions());
+ }
+ return $this->options;
+ }
+
+ /**
+ * @param mixed $value
+ * @return int
+ */
+ protected function getWriteFlag(& $value)
+ {
+ if (!$this->getOptions()->getCompression()) {
+ return 0;
+ }
+ // Don't compress numeric or boolean types
+ return (is_bool($value) || is_int($value) || is_float($value)) ? 0 : MEMCACHE_COMPRESSED;
+ }
+
+ /* FlushableInterface */
+
+ /**
+ * Flush the whole storage
+ *
+ * @return bool
+ */
+ public function flush()
+ {
+ $memc = $this->getMemcacheResource();
+ if (!$memc->flush()) {
+ return new Exception\RuntimeException("Memcache flush failed");
+ }
+ return true;
+ }
+
+ /* TotalSpaceCapableInterface */
+
+ /**
+ * Get total space in bytes
+ *
+ * @return int|float
+ */
+ public function getTotalSpace()
+ {
+ $memc = $this->getMemcacheResource();
+ $stats = $memc->getExtendedStats();
+ if ($stats === false) {
+ return new Exception\RuntimeException("Memcache getStats failed");
+ }
+
+ $mem = array_pop($stats);
+ return $mem['limit_maxbytes'];
+ }
+
+ /* AvailableSpaceCapableInterface */
+
+ /**
+ * Get available space in bytes
+ *
+ * @return int|float
+ */
+ public function getAvailableSpace()
+ {
+ $memc = $this->getMemcacheResource();
+ $stats = $memc->getExtendedStats();
+ if ($stats === false) {
+ throw new Exception\RuntimeException('Memcache getStats failed');
+ }
+
+ $mem = array_pop($stats);
+ return $mem['limit_maxbytes'] - $mem['bytes'];
+ }
+
+ /* reading */
+
+ /**
+ * Internal method to get an item.
+ *
+ * @param string $normalizedKey
+ * @param bool $success
+ * @param mixed $casToken
+ * @return mixed Data on success, null on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetItem(& $normalizedKey, & $success = null, & $casToken = null)
+ {
+ $memc = $this->getMemcacheResource();
+ $internalKey = $this->namespacePrefix . $normalizedKey;
+
+ $result = $memc->get($internalKey);
+ $success = ($result !== false);
+ if ($result === false) {
+ return null;
+ }
+
+ $casToken = $result;
+ return $result;
+ }
+
+ /**
+ * Internal method to get multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Associative array of keys and values
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetItems(array & $normalizedKeys)
+ {
+ $memc = $this->getMemcacheResource();
+
+ foreach ($normalizedKeys as & $normalizedKey) {
+ $normalizedKey = $this->namespacePrefix . $normalizedKey;
+ }
+
+ $result = $memc->get($normalizedKeys);
+ if ($result === false) {
+ return array();
+ }
+
+ // remove namespace prefix from result
+ if ($this->namespacePrefix !== '') {
+ $tmp = array();
+ $nsPrefixLength = strlen($this->namespacePrefix);
+ foreach ($result as $internalKey => & $value) {
+ $tmp[substr($internalKey, $nsPrefixLength)] = & $value;
+ }
+ $result = $tmp;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to test if an item exists.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalHasItem(& $normalizedKey)
+ {
+ $memc = $this->getMemcacheResource();
+ $value = $memc->get($this->namespacePrefix . $normalizedKey);
+ return ($value !== false);
+ }
+
+ /**
+ * Internal method to test multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Array of found keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalHasItems(array & $normalizedKeys)
+ {
+ $memc = $this->getMemcacheResource();
+
+ foreach ($normalizedKeys as & $normalizedKey) {
+ $normalizedKey = $this->namespacePrefix . $normalizedKey;
+ }
+
+ $result = $memc->get($normalizedKeys);
+ if ($result === false) {
+ return array();
+ }
+
+ // Convert to a single list
+ $result = array_keys($result);
+
+ // remove namespace prefix
+ if ($result && $this->namespacePrefix !== '') {
+ $nsPrefixLength = strlen($this->namespacePrefix);
+ foreach ($result as & $internalKey) {
+ $internalKey = substr($internalKey, $nsPrefixLength);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get metadata of multiple items
+ *
+ * @param array $normalizedKeys
+ * @return array Associative array of keys and metadata
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetMetadatas(array & $normalizedKeys)
+ {
+ $memc = $this->getMemcacheResource();
+
+ foreach ($normalizedKeys as & $normalizedKey) {
+ $normalizedKey = $this->namespacePrefix . $normalizedKey;
+ }
+
+ $result = $memc->get($normalizedKeys);
+ if ($result === false) {
+ return array();
+ }
+
+ // remove namespace prefix and use an empty array as metadata
+ if ($this->namespacePrefix === '') {
+ foreach ($result as & $value) {
+ $value = array();
+ }
+ return $result;
+ }
+
+ $final = array();
+ $nsPrefixLength = strlen($this->namespacePrefix);
+ foreach (array_keys($result) as $internalKey) {
+ $final[substr($internalKey, $nsPrefixLength)] = array();
+ }
+ return $final;
+ }
+
+ /* writing */
+
+ /**
+ * Internal method to store an item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalSetItem(& $normalizedKey, & $value)
+ {
+ $memc = $this->getMemcacheResource();
+ $expiration = $this->expirationTime();
+ $flag = $this->getWriteFlag($value);
+
+ if (!$memc->set($this->namespacePrefix . $normalizedKey, $value, $flag, $expiration)) {
+ throw new Exception\RuntimeException('Memcache set value failed');
+ }
+
+ return true;
+ }
+
+ /**
+ * Add an item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalAddItem(& $normalizedKey, & $value)
+ {
+ $memc = $this->getMemcacheResource();
+ $expiration = $this->expirationTime();
+ $flag = $this->getWriteFlag($value);
+
+ return $memc->add($this->namespacePrefix . $normalizedKey, $value, $flag, $expiration);
+ }
+
+ /**
+ * Internal method to replace an existing item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalReplaceItem(& $normalizedKey, & $value)
+ {
+ $memc = $this->getMemcacheResource();
+ $expiration = $this->expirationTime();
+ $flag = $this->getWriteFlag($value);
+
+ return $memc->replace($this->namespacePrefix . $normalizedKey, $value, $flag, $expiration);
+ }
+
+ /**
+ * Internal method to remove an item.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalRemoveItem(& $normalizedKey)
+ {
+ $memc = $this->getMemcacheResource();
+ // Delete's second parameter (timeout) is deprecated and not supported.
+ // Values other than 0 may cause delete to fail.
+ // http://www.php.net/manual/memcache.delete.php
+ return $memc->delete($this->namespacePrefix . $normalizedKey, 0);
+ }
+
+ /**
+ * Internal method to increment an item.
+ *
+ * @param string $normalizedKey
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalIncrementItem(& $normalizedKey, & $value)
+ {
+ $memc = $this->getMemcacheResource();
+ $internalKey = $this->namespacePrefix . $normalizedKey;
+ $value = (int) $value;
+ $newValue = $memc->increment($internalKey, $value);
+
+ if ($newValue !== false) {
+ return $newValue;
+ }
+
+ // Set initial value. Don't use compression!
+ // http://www.php.net/manual/memcache.increment.php
+ $newValue = $value;
+ if (!$memc->add($internalKey, $newValue, 0, $this->expirationTime())) {
+ throw new Exception\RuntimeException('Memcache unable to add increment value');
+ }
+
+ return $newValue;
+ }
+
+ /**
+ * Internal method to decrement an item.
+ *
+ * @param string $normalizedKey
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalDecrementItem(& $normalizedKey, & $value)
+ {
+ $memc = $this->getMemcacheResource();
+ $internalKey = $this->namespacePrefix . $normalizedKey;
+ $value = (int) $value;
+ $newValue = $memc->decrement($internalKey, $value);
+
+ if ($newValue !== false) {
+ return $newValue;
+ }
+
+ // Set initial value. Don't use compression!
+ // http://www.php.net/manual/memcache.decrement.php
+ $newValue = -$value;
+ if (!$memc->add($internalKey, $newValue, 0, $this->expirationTime())) {
+ throw new Exception\RuntimeException('Memcache unable to add decrement value');
+ }
+
+ return $newValue;
+ }
+
+ /* status */
+
+ /**
+ * Internal method to get capabilities of this adapter
+ *
+ * @return Capabilities
+ */
+ protected function internalGetCapabilities()
+ {
+ if ($this->capabilities !== null) {
+ return $this->capabilities;
+ }
+
+ if (version_compare('3.0.3', phpversion('memcache')) <= 0) {
+ // In ext/memcache v3.0.3:
+ // Scalar data types (int, bool, double) are preserved by get/set.
+ // http://pecl.php.net/package/memcache/3.0.3
+ //
+ // This effectively removes support for `boolean` types since
+ // "not found" return values are === false.
+ $supportedDatatypes = array(
+ 'NULL' => true,
+ 'boolean' => false,
+ 'integer' => true,
+ 'double' => true,
+ 'string' => true,
+ 'array' => true,
+ 'object' => 'object',
+ 'resource' => false,
+ );
+ } else {
+ // In stable 2.x ext/memcache versions, scalar data types are
+ // converted to strings and must be manually cast back to original
+ // types by the user.
+ //
+ // ie. It is impossible to know if the saved value: (string)"1"
+ // was previously: (bool)true, (int)1, or (string)"1".
+ // Similarly, the saved value: (string)""
+ // might have previously been: (bool)false or (string)""
+ $supportedDatatypes = array(
+ 'NULL' => true,
+ 'boolean' => 'boolean',
+ 'integer' => 'integer',
+ 'double' => 'double',
+ 'string' => true,
+ 'array' => true,
+ 'object' => 'object',
+ 'resource' => false,
+ );
+ }
+
+ $this->capabilityMarker = new stdClass();
+ $this->capabilities = new Capabilities(
+ $this,
+ $this->capabilityMarker,
+ array(
+ 'supportedDatatypes' => $supportedDatatypes,
+ 'supportedMetadata' => array(),
+ 'minTtl' => 1,
+ 'maxTtl' => 0,
+ 'staticTtl' => true,
+ 'ttlPrecision' => 1,
+ 'useRequestTime' => false,
+ 'expiredRead' => false,
+ 'maxKeyLength' => 255,
+ 'namespaceIsPrefix' => true,
+ )
+ );
+
+ return $this->capabilities;
+ }
+
+ /* internal */
+
+ /**
+ * Get expiration time by ttl
+ *
+ * Some storage commands involve sending an expiration value (relative to
+ * an item or to an operation requested by the client) to the server. In
+ * all such cases, the actual value sent may either be Unix time (number of
+ * seconds since January 1, 1970, as an integer), or a number of seconds
+ * starting from current time. In the latter case, this number of seconds
+ * may not exceed 60*60*24*30 (number of seconds in 30 days); if the
+ * expiration value is larger than that, the server will consider it to be
+ * real Unix time value rather than an offset from current time.
+ *
+ * @return int
+ */
+ protected function expirationTime()
+ {
+ $ttl = $this->getOptions()->getTtl();
+ if ($ttl > 2592000) {
+ return time() + $ttl;
+ }
+ return $ttl;
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/MemcacheOptions.php b/library/Zend/Cache/Storage/Adapter/MemcacheOptions.php
new file mode 100755
index 0000000000..5fcdc3427b
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/MemcacheOptions.php
@@ -0,0 +1,284 @@
+namespaceSeparator !== $namespaceSeparator) {
+ $this->triggerOptionEvent('namespace_separator', $namespaceSeparator);
+ $this->namespaceSeparator = $namespaceSeparator;
+ }
+ return $this;
+ }
+
+ /**
+ * Get namespace separator
+ *
+ * @return string
+ */
+ public function getNamespaceSeparator()
+ {
+ return $this->namespaceSeparator;
+ }
+
+ /**
+ * Set the memcache resource manager to use
+ *
+ * @param null|MemcacheResourceManager $resourceManager
+ * @return MemcacheOptions
+ */
+ public function setResourceManager(MemcacheResourceManager $resourceManager = null)
+ {
+ if ($this->resourceManager !== $resourceManager) {
+ $this->triggerOptionEvent('resource_manager', $resourceManager);
+ $this->resourceManager = $resourceManager;
+ }
+ return $this;
+ }
+
+ /**
+ * Get the memcache resource manager
+ *
+ * @return MemcacheResourceManager
+ */
+ public function getResourceManager()
+ {
+ if (!$this->resourceManager) {
+ $this->resourceManager = new MemcacheResourceManager();
+ }
+ return $this->resourceManager;
+ }
+
+ /**
+ * Get the memcache resource id
+ *
+ * @return string
+ */
+ public function getResourceId()
+ {
+ return $this->resourceId;
+ }
+
+ /**
+ * Set the memcache resource id
+ *
+ * @param string $resourceId
+ * @return MemcacheOptions
+ */
+ public function setResourceId($resourceId)
+ {
+ $resourceId = (string) $resourceId;
+ if ($this->resourceId !== $resourceId) {
+ $this->triggerOptionEvent('resource_id', $resourceId);
+ $this->resourceId = $resourceId;
+ }
+ return $this;
+ }
+
+ /**
+ * Is compressed writes turned on?
+ *
+ * @return boolean
+ */
+ public function getCompression()
+ {
+ return $this->compression;
+ }
+
+ /**
+ * Set whether compressed writes are turned on or not
+ *
+ * @param boolean $compression
+ * @return $this
+ */
+ public function setCompression($compression)
+ {
+ $compression = (bool) $compression;
+ if ($this->compression !== $compression) {
+ $this->triggerOptionEvent('compression', $compression);
+ $this->compression = $compression;
+ }
+ return $this;
+ }
+
+ /**
+ * Sets a list of memcache servers to add on initialize
+ *
+ * @param string|array $servers list of servers
+ * @return MemcacheOptions
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setServers($servers)
+ {
+ $this->getResourceManager()->addServers($this->getResourceId(), $servers);
+ return $this;
+ }
+
+ /**
+ * Get Servers
+ *
+ * @return array
+ */
+ public function getServers()
+ {
+ return $this->getResourceManager()->getServers($this->getResourceId());
+ }
+
+ /**
+ * Set compress threshold
+ *
+ * @param int|string|array|\ArrayAccess|null $threshold
+ * @return MemcacheOptions
+ */
+ public function setAutoCompressThreshold($threshold)
+ {
+ $this->getResourceManager()->setAutoCompressThreshold($this->getResourceId(), $threshold);
+ return $this;
+ }
+
+ /**
+ * Get compress threshold
+ *
+ * @return int|null
+ */
+ public function getAutoCompressThreshold()
+ {
+ return $this->getResourceManager()->getAutoCompressThreshold($this->getResourceId());
+ }
+
+ /**
+ * Set compress min savings option
+ *
+ * @param float|string|null $minSavings
+ * @return MemcacheOptions
+ */
+ public function setAutoCompressMinSavings($minSavings)
+ {
+ $this->getResourceManager()->setAutoCompressMinSavings($this->getResourceId(), $minSavings);
+ return $this;
+ }
+
+ /**
+ * Get compress min savings
+ *
+ * @return Exception\RuntimeException
+ */
+ public function getAutoCompressMinSavings()
+ {
+ return $this->getResourceManager()->getAutoCompressMinSavings($this->getResourceId());
+ }
+
+ /**
+ * Set default server values
+ *
+ * @param array $serverDefaults
+ * @return MemcacheOptions
+ */
+ public function setServerDefaults(array $serverDefaults)
+ {
+ $this->getResourceManager()->setServerDefaults($this->getResourceId(), $serverDefaults);
+ return $this;
+ }
+
+ /**
+ * Get default server values
+ *
+ * @return array
+ */
+ public function getServerDefaults()
+ {
+ return $this->getResourceManager()->getServerDefaults($this->getResourceId());
+ }
+
+ /**
+ * Set callback for server connection failures
+ *
+ * @param callable $callback
+ * @return $this
+ */
+ public function setFailureCallback($callback)
+ {
+ $this->getResourceManager()->setFailureCallback($this->getResourceId(), $callback);
+ return $this;
+ }
+
+ /**
+ * Get callback for server connection failures
+ *
+ * @return callable
+ */
+ public function getFailureCallback()
+ {
+ return $this->getResourceManager()->getFailureCallback($this->getResourceId());
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/MemcacheResourceManager.php b/library/Zend/Cache/Storage/Adapter/MemcacheResourceManager.php
new file mode 100755
index 0000000000..1fbff1828d
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/MemcacheResourceManager.php
@@ -0,0 +1,646 @@
+resources[$id]);
+ }
+
+ /**
+ * Gets a memcache resource
+ *
+ * @param string $id
+ * @return MemcacheResource
+ * @throws Exception\RuntimeException
+ */
+ public function getResource($id)
+ {
+ if (!$this->hasResource($id)) {
+ throw new Exception\RuntimeException("No resource with id '{$id}'");
+ }
+
+ $resource = $this->resources[$id];
+ if ($resource instanceof MemcacheResource) {
+ return $resource;
+ }
+
+ $memc = new MemcacheResource();
+ $this->setResourceAutoCompressThreshold(
+ $memc,
+ $resource['auto_compress_threshold'],
+ $resource['auto_compress_min_savings']
+ );
+ foreach ($resource['servers'] as $server) {
+ $this->addServerToResource(
+ $memc,
+ $server,
+ $this->serverDefaults[$id],
+ $this->failureCallbacks[$id]
+ );
+ }
+
+ // buffer and return
+ $this->resources[$id] = $memc;
+ return $memc;
+ }
+
+ /**
+ * Set a resource
+ *
+ * @param string $id
+ * @param array|Traversable|MemcacheResource $resource
+ * @return MemcacheResourceManager
+ */
+ public function setResource($id, $resource, $failureCallback = null, $serverDefaults = array())
+ {
+ $id = (string) $id;
+
+ if ($serverDefaults instanceof Traversable) {
+ $serverDefaults = ArrayUtils::iteratorToArray($serverDefaults);
+ } elseif (!is_array($serverDefaults)) {
+ throw new Exception\InvalidArgumentException(
+ 'ServerDefaults must be an instance Traversable or an array'
+ );
+ }
+
+ if (!($resource instanceof MemcacheResource)) {
+ if ($resource instanceof Traversable) {
+ $resource = ArrayUtils::iteratorToArray($resource);
+ } elseif (!is_array($resource)) {
+ throw new Exception\InvalidArgumentException(
+ 'Resource must be an instance of Memcache or an array or Traversable'
+ );
+ }
+
+ if (isset($resource['server_defaults'])) {
+ $serverDefaults = array_merge($serverDefaults, $resource['server_defaults']);
+ unset($resource['server_defaults']);
+ }
+
+ $resourceOptions = array(
+ 'servers' => array(),
+ 'auto_compress_threshold' => null,
+ 'auto_compress_min_savings' => null,
+ );
+ $resource = array_merge($resourceOptions, $resource);
+
+ // normalize and validate params
+ $this->normalizeAutoCompressThreshold(
+ $resource['auto_compress_threshold'],
+ $resource['auto_compress_min_savings']
+ );
+ $this->normalizeServers($resource['servers']);
+ }
+
+ $this->normalizeServerDefaults($serverDefaults);
+
+ $this->resources[$id] = $resource;
+ $this->failureCallbacks[$id] = $failureCallback;
+ $this->serverDefaults[$id] = $serverDefaults;
+
+ return $this;
+ }
+
+ /**
+ * Remove a resource
+ *
+ * @param string $id
+ * @return MemcacheResourceManager
+ */
+ public function removeResource($id)
+ {
+ unset($this->resources[$id]);
+ return $this;
+ }
+
+ /**
+ * Normalize compress threshold options
+ *
+ * @param int|string|array|ArrayAccess $threshold
+ * @param float|string $minSavings
+ */
+ protected function normalizeAutoCompressThreshold(& $threshold, & $minSavings)
+ {
+ if (is_array($threshold) || ($threshold instanceof ArrayAccess)) {
+ $tmpThreshold = (isset($threshold['threshold'])) ? $threshold['threshold'] : null;
+ $minSavings = (isset($threshold['min_savings'])) ? $threshold['min_savings'] : $minSavings;
+ $threshold = $tmpThreshold;
+ }
+ if (isset($threshold)) {
+ $threshold = (int) $threshold;
+ }
+ if (isset($minSavings)) {
+ $minSavings = (float) $minSavings;
+ }
+ }
+
+ /**
+ * Set compress threshold on a Memcache resource
+ *
+ * @param MemcacheResource $resource
+ * @param array $libOptions
+ */
+ protected function setResourceAutoCompressThreshold(MemcacheResource $resource, $threshold, $minSavings)
+ {
+ if (!isset($threshold)) {
+ return;
+ }
+ if (isset($minSavings)) {
+ $resource->setCompressThreshold($threshold, $minSavings);
+ } else {
+ $resource->setCompressThreshold($threshold);
+ }
+ }
+
+ /**
+ * Get compress threshold
+ *
+ * @param string $id
+ * @return int|null
+ * @throws \Zend\Cache\Exception\RuntimeException
+ */
+ public function getAutoCompressThreshold($id)
+ {
+ if (!$this->hasResource($id)) {
+ throw new Exception\RuntimeException("No resource with id '{$id}'");
+ }
+
+ $resource = & $this->resources[$id];
+ if ($resource instanceof MemcacheResource) {
+ // Cannot get options from Memcache resource once created
+ throw new Exception\RuntimeException("Cannot get compress threshold once resource is created");
+ }
+ return $resource['auto_compress_threshold'];
+ }
+
+ /**
+ * Set compress threshold
+ *
+ * @param string $id
+ * @param int|string|array|ArrayAccess|null $threshold
+ * @param float|string|bool $minSavings
+ * @return MemcacheResourceManager
+ */
+ public function setAutoCompressThreshold($id, $threshold, $minSavings = false)
+ {
+ if (!$this->hasResource($id)) {
+ return $this->setResource($id, array(
+ 'auto_compress_threshold' => $threshold,
+ ));
+ }
+
+ $this->normalizeAutoCompressThreshold($threshold, $minSavings);
+
+ $resource = & $this->resources[$id];
+ if ($resource instanceof MemcacheResource) {
+ $this->setResourceAutoCompressThreshold($resource, $threshold, $minSavings);
+ } else {
+ $resource['auto_compress_threshold'] = $threshold;
+ if ($minSavings !== false) {
+ $resource['auto_compress_min_savings'] = $minSavings;
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Get compress min savings
+ *
+ * @param string $id
+ * @return float|null
+ * @throws Exception\RuntimeException
+ */
+ public function getAutoCompressMinSavings($id)
+ {
+ if (!$this->hasResource($id)) {
+ throw new Exception\RuntimeException("No resource with id '{$id}'");
+ }
+
+ $resource = & $this->resources[$id];
+ if ($resource instanceof MemcacheResource) {
+ // Cannot get options from Memcache resource once created
+ throw new Exception\RuntimeException("Cannot get compress min savings once resource is created");
+ }
+ return $resource['auto_compress_min_savings'];
+ }
+
+ /**
+ * Set compress min savings
+ *
+ * @param string $id
+ * @param float|string|null $minSavings
+ * @return MemcacheResourceManager
+ * @throws \Zend\Cache\Exception\RuntimeException
+ */
+ public function setAutoCompressMinSavings($id, $minSavings)
+ {
+ if (!$this->hasResource($id)) {
+ return $this->setResource($id, array(
+ 'auto_compress_min_savings' => $minSavings,
+ ));
+ }
+
+ $minSavings = (float) $minSavings;
+
+ $resource = & $this->resources[$id];
+ if ($resource instanceof MemcacheResource) {
+ throw new Exception\RuntimeException(
+ "Cannot set compress min savings without a threshold value once a resource is created"
+ );
+ } else {
+ $resource['auto_compress_min_savings'] = $minSavings;
+ }
+ return $this;
+ }
+
+ /**
+ * Set default server values
+ * array(
+ * 'persistent' => , 'weight' => ,
+ * 'timeout' => , 'retry_interval' => ,
+ * )
+ * @param string $id
+ * @param array $serverDefaults
+ * @return MemcacheResourceManager
+ */
+ public function setServerDefaults($id, array $serverDefaults)
+ {
+ if (!$this->hasResource($id)) {
+ return $this->setResource($id, array(
+ 'server_defaults' => $serverDefaults
+ ));
+ }
+
+ $this->normalizeServerDefaults($serverDefaults);
+ $this->serverDefaults[$id] = $serverDefaults;
+
+ return $this;
+ }
+
+ /**
+ * Get default server values
+ *
+ * @param string $id
+ * @return array
+ * @throws Exception\RuntimeException
+ */
+ public function getServerDefaults($id)
+ {
+ if (!isset($this->serverDefaults[$id])) {
+ throw new Exception\RuntimeException("No resource with id '{$id}'");
+ }
+ return $this->serverDefaults[$id];
+ }
+
+ /**
+ * @param array $serverDefaults
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function normalizeServerDefaults(& $serverDefaults)
+ {
+ if (!is_array($serverDefaults) && !($serverDefaults instanceof Traversable)) {
+ throw new Exception\InvalidArgumentException(
+ "Server defaults must be an array or an instance of Traversable"
+ );
+ }
+
+ // Defaults
+ $result = array(
+ 'persistent' => true,
+ 'weight' => 1,
+ 'timeout' => 1, // seconds
+ 'retry_interval' => 15, // seconds
+ );
+
+ foreach ($serverDefaults as $key => $value) {
+ switch ($key) {
+ case 'persistent':
+ $value = (bool) $value;
+ break;
+ case 'weight':
+ case 'timeout':
+ case 'retry_interval':
+ $value = (int) $value;
+ break;
+ }
+ $result[$key] = $value;
+ }
+
+ $serverDefaults = $result;
+ }
+
+ /**
+ * Set callback for server connection failures
+ *
+ * @param string $id
+ * @param callable|null $failureCallback
+ * @return MemcacheResourceManager
+ */
+ public function setFailureCallback($id, $failureCallback)
+ {
+ if (!$this->hasResource($id)) {
+ return $this->setResource($id, array(), $failureCallback);
+ }
+
+ $this->failureCallbacks[$id] = $failureCallback;
+ return $this;
+ }
+
+ /**
+ * Get callback for server connection failures
+ *
+ * @param string $id
+ * @return callable|null
+ * @throws Exception\RuntimeException
+ */
+ public function getFailureCallback($id)
+ {
+ if (!isset($this->failureCallbacks[$id])) {
+ throw new Exception\RuntimeException("No resource with id '{$id}'");
+ }
+ return $this->failureCallbacks[$id];
+ }
+
+ /**
+ * Get servers
+ *
+ * @param string $id
+ * @throws Exception\RuntimeException
+ * @return array array('host' => , 'port' => , 'weight' => )
+ */
+ public function getServers($id)
+ {
+ if (!$this->hasResource($id)) {
+ throw new Exception\RuntimeException("No resource with id '{$id}'");
+ }
+
+ $resource = & $this->resources[$id];
+ if ($resource instanceof MemcacheResource) {
+ throw new Exception\RuntimeException("Cannot get server list once resource is created");
+ }
+ return $resource['servers'];
+ }
+
+ /**
+ * Add servers
+ *
+ * @param string $id
+ * @param string|array $servers
+ * @return MemcacheResourceManager
+ */
+ public function addServers($id, $servers)
+ {
+ if (!$this->hasResource($id)) {
+ return $this->setResource($id, array(
+ 'servers' => $servers
+ ));
+ }
+
+ $this->normalizeServers($servers);
+
+ $resource = & $this->resources[$id];
+ if ($resource instanceof MemcacheResource) {
+ foreach ($servers as $server) {
+ $this->addServerToResource(
+ $resource,
+ $server,
+ $this->serverDefaults[$id],
+ $this->failureCallbacks[$id]
+ );
+ }
+ } else {
+ // don't add servers twice
+ $resource['servers'] = array_merge(
+ $resource['servers'],
+ array_udiff($servers, $resource['servers'], array($this, 'compareServers'))
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add one server
+ *
+ * @param string $id
+ * @param string|array $server
+ * @return MemcacheResourceManager
+ */
+ public function addServer($id, $server)
+ {
+ return $this->addServers($id, array($server));
+ }
+
+ /**
+ * @param MemcacheResource $resource
+ * @param array $server
+ * @param array $serverDefaults
+ * @param callable|null $failureCallback
+ */
+ protected function addServerToResource(
+ MemcacheResource $resource,
+ array $server,
+ array $serverDefaults,
+ $failureCallback
+ ) {
+ // Apply server defaults
+ $server = array_merge($serverDefaults, $server);
+
+ // Reorder parameters
+ $params = array(
+ $server['host'],
+ $server['port'],
+ $server['persistent'],
+ $server['weight'],
+ $server['timeout'],
+ $server['retry_interval'],
+ $server['status'],
+ );
+ if (isset($failureCallback)) {
+ $params[] = $failureCallback;
+ }
+ call_user_func_array(array($resource, 'addServer'), $params);
+ }
+
+ /**
+ * Normalize a list of servers into the following format:
+ * array(array('host' => , 'port' => , 'weight' => )[, ...])
+ *
+ * @param string|array $servers
+ */
+ protected function normalizeServers(& $servers)
+ {
+ if (is_string($servers)) {
+ // Convert string into a list of servers
+ $servers = explode(',', $servers);
+ }
+
+ $result = array();
+ foreach ($servers as $server) {
+ $this->normalizeServer($server);
+ $result[$server['host'] . ':' . $server['port']] = $server;
+ }
+
+ $servers = array_values($result);
+ }
+
+ /**
+ * Normalize one server into the following format:
+ * array(
+ * 'host' => , 'port' => , 'weight' => ,
+ * 'status' => , 'persistent' => ,
+ * 'timeout' => , 'retry_interval' => ,
+ * )
+ *
+ * @param string|array $server
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function normalizeServer(& $server)
+ {
+ // WARNING: The order of this array is important.
+ // Used for converting an ordered array to a keyed array.
+ // Append new options, do not insert or you will break BC.
+ $sTmp = array(
+ 'host' => null,
+ 'port' => 11211,
+ 'weight' => null,
+ 'status' => true,
+ 'persistent' => null,
+ 'timeout' => null,
+ 'retry_interval' => null,
+ );
+
+ // convert a single server into an array
+ if ($server instanceof Traversable) {
+ $server = ArrayUtils::iteratorToArray($server);
+ }
+
+ if (is_array($server)) {
+ if (isset($server[0])) {
+ // Convert ordered array to keyed array
+ // array([, [, [, [, [, [, ]]]]]])
+ $server = array_combine(
+ array_slice(array_keys($sTmp), 0, count($server)),
+ $server
+ );
+ }
+ $sTmp = array_merge($sTmp, $server);
+ } elseif (is_string($server)) {
+ // parse server from URI host{:?port}{?weight}
+ $server = trim($server);
+ if (strpos($server, '://') === false) {
+ $server = 'tcp://' . $server;
+ }
+
+ $urlParts = parse_url($server);
+ if (!$urlParts) {
+ throw new Exception\InvalidArgumentException("Invalid server given");
+ }
+
+ $sTmp = array_merge($sTmp, array_intersect_key($urlParts, $sTmp));
+ if (isset($urlParts['query'])) {
+ $query = null;
+ parse_str($urlParts['query'], $query);
+ $sTmp = array_merge($sTmp, array_intersect_key($query, $sTmp));
+ }
+ }
+
+ if (!$sTmp['host']) {
+ throw new Exception\InvalidArgumentException('Missing required server host');
+ }
+
+ // Filter values
+ foreach ($sTmp as $key => $value) {
+ if (isset($value)) {
+ switch ($key) {
+ case 'host':
+ $value = (string) $value;
+ break;
+ case 'status':
+ case 'persistent':
+ $value = (bool) $value;
+ break;
+ case 'port':
+ case 'weight':
+ case 'timeout':
+ case 'retry_interval':
+ $value = (int) $value;
+ break;
+ }
+ }
+ $sTmp[$key] = $value;
+ }
+ $sTmp = array_filter(
+ $sTmp,
+ function ($val) {
+ return isset($val);
+ }
+ );
+
+ $server = $sTmp;
+ }
+
+ /**
+ * Compare 2 normalized server arrays
+ * (Compares only the host and the port)
+ *
+ * @param array $serverA
+ * @param array $serverB
+ * @return int
+ */
+ protected function compareServers(array $serverA, array $serverB)
+ {
+ $keyA = $serverA['host'] . ':' . $serverA['port'];
+ $keyB = $serverB['host'] . ':' . $serverB['port'];
+ if ($keyA === $keyB) {
+ return 0;
+ }
+ return $keyA > $keyB ? 1 : -1;
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/Memcached.php b/library/Zend/Cache/Storage/Adapter/Memcached.php
new file mode 100755
index 0000000000..54bbed7832
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/Memcached.php
@@ -0,0 +1,703 @@
+= 1.0.0');
+ }
+
+ parent::__construct($options);
+
+ // reset initialized flag on update option(s)
+ $initialized = & $this->initialized;
+ $this->getEventManager()->attach('option', function ($event) use (& $initialized) {
+ $initialized = false;
+ });
+ }
+
+ /**
+ * Initialize the internal memcached resource
+ *
+ * @return MemcachedResource
+ */
+ protected function getMemcachedResource()
+ {
+ if (!$this->initialized) {
+ $options = $this->getOptions();
+
+ // get resource manager and resource id
+ $this->resourceManager = $options->getResourceManager();
+ $this->resourceId = $options->getResourceId();
+
+ // init namespace prefix
+ $namespace = $options->getNamespace();
+ if ($namespace !== '') {
+ $this->namespacePrefix = $namespace . $options->getNamespaceSeparator();
+ } else {
+ $this->namespacePrefix = '';
+ }
+
+ // update initialized flag
+ $this->initialized = true;
+ }
+
+ return $this->resourceManager->getResource($this->resourceId);
+ }
+
+ /* options */
+
+ /**
+ * Set options.
+ *
+ * @param array|Traversable|MemcachedOptions $options
+ * @return Memcached
+ * @see getOptions()
+ */
+ public function setOptions($options)
+ {
+ if (!$options instanceof MemcachedOptions) {
+ $options = new MemcachedOptions($options);
+ }
+
+ return parent::setOptions($options);
+ }
+
+ /**
+ * Get options.
+ *
+ * @return MemcachedOptions
+ * @see setOptions()
+ */
+ public function getOptions()
+ {
+ if (!$this->options) {
+ $this->setOptions(new MemcachedOptions());
+ }
+ return $this->options;
+ }
+
+ /* FlushableInterface */
+
+ /**
+ * Flush the whole storage
+ *
+ * @return bool
+ */
+ public function flush()
+ {
+ $memc = $this->getMemcachedResource();
+ if (!$memc->flush()) {
+ throw $this->getExceptionByResultCode($memc->getResultCode());
+ }
+ return true;
+ }
+
+ /* TotalSpaceCapableInterface */
+
+ /**
+ * Get total space in bytes
+ *
+ * @return int|float
+ */
+ public function getTotalSpace()
+ {
+ $memc = $this->getMemcachedResource();
+ $stats = $memc->getStats();
+ if ($stats === false) {
+ throw new Exception\RuntimeException($memc->getResultMessage());
+ }
+
+ $mem = array_pop($stats);
+ return $mem['limit_maxbytes'];
+ }
+
+ /* AvailableSpaceCapableInterface */
+
+ /**
+ * Get available space in bytes
+ *
+ * @return int|float
+ */
+ public function getAvailableSpace()
+ {
+ $memc = $this->getMemcachedResource();
+ $stats = $memc->getStats();
+ if ($stats === false) {
+ throw new Exception\RuntimeException($memc->getResultMessage());
+ }
+
+ $mem = array_pop($stats);
+ return $mem['limit_maxbytes'] - $mem['bytes'];
+ }
+
+ /* reading */
+
+ /**
+ * Internal method to get an item.
+ *
+ * @param string $normalizedKey
+ * @param bool $success
+ * @param mixed $casToken
+ * @return mixed Data on success, null on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetItem(& $normalizedKey, & $success = null, & $casToken = null)
+ {
+ $memc = $this->getMemcachedResource();
+ $internalKey = $this->namespacePrefix . $normalizedKey;
+
+ if (func_num_args() > 2) {
+ $result = $memc->get($internalKey, null, $casToken);
+ } else {
+ $result = $memc->get($internalKey);
+ }
+
+ $success = true;
+ if ($result === false) {
+ $rsCode = $memc->getResultCode();
+ if ($rsCode == MemcachedResource::RES_NOTFOUND) {
+ $result = null;
+ $success = false;
+ } elseif ($rsCode) {
+ $success = false;
+ throw $this->getExceptionByResultCode($rsCode);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to get multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Associative array of keys and values
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetItems(array & $normalizedKeys)
+ {
+ $memc = $this->getMemcachedResource();
+
+ foreach ($normalizedKeys as & $normalizedKey) {
+ $normalizedKey = $this->namespacePrefix . $normalizedKey;
+ }
+
+ $result = $memc->getMulti($normalizedKeys);
+ if ($result === false) {
+ throw $this->getExceptionByResultCode($memc->getResultCode());
+ }
+
+ // remove namespace prefix from result
+ if ($result && $this->namespacePrefix !== '') {
+ $tmp = array();
+ $nsPrefixLength = strlen($this->namespacePrefix);
+ foreach ($result as $internalKey => & $value) {
+ $tmp[substr($internalKey, $nsPrefixLength)] = & $value;
+ }
+ $result = $tmp;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to test if an item exists.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalHasItem(& $normalizedKey)
+ {
+ $memc = $this->getMemcachedResource();
+ $value = $memc->get($this->namespacePrefix . $normalizedKey);
+ if ($value === false) {
+ $rsCode = $memc->getResultCode();
+ if ($rsCode == MemcachedResource::RES_SUCCESS) {
+ return true;
+ } elseif ($rsCode == MemcachedResource::RES_NOTFOUND) {
+ return false;
+ } else {
+ throw $this->getExceptionByResultCode($rsCode);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Internal method to test multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Array of found keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalHasItems(array & $normalizedKeys)
+ {
+ $memc = $this->getMemcachedResource();
+
+ foreach ($normalizedKeys as & $normalizedKey) {
+ $normalizedKey = $this->namespacePrefix . $normalizedKey;
+ }
+
+ $result = $memc->getMulti($normalizedKeys);
+ if ($result === false) {
+ throw $this->getExceptionByResultCode($memc->getResultCode());
+ }
+
+ // Convert to a simgle list
+ $result = array_keys($result);
+
+ // remove namespace prefix
+ if ($result && $this->namespacePrefix !== '') {
+ $nsPrefixLength = strlen($this->namespacePrefix);
+ foreach ($result as & $internalKey) {
+ $internalKey = substr($internalKey, $nsPrefixLength);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get metadata of multiple items
+ *
+ * @param array $normalizedKeys
+ * @return array Associative array of keys and metadata
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetMetadatas(array & $normalizedKeys)
+ {
+ $memc = $this->getMemcachedResource();
+
+ foreach ($normalizedKeys as & $normalizedKey) {
+ $normalizedKey = $this->namespacePrefix . $normalizedKey;
+ }
+
+ $result = $memc->getMulti($normalizedKeys);
+ if ($result === false) {
+ throw $this->getExceptionByResultCode($memc->getResultCode());
+ }
+
+ // remove namespace prefix and use an empty array as metadata
+ if ($this->namespacePrefix !== '') {
+ $tmp = array();
+ $nsPrefixLength = strlen($this->namespacePrefix);
+ foreach (array_keys($result) as $internalKey) {
+ $tmp[substr($internalKey, $nsPrefixLength)] = array();
+ }
+ $result = $tmp;
+ } else {
+ foreach ($result as & $value) {
+ $value = array();
+ }
+ }
+
+ return $result;
+ }
+
+ /* writing */
+
+ /**
+ * Internal method to store an item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalSetItem(& $normalizedKey, & $value)
+ {
+ $memc = $this->getMemcachedResource();
+ $expiration = $this->expirationTime();
+ if (!$memc->set($this->namespacePrefix . $normalizedKey, $value, $expiration)) {
+ throw $this->getExceptionByResultCode($memc->getResultCode());
+ }
+
+ return true;
+ }
+
+ /**
+ * Internal method to store multiple items.
+ *
+ * @param array $normalizedKeyValuePairs
+ * @return array Array of not stored keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalSetItems(array & $normalizedKeyValuePairs)
+ {
+ $memc = $this->getMemcachedResource();
+ $expiration = $this->expirationTime();
+
+ $namespacedKeyValuePairs = array();
+ foreach ($normalizedKeyValuePairs as $normalizedKey => & $value) {
+ $namespacedKeyValuePairs[$this->namespacePrefix . $normalizedKey] = & $value;
+ }
+
+ if (!$memc->setMulti($namespacedKeyValuePairs, $expiration)) {
+ throw $this->getExceptionByResultCode($memc->getResultCode());
+ }
+
+ return array();
+ }
+
+ /**
+ * Add an item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalAddItem(& $normalizedKey, & $value)
+ {
+ $memc = $this->getMemcachedResource();
+ $expiration = $this->expirationTime();
+ if (!$memc->add($this->namespacePrefix . $normalizedKey, $value, $expiration)) {
+ if ($memc->getResultCode() == MemcachedResource::RES_NOTSTORED) {
+ return false;
+ }
+ throw $this->getExceptionByResultCode($memc->getResultCode());
+ }
+
+ return true;
+ }
+
+ /**
+ * Internal method to replace an existing item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalReplaceItem(& $normalizedKey, & $value)
+ {
+ $memc = $this->getMemcachedResource();
+ $expiration = $this->expirationTime();
+ if (!$memc->replace($this->namespacePrefix . $normalizedKey, $value, $expiration)) {
+ $rsCode = $memc->getResultCode();
+ if ($rsCode == MemcachedResource::RES_NOTSTORED) {
+ return false;
+ }
+ throw $this->getExceptionByResultCode($rsCode);
+ }
+
+ return true;
+ }
+
+ /**
+ * Internal method to set an item only if token matches
+ *
+ * @param mixed $token
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ * @see getItem()
+ * @see setItem()
+ */
+ protected function internalCheckAndSetItem(& $token, & $normalizedKey, & $value)
+ {
+ $memc = $this->getMemcachedResource();
+ $expiration = $this->expirationTime();
+ $result = $memc->cas($token, $this->namespacePrefix . $normalizedKey, $value, $expiration);
+
+ if ($result === false) {
+ $rsCode = $memc->getResultCode();
+ if ($rsCode !== 0 && $rsCode != MemcachedResource::RES_DATA_EXISTS) {
+ throw $this->getExceptionByResultCode($rsCode);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to remove an item.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalRemoveItem(& $normalizedKey)
+ {
+ $memc = $this->getMemcachedResource();
+ $result = $memc->delete($this->namespacePrefix . $normalizedKey);
+
+ if ($result === false) {
+ $rsCode = $memc->getResultCode();
+ if ($rsCode == MemcachedResource::RES_NOTFOUND) {
+ return false;
+ } elseif ($rsCode != MemcachedResource::RES_SUCCESS) {
+ throw $this->getExceptionByResultCode($rsCode);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Internal method to remove multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Array of not removed keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalRemoveItems(array & $normalizedKeys)
+ {
+ // support for removing multiple items at once has been added in ext/memcached-2.0.0
+ if (static::$extMemcachedMajorVersion < 2) {
+ return parent::internalRemoveItems($normalizedKeys);
+ }
+
+ $memc = $this->getMemcachedResource();
+
+ foreach ($normalizedKeys as & $normalizedKey) {
+ $normalizedKey = $this->namespacePrefix . $normalizedKey;
+ }
+
+ $rsCodes = $memc->deleteMulti($normalizedKeys);
+
+ $missingKeys = array();
+ foreach ($rsCodes as $key => $rsCode) {
+ if ($rsCode !== true && $rsCode != MemcachedResource::RES_SUCCESS) {
+ if ($rsCode != MemcachedResource::RES_NOTFOUND) {
+ throw $this->getExceptionByResultCode($rsCode);
+ }
+ $missingKeys[] = $key;
+ }
+ }
+
+ // remove namespace prefix
+ if ($missingKeys && $this->namespacePrefix !== '') {
+ $nsPrefixLength = strlen($this->namespacePrefix);
+ foreach ($missingKeys as & $missingKey) {
+ $missingKey = substr($missingKey, $nsPrefixLength);
+ }
+ }
+
+ return $missingKeys;
+ }
+
+ /**
+ * Internal method to increment an item.
+ *
+ * @param string $normalizedKey
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalIncrementItem(& $normalizedKey, & $value)
+ {
+ $memc = $this->getMemcachedResource();
+ $internalKey = $this->namespacePrefix . $normalizedKey;
+ $value = (int) $value;
+ $newValue = $memc->increment($internalKey, $value);
+
+ if ($newValue === false) {
+ $rsCode = $memc->getResultCode();
+
+ // initial value
+ if ($rsCode == MemcachedResource::RES_NOTFOUND) {
+ $newValue = $value;
+ $memc->add($internalKey, $newValue, $this->expirationTime());
+ $rsCode = $memc->getResultCode();
+ }
+
+ if ($rsCode) {
+ throw $this->getExceptionByResultCode($rsCode);
+ }
+ }
+
+ return $newValue;
+ }
+
+ /**
+ * Internal method to decrement an item.
+ *
+ * @param string $normalizedKey
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalDecrementItem(& $normalizedKey, & $value)
+ {
+ $memc = $this->getMemcachedResource();
+ $internalKey = $this->namespacePrefix . $normalizedKey;
+ $value = (int) $value;
+ $newValue = $memc->decrement($internalKey, $value);
+
+ if ($newValue === false) {
+ $rsCode = $memc->getResultCode();
+
+ // initial value
+ if ($rsCode == MemcachedResource::RES_NOTFOUND) {
+ $newValue = -$value;
+ $memc->add($internalKey, $newValue, $this->expirationTime());
+ $rsCode = $memc->getResultCode();
+ }
+
+ if ($rsCode) {
+ throw $this->getExceptionByResultCode($rsCode);
+ }
+ }
+
+ return $newValue;
+ }
+
+ /* status */
+
+ /**
+ * Internal method to get capabilities of this adapter
+ *
+ * @return Capabilities
+ */
+ protected function internalGetCapabilities()
+ {
+ if ($this->capabilities === null) {
+ $this->capabilityMarker = new stdClass();
+ $this->capabilities = new Capabilities(
+ $this,
+ $this->capabilityMarker,
+ array(
+ 'supportedDatatypes' => array(
+ 'NULL' => true,
+ 'boolean' => true,
+ 'integer' => true,
+ 'double' => true,
+ 'string' => true,
+ 'array' => true,
+ 'object' => 'object',
+ 'resource' => false,
+ ),
+ 'supportedMetadata' => array(),
+ 'minTtl' => 1,
+ 'maxTtl' => 0,
+ 'staticTtl' => true,
+ 'ttlPrecision' => 1,
+ 'useRequestTime' => false,
+ 'expiredRead' => false,
+ 'maxKeyLength' => 255,
+ 'namespaceIsPrefix' => true,
+ )
+ );
+ }
+
+ return $this->capabilities;
+ }
+
+ /* internal */
+
+ /**
+ * Get expiration time by ttl
+ *
+ * Some storage commands involve sending an expiration value (relative to
+ * an item or to an operation requested by the client) to the server. In
+ * all such cases, the actual value sent may either be Unix time (number of
+ * seconds since January 1, 1970, as an integer), or a number of seconds
+ * starting from current time. In the latter case, this number of seconds
+ * may not exceed 60*60*24*30 (number of seconds in 30 days); if the
+ * expiration value is larger than that, the server will consider it to be
+ * real Unix time value rather than an offset from current time.
+ *
+ * @return int
+ */
+ protected function expirationTime()
+ {
+ $ttl = $this->getOptions()->getTtl();
+ if ($ttl > 2592000) {
+ return time() + $ttl;
+ }
+ return $ttl;
+ }
+
+ /**
+ * Generate exception based of memcached result code
+ *
+ * @param int $code
+ * @return Exception\RuntimeException
+ * @throws Exception\InvalidArgumentException On success code
+ */
+ protected function getExceptionByResultCode($code)
+ {
+ switch ($code) {
+ case MemcachedResource::RES_SUCCESS:
+ throw new Exception\InvalidArgumentException(
+ "The result code '{$code}' (SUCCESS) isn't an error"
+ );
+
+ default:
+ return new Exception\RuntimeException($this->getMemcachedResource()->getResultMessage());
+ }
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/MemcachedOptions.php b/library/Zend/Cache/Storage/Adapter/MemcachedOptions.php
new file mode 100755
index 0000000000..fabf3033df
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/MemcachedOptions.php
@@ -0,0 +1,319 @@
+namespaceSeparator !== $namespaceSeparator) {
+ $this->triggerOptionEvent('namespace_separator', $namespaceSeparator);
+ $this->namespaceSeparator = $namespaceSeparator;
+ }
+ return $this;
+ }
+
+ /**
+ * Get namespace separator
+ *
+ * @return string
+ */
+ public function getNamespaceSeparator()
+ {
+ return $this->namespaceSeparator;
+ }
+
+ /**
+ * A memcached resource to share
+ *
+ * @param null|MemcachedResource $memcachedResource
+ * @return MemcachedOptions
+ * @deprecated Please use the resource manager instead
+ */
+ public function setMemcachedResource(MemcachedResource $memcachedResource = null)
+ {
+ trigger_error(
+ 'This method is deprecated and will be removed in the feature'
+ . ', please use the resource manager instead',
+ E_USER_DEPRECATED
+ );
+
+ if ($memcachedResource !== null) {
+ $this->triggerOptionEvent('memcached_resource', $memcachedResource);
+ $resourceManager = $this->getResourceManager();
+ $resourceId = $this->getResourceId();
+ $resourceManager->setResource($resourceId, $memcachedResource);
+ }
+ return $this;
+ }
+
+ /**
+ * Get memcached resource to share
+ *
+ * @return MemcachedResource
+ * @deprecated Please use the resource manager instead
+ */
+ public function getMemcachedResource()
+ {
+ trigger_error(
+ 'This method is deprecated and will be removed in the feature'
+ . ', please use the resource manager instead',
+ E_USER_DEPRECATED
+ );
+
+ return $this->resourceManager->getResource($this->getResourceId());
+ }
+
+ /**
+ * Set the memcached resource manager to use
+ *
+ * @param null|MemcachedResourceManager $resourceManager
+ * @return MemcachedOptions
+ */
+ public function setResourceManager(MemcachedResourceManager $resourceManager = null)
+ {
+ if ($this->resourceManager !== $resourceManager) {
+ $this->triggerOptionEvent('resource_manager', $resourceManager);
+ $this->resourceManager = $resourceManager;
+ }
+ return $this;
+ }
+
+ /**
+ * Get the memcached resource manager
+ *
+ * @return MemcachedResourceManager
+ */
+ public function getResourceManager()
+ {
+ if (!$this->resourceManager) {
+ $this->resourceManager = new MemcachedResourceManager();
+ }
+ return $this->resourceManager;
+ }
+
+ /**
+ * Get the memcached resource id
+ *
+ * @return string
+ */
+ public function getResourceId()
+ {
+ return $this->resourceId;
+ }
+
+ /**
+ * Set the memcached resource id
+ *
+ * @param string $resourceId
+ * @return MemcachedOptions
+ */
+ public function setResourceId($resourceId)
+ {
+ $resourceId = (string) $resourceId;
+ if ($this->resourceId !== $resourceId) {
+ $this->triggerOptionEvent('resource_id', $resourceId);
+ $this->resourceId = $resourceId;
+ }
+ return $this;
+ }
+
+ /**
+ * Get the persistent id
+ *
+ * @return string
+ */
+ public function getPersistentId()
+ {
+ return $this->getResourceManager()->getPersistentId($this->getResourceId());
+ }
+
+ /**
+ * Set the persistent id
+ *
+ * @param string $persistentId
+ * @return MemcachedOptions
+ */
+ public function setPersistentId($persistentId)
+ {
+ $this->triggerOptionEvent('persistent_id', $persistentId);
+ $this->getResourceManager()->setPersistentId($this->getResourceId(), $persistentId);
+ return $this;
+ }
+
+ /**
+ * Add a server to the list
+ *
+ * @param string $host
+ * @param int $port
+ * @param int $weight
+ * @return MemcachedOptions
+ * @deprecated Please use the resource manager instead
+ */
+ public function addServer($host, $port = 11211, $weight = 0)
+ {
+ trigger_error(
+ 'This method is deprecated and will be removed in the feature'
+ . ', please use the resource manager instead',
+ E_USER_DEPRECATED
+ );
+
+ $this->getResourceManager()->addServer($this->getResourceId(), array(
+ 'host' => $host,
+ 'port' => $port,
+ 'weight' => $weight
+ ));
+
+ return $this;
+ }
+
+ /**
+ * Set a list of memcached servers to add on initialize
+ *
+ * @param string|array $servers list of servers
+ * @return MemcachedOptions
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setServers($servers)
+ {
+ $this->getResourceManager()->setServers($this->getResourceId(), $servers);
+ return $this;
+ }
+
+ /**
+ * Get Servers
+ *
+ * @return array
+ */
+ public function getServers()
+ {
+ return $this->getResourceManager()->getServers($this->getResourceId());
+ }
+
+ /**
+ * Set libmemcached options
+ *
+ * @param array $libOptions
+ * @return MemcachedOptions
+ * @link http://php.net/manual/memcached.constants.php
+ */
+ public function setLibOptions(array $libOptions)
+ {
+ $this->getResourceManager()->setLibOptions($this->getResourceId(), $libOptions);
+ return $this;
+ }
+
+ /**
+ * Set libmemcached option
+ *
+ * @param string|int $key
+ * @param mixed $value
+ * @return MemcachedOptions
+ * @link http://php.net/manual/memcached.constants.php
+ * @deprecated Please use lib_options or the resource manager instead
+ */
+ public function setLibOption($key, $value)
+ {
+ trigger_error(
+ 'This method is deprecated and will be removed in the feature'
+ . ', please use "lib_options" or the resource manager instead',
+ E_USER_DEPRECATED
+ );
+
+ $this->getResourceManager()->setLibOption($this->getResourceId(), $key, $value);
+ return $this;
+ }
+
+ /**
+ * Get libmemcached options
+ *
+ * @return array
+ * @link http://php.net/manual/memcached.constants.php
+ */
+ public function getLibOptions()
+ {
+ return $this->getResourceManager()->getLibOptions($this->getResourceId());
+ }
+
+ /**
+ * Get libmemcached option
+ *
+ * @param string|int $key
+ * @return mixed
+ * @link http://php.net/manual/memcached.constants.php
+ * @deprecated Please use lib_options or the resource manager instead
+ */
+ public function getLibOption($key)
+ {
+ trigger_error(
+ 'This method is deprecated and will be removed in the feature'
+ . ', please use "lib_options" or the resource manager instead',
+ E_USER_DEPRECATED
+ );
+
+ return $this->getResourceManager()->getLibOption($this->getResourceId(), $key);
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/MemcachedResourceManager.php b/library/Zend/Cache/Storage/Adapter/MemcachedResourceManager.php
new file mode 100755
index 0000000000..10edb99aef
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/MemcachedResourceManager.php
@@ -0,0 +1,547 @@
+ , 'port' => , 'weight' => )
+ */
+ public function getServers($id)
+ {
+ if (!$this->hasResource($id)) {
+ throw new Exception\RuntimeException("No resource with id '{$id}'");
+ }
+
+ $resource = & $this->resources[$id];
+
+ if ($resource instanceof MemcachedResource) {
+ return $resource->getServerList();
+ }
+ return $resource['servers'];
+ }
+
+ /**
+ * Normalize one server into the following format:
+ * array('host' => , 'port' => , 'weight' => )
+ *
+ * @param string|array &$server
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function normalizeServer(&$server)
+ {
+ $host = null;
+ $port = 11211;
+ $weight = 0;
+
+ // convert a single server into an array
+ if ($server instanceof Traversable) {
+ $server = ArrayUtils::iteratorToArray($server);
+ }
+
+ if (is_array($server)) {
+ // array([, [, ]])
+ if (isset($server[0])) {
+ $host = (string) $server[0];
+ $port = isset($server[1]) ? (int) $server[1] : $port;
+ $weight = isset($server[2]) ? (int) $server[2] : $weight;
+ }
+
+ // array('host' => [, 'port' => [, 'weight' => ]])
+ if (!isset($server[0]) && isset($server['host'])) {
+ $host = (string) $server['host'];
+ $port = isset($server['port']) ? (int) $server['port'] : $port;
+ $weight = isset($server['weight']) ? (int) $server['weight'] : $weight;
+ }
+ } else {
+ // parse server from URI host{:?port}{?weight}
+ $server = trim($server);
+ if (strpos($server, '://') === false) {
+ $server = 'tcp://' . $server;
+ }
+
+ $server = parse_url($server);
+ if (!$server) {
+ throw new Exception\InvalidArgumentException("Invalid server given");
+ }
+
+ $host = $server['host'];
+ $port = isset($server['port']) ? (int) $server['port'] : $port;
+
+ if (isset($server['query'])) {
+ $query = null;
+ parse_str($server['query'], $query);
+ if (isset($query['weight'])) {
+ $weight = (int) $query['weight'];
+ }
+ }
+ }
+
+ if (!$host) {
+ throw new Exception\InvalidArgumentException('Missing required server host');
+ }
+
+ $server = array(
+ 'host' => $host,
+ 'port' => $port,
+ 'weight' => $weight,
+ );
+ }
+
+ /**
+ * Check if a resource exists
+ *
+ * @param string $id
+ * @return bool
+ */
+ public function hasResource($id)
+ {
+ return isset($this->resources[$id]);
+ }
+
+ /**
+ * Gets a memcached resource
+ *
+ * @param string $id
+ * @return MemcachedResource
+ * @throws Exception\RuntimeException
+ */
+ public function getResource($id)
+ {
+ if (!$this->hasResource($id)) {
+ throw new Exception\RuntimeException("No resource with id '{$id}'");
+ }
+
+ $resource = $this->resources[$id];
+ if ($resource instanceof MemcachedResource) {
+ return $resource;
+ }
+
+ if ($resource['persistent_id'] !== '') {
+ $memc = new MemcachedResource($resource['persistent_id']);
+ } else {
+ $memc = new MemcachedResource();
+ }
+
+ if (method_exists($memc, 'setOptions')) {
+ $memc->setOptions($resource['lib_options']);
+ } else {
+ foreach ($resource['lib_options'] as $k => $v) {
+ $memc->setOption($k, $v);
+ }
+ }
+
+ // merge and add servers (with persistence id servers could be added already)
+ $servers = array_udiff($resource['servers'], $memc->getServerList(), array($this, 'compareServers'));
+ if ($servers) {
+ $memc->addServers($servers);
+ }
+
+ // buffer and return
+ $this->resources[$id] = $memc;
+ return $memc;
+ }
+
+ /**
+ * Set a resource
+ *
+ * @param string $id
+ * @param array|Traversable|MemcachedResource $resource
+ * @return MemcachedResourceManager Fluent interface
+ */
+ public function setResource($id, $resource)
+ {
+ $id = (string) $id;
+
+ if (!($resource instanceof MemcachedResource)) {
+ if ($resource instanceof Traversable) {
+ $resource = ArrayUtils::iteratorToArray($resource);
+ } elseif (!is_array($resource)) {
+ throw new Exception\InvalidArgumentException(
+ 'Resource must be an instance of Memcached or an array or Traversable'
+ );
+ }
+
+ $resource = array_merge(array(
+ 'persistent_id' => '',
+ 'lib_options' => array(),
+ 'servers' => array(),
+ ), $resource);
+
+ // normalize and validate params
+ $this->normalizePersistentId($resource['persistent_id']);
+ $this->normalizeLibOptions($resource['lib_options']);
+ $this->normalizeServers($resource['servers']);
+ }
+
+ $this->resources[$id] = $resource;
+ return $this;
+ }
+
+ /**
+ * Remove a resource
+ *
+ * @param string $id
+ * @return MemcachedResourceManager Fluent interface
+ */
+ public function removeResource($id)
+ {
+ unset($this->resources[$id]);
+ return $this;
+ }
+
+ /**
+ * Set the persistent id
+ *
+ * @param string $id
+ * @param string $persistentId
+ * @return MemcachedResourceManager Fluent interface
+ * @throws Exception\RuntimeException
+ */
+ public function setPersistentId($id, $persistentId)
+ {
+ if (!$this->hasResource($id)) {
+ return $this->setResource($id, array(
+ 'persistent_id' => $persistentId
+ ));
+ }
+
+ $resource = & $this->resources[$id];
+ if ($resource instanceof MemcachedResource) {
+ throw new Exception\RuntimeException(
+ "Can't change persistent id of resource {$id} after instanziation"
+ );
+ }
+
+ $this->normalizePersistentId($persistentId);
+ $resource['persistent_id'] = $persistentId;
+
+ return $this;
+ }
+
+ /**
+ * Get the persistent id
+ *
+ * @param string $id
+ * @return string
+ * @throws Exception\RuntimeException
+ */
+ public function getPersistentId($id)
+ {
+ if (!$this->hasResource($id)) {
+ throw new Exception\RuntimeException("No resource with id '{$id}'");
+ }
+
+ $resource = & $this->resources[$id];
+
+ if ($resource instanceof MemcachedResource) {
+ throw new Exception\RuntimeException(
+ "Can't get persistent id of an instantiated memcached resource"
+ );
+ }
+
+ return $resource['persistent_id'];
+ }
+
+ /**
+ * Normalize the persistent id
+ *
+ * @param string $persistentId
+ */
+ protected function normalizePersistentId(& $persistentId)
+ {
+ $persistentId = (string) $persistentId;
+ }
+
+ /**
+ * Set Libmemcached options
+ *
+ * @param string $id
+ * @param array $libOptions
+ * @return MemcachedResourceManager Fluent interface
+ */
+ public function setLibOptions($id, array $libOptions)
+ {
+ if (!$this->hasResource($id)) {
+ return $this->setResource($id, array(
+ 'lib_options' => $libOptions
+ ));
+ }
+
+ $this->normalizeLibOptions($libOptions);
+
+ $resource = & $this->resources[$id];
+ if ($resource instanceof MemcachedResource) {
+ if (method_exists($resource, 'setOptions')) {
+ $resource->setOptions($libOptions);
+ } else {
+ foreach ($libOptions as $key => $value) {
+ $resource->setOption($key, $value);
+ }
+ }
+ } else {
+ $resource['lib_options'] = $libOptions;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Libmemcached options
+ *
+ * @param string $id
+ * @return array
+ * @throws Exception\RuntimeException
+ */
+ public function getLibOptions($id)
+ {
+ if (!$this->hasResource($id)) {
+ throw new Exception\RuntimeException("No resource with id '{$id}'");
+ }
+
+ $resource = & $this->resources[$id];
+
+ if ($resource instanceof MemcachedResource) {
+ $libOptions = array();
+ $reflection = new ReflectionClass('Memcached');
+ $constants = $reflection->getConstants();
+ foreach ($constants as $constName => $constValue) {
+ if (substr($constName, 0, 4) == 'OPT_') {
+ $libOptions[$constValue] = $resource->getOption($constValue);
+ }
+ }
+ return $libOptions;
+ }
+ return $resource['lib_options'];
+ }
+
+ /**
+ * Set one Libmemcached option
+ *
+ * @param string $id
+ * @param string|int $key
+ * @param mixed $value
+ * @return MemcachedResourceManager Fluent interface
+ */
+ public function setLibOption($id, $key, $value)
+ {
+ return $this->setLibOptions($id, array($key => $value));
+ }
+
+ /**
+ * Get one Libmemcached option
+ *
+ * @param string $id
+ * @param string|int $key
+ * @return mixed
+ * @throws Exception\RuntimeException
+ */
+ public function getLibOption($id, $key)
+ {
+ if (!$this->hasResource($id)) {
+ throw new Exception\RuntimeException("No resource with id '{$id}'");
+ }
+
+ $this->normalizeLibOptionKey($key);
+ $resource = & $this->resources[$id];
+
+ if ($resource instanceof MemcachedResource) {
+ return $resource->getOption($key);
+ }
+
+ return isset($resource['lib_options'][$key]) ? $resource['lib_options'][$key] : null;
+ }
+
+ /**
+ * Normalize libmemcached options
+ *
+ * @param array|Traversable $libOptions
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function normalizeLibOptions(& $libOptions)
+ {
+ if (!is_array($libOptions) && !($libOptions instanceof Traversable)) {
+ throw new Exception\InvalidArgumentException(
+ "Lib-Options must be an array or an instance of Traversable"
+ );
+ }
+
+ $result = array();
+ foreach ($libOptions as $key => $value) {
+ $this->normalizeLibOptionKey($key);
+ $result[$key] = $value;
+ }
+
+ $libOptions = $result;
+ }
+
+ /**
+ * Convert option name into it's constant value
+ *
+ * @param string|int $key
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function normalizeLibOptionKey(& $key)
+ {
+ // convert option name into it's constant value
+ if (is_string($key)) {
+ $const = 'Memcached::OPT_' . str_replace(array(' ', '-'), '_', strtoupper($key));
+ if (!defined($const)) {
+ throw new Exception\InvalidArgumentException("Unknown libmemcached option '{$key}' ({$const})");
+ }
+ $key = constant($const);
+ } else {
+ $key = (int) $key;
+ }
+ }
+
+ /**
+ * Set servers
+ *
+ * $servers can be an array list or a comma separated list of servers.
+ * One server in the list can be descripted as follows:
+ * - URI: [tcp://][:][?weight=]
+ * - Assoc: array('host' => [, 'port' => ][, 'weight' => ])
+ * - List: array([, ][, ])
+ *
+ * @param string $id
+ * @param string|array $servers
+ * @return MemcachedResourceManager
+ */
+ public function setServers($id, $servers)
+ {
+ if (!$this->hasResource($id)) {
+ return $this->setResource($id, array(
+ 'servers' => $servers
+ ));
+ }
+
+ $this->normalizeServers($servers);
+
+ $resource = & $this->resources[$id];
+ if ($resource instanceof MemcachedResource) {
+ // don't add servers twice
+ $servers = array_udiff($servers, $resource->getServerList(), array($this, 'compareServers'));
+ if ($servers) {
+ $resource->addServers($servers);
+ }
+ } else {
+ $resource['servers'] = $servers;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add servers
+ *
+ * @param string $id
+ * @param string|array $servers
+ * @return MemcachedResourceManager
+ */
+ public function addServers($id, $servers)
+ {
+ if (!$this->hasResource($id)) {
+ return $this->setResource($id, array(
+ 'servers' => $servers
+ ));
+ }
+
+ $this->normalizeServers($servers);
+
+ $resource = & $this->resources[$id];
+ if ($resource instanceof MemcachedResource) {
+ // don't add servers twice
+ $servers = array_udiff($servers, $resource->getServerList(), array($this, 'compareServers'));
+ if ($servers) {
+ $resource->addServers($servers);
+ }
+ } else {
+ // don't add servers twice
+ $resource['servers'] = array_merge(
+ $resource['servers'],
+ array_udiff($servers, $resource['servers'], array($this, 'compareServers'))
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add one server
+ *
+ * @param string $id
+ * @param string|array $server
+ * @return MemcachedResourceManager
+ */
+ public function addServer($id, $server)
+ {
+ return $this->addServers($id, array($server));
+ }
+
+ /**
+ * Normalize a list of servers into the following format:
+ * array(array('host' => , 'port' => , 'weight' => )[, ...])
+ *
+ * @param string|array $servers
+ */
+ protected function normalizeServers(& $servers)
+ {
+ if (!is_array($servers) && !$servers instanceof Traversable) {
+ // Convert string into a list of servers
+ $servers = explode(',', $servers);
+ }
+
+ $result = array();
+ foreach ($servers as $server) {
+ $this->normalizeServer($server);
+ $result[$server['host'] . ':' . $server['port']] = $server;
+ }
+
+ $servers = array_values($result);
+ }
+
+ /**
+ * Compare 2 normalized server arrays
+ * (Compares only the host and the port)
+ *
+ * @param array $serverA
+ * @param array $serverB
+ * @return int
+ */
+ protected function compareServers(array $serverA, array $serverB)
+ {
+ $keyA = $serverA['host'] . ':' . $serverA['port'];
+ $keyB = $serverB['host'] . ':' . $serverB['port'];
+ if ($keyA === $keyB) {
+ return 0;
+ }
+ return $keyA > $keyB ? 1 : -1;
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/Memory.php b/library/Zend/Cache/Storage/Adapter/Memory.php
new file mode 100755
index 0000000000..85aa32e6b4
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/Memory.php
@@ -0,0 +1,747 @@
+ => array(
+ * => array(
+ * 0 =>
+ * 1 =>
+ * ['tags' => ]
+ * )
+ * )
+ * )
+ *
+ * @var array
+ */
+ protected $data = array();
+
+ /**
+ * Set options.
+ *
+ * @param array|\Traversable|MemoryOptions $options
+ * @return Memory
+ * @see getOptions()
+ */
+ public function setOptions($options)
+ {
+ if (!$options instanceof MemoryOptions) {
+ $options = new MemoryOptions($options);
+ }
+
+ return parent::setOptions($options);
+ }
+
+ /**
+ * Get options.
+ *
+ * @return MemoryOptions
+ * @see setOptions()
+ */
+ public function getOptions()
+ {
+ if (!$this->options) {
+ $this->setOptions(new MemoryOptions());
+ }
+ return $this->options;
+ }
+
+ /* TotalSpaceCapableInterface */
+
+ /**
+ * Get total space in bytes
+ *
+ * @return int|float
+ */
+ public function getTotalSpace()
+ {
+ return $this->getOptions()->getMemoryLimit();
+ }
+
+ /* AvailableSpaceCapableInterface */
+
+ /**
+ * Get available space in bytes
+ *
+ * @return int|float
+ */
+ public function getAvailableSpace()
+ {
+ $total = $this->getOptions()->getMemoryLimit();
+ $avail = $total - (float) memory_get_usage(true);
+ return ($avail > 0) ? $avail : 0;
+ }
+
+ /* IterableInterface */
+
+ /**
+ * Get the storage iterator
+ *
+ * @return KeyListIterator
+ */
+ public function getIterator()
+ {
+ $ns = $this->getOptions()->getNamespace();
+ $keys = array();
+
+ if (isset($this->data[$ns])) {
+ foreach ($this->data[$ns] as $key => & $tmp) {
+ if ($this->internalHasItem($key)) {
+ $keys[] = $key;
+ }
+ }
+ }
+
+ return new KeyListIterator($this, $keys);
+ }
+
+ /* FlushableInterface */
+
+ /**
+ * Flush the whole storage
+ *
+ * @return bool
+ */
+ public function flush()
+ {
+ $this->data = array();
+ return true;
+ }
+
+ /* ClearExpiredInterface */
+
+ /**
+ * Remove expired items
+ *
+ * @return bool
+ */
+ public function clearExpired()
+ {
+ $ttl = $this->getOptions()->getTtl();
+ if ($ttl <= 0) {
+ return true;
+ }
+
+ $ns = $this->getOptions()->getNamespace();
+ if (!isset($this->data[$ns])) {
+ return true;
+ }
+
+ $data = & $this->data[$ns];
+ foreach ($data as $key => & $item) {
+ if (microtime(true) >= $data[$key][1] + $ttl) {
+ unset($data[$key]);
+ }
+ }
+
+ return true;
+ }
+
+ /* ClearByNamespaceInterface */
+
+ public function clearByNamespace($namespace)
+ {
+ $namespace = (string) $namespace;
+ if ($namespace === '') {
+ throw new Exception\InvalidArgumentException('No namespace given');
+ }
+
+ unset($this->data[$namespace]);
+ return true;
+ }
+
+ /* ClearByPrefixInterface */
+
+ /**
+ * Remove items matching given prefix
+ *
+ * @param string $prefix
+ * @return bool
+ */
+ public function clearByPrefix($prefix)
+ {
+ $prefix = (string) $prefix;
+ if ($prefix === '') {
+ throw new Exception\InvalidArgumentException('No prefix given');
+ }
+
+ $ns = $this->getOptions()->getNamespace();
+ if (!isset($this->data[$ns])) {
+ return true;
+ }
+
+ $prefixL = strlen($prefix);
+ $data = & $this->data[$ns];
+ foreach ($data as $key => & $item) {
+ if (substr($key, 0, $prefixL) === $prefix) {
+ unset($data[$key]);
+ }
+ }
+
+ return true;
+ }
+
+ /* TaggableInterface */
+
+ /**
+ * Set tags to an item by given key.
+ * An empty array will remove all tags.
+ *
+ * @param string $key
+ * @param string[] $tags
+ * @return bool
+ */
+ public function setTags($key, array $tags)
+ {
+ $ns = $this->getOptions()->getNamespace();
+ if (!isset($this->data[$ns][$key])) {
+ return false;
+ }
+
+ $this->data[$ns][$key]['tags'] = $tags;
+ return true;
+ }
+
+ /**
+ * Get tags of an item by given key
+ *
+ * @param string $key
+ * @return string[]|FALSE
+ */
+ public function getTags($key)
+ {
+ $ns = $this->getOptions()->getNamespace();
+ if (!isset($this->data[$ns][$key])) {
+ return false;
+ }
+
+ return isset($this->data[$ns][$key]['tags']) ? $this->data[$ns][$key]['tags'] : array();
+ }
+
+ /**
+ * Remove items matching given tags.
+ *
+ * If $disjunction only one of the given tags must match
+ * else all given tags must match.
+ *
+ * @param string[] $tags
+ * @param bool $disjunction
+ * @return bool
+ */
+ public function clearByTags(array $tags, $disjunction = false)
+ {
+ $ns = $this->getOptions()->getNamespace();
+ if (!isset($this->data[$ns])) {
+ return true;
+ }
+
+ $tagCount = count($tags);
+ $data = & $this->data[$ns];
+ foreach ($data as $key => & $item) {
+ if (isset($item['tags'])) {
+ $diff = array_diff($tags, $item['tags']);
+ if (($disjunction && count($diff) < $tagCount) || (!$disjunction && !$diff)) {
+ unset($data[$key]);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /* reading */
+
+ /**
+ * Internal method to get an item.
+ *
+ * @param string $normalizedKey
+ * @param bool $success
+ * @param mixed $casToken
+ * @return mixed Data on success, null on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetItem(& $normalizedKey, & $success = null, & $casToken = null)
+ {
+ $options = $this->getOptions();
+ $ns = $options->getNamespace();
+ $success = isset($this->data[$ns][$normalizedKey]);
+ if ($success) {
+ $data = & $this->data[$ns][$normalizedKey];
+ $ttl = $options->getTtl();
+ if ($ttl && microtime(true) >= ($data[1] + $ttl)) {
+ $success = false;
+ }
+ }
+
+ if (!$success) {
+ return null;
+ }
+
+ $casToken = $data[0];
+ return $data[0];
+ }
+
+ /**
+ * Internal method to get multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Associative array of keys and values
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetItems(array & $normalizedKeys)
+ {
+ $options = $this->getOptions();
+ $ns = $options->getNamespace();
+ if (!isset($this->data[$ns])) {
+ return array();
+ }
+
+ $data = & $this->data[$ns];
+ $ttl = $options->getTtl();
+ $now = microtime(true);
+
+ $result = array();
+ foreach ($normalizedKeys as $normalizedKey) {
+ if (isset($data[$normalizedKey])) {
+ if (!$ttl || $now < ($data[$normalizedKey][1] + $ttl)) {
+ $result[$normalizedKey] = $data[$normalizedKey][0];
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to test if an item exists.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ */
+ protected function internalHasItem(& $normalizedKey)
+ {
+ $options = $this->getOptions();
+ $ns = $options->getNamespace();
+ if (!isset($this->data[$ns][$normalizedKey])) {
+ return false;
+ }
+
+ // check if expired
+ $ttl = $options->getTtl();
+ if ($ttl && microtime(true) >= ($this->data[$ns][$normalizedKey][1] + $ttl)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Internal method to test multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Array of found keys
+ */
+ protected function internalHasItems(array & $normalizedKeys)
+ {
+ $options = $this->getOptions();
+ $ns = $options->getNamespace();
+ if (!isset($this->data[$ns])) {
+ return array();
+ }
+
+ $data = & $this->data[$ns];
+ $ttl = $options->getTtl();
+ $now = microtime(true);
+
+ $result = array();
+ foreach ($normalizedKeys as $normalizedKey) {
+ if (isset($data[$normalizedKey])) {
+ if (!$ttl || $now < ($data[$normalizedKey][1] + $ttl)) {
+ $result[] = $normalizedKey;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get metadata of an item.
+ *
+ * @param string $normalizedKey
+ * @return array|bool Metadata on success, false on failure
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers getMetadata.pre(PreEvent)
+ * @triggers getMetadata.post(PostEvent)
+ * @triggers getMetadata.exception(ExceptionEvent)
+ */
+ protected function internalGetMetadata(& $normalizedKey)
+ {
+ if (!$this->internalHasItem($normalizedKey)) {
+ return false;
+ }
+
+ $ns = $this->getOptions()->getNamespace();
+ return array(
+ 'mtime' => $this->data[$ns][$normalizedKey][1],
+ );
+ }
+
+ /* writing */
+
+ /**
+ * Internal method to store an item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalSetItem(& $normalizedKey, & $value)
+ {
+ $options = $this->getOptions();
+
+ if (!$this->hasAvailableSpace()) {
+ $memoryLimit = $options->getMemoryLimit();
+ throw new Exception\OutOfSpaceException(
+ "Memory usage exceeds limit ({$memoryLimit})."
+ );
+ }
+
+ $ns = $options->getNamespace();
+ $this->data[$ns][$normalizedKey] = array($value, microtime(true));
+
+ return true;
+ }
+
+ /**
+ * Internal method to store multiple items.
+ *
+ * @param array $normalizedKeyValuePairs
+ * @return array Array of not stored keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalSetItems(array & $normalizedKeyValuePairs)
+ {
+ $options = $this->getOptions();
+
+ if (!$this->hasAvailableSpace()) {
+ $memoryLimit = $options->getMemoryLimit();
+ throw new Exception\OutOfSpaceException(
+ "Memory usage exceeds limit ({$memoryLimit})."
+ );
+ }
+
+ $ns = $options->getNamespace();
+ if (!isset($this->data[$ns])) {
+ $this->data[$ns] = array();
+ }
+
+ $data = & $this->data[$ns];
+ $now = microtime(true);
+ foreach ($normalizedKeyValuePairs as $normalizedKey => $value) {
+ $data[$normalizedKey] = array($value, $now);
+ }
+
+ return array();
+ }
+
+ /**
+ * Add an item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalAddItem(& $normalizedKey, & $value)
+ {
+ $options = $this->getOptions();
+
+ if (!$this->hasAvailableSpace()) {
+ $memoryLimit = $options->getMemoryLimit();
+ throw new Exception\OutOfSpaceException(
+ "Memory usage exceeds limit ({$memoryLimit})."
+ );
+ }
+
+ $ns = $options->getNamespace();
+ if (isset($this->data[$ns][$normalizedKey])) {
+ return false;
+ }
+
+ $this->data[$ns][$normalizedKey] = array($value, microtime(true));
+ return true;
+ }
+
+ /**
+ * Internal method to add multiple items.
+ *
+ * @param array $normalizedKeyValuePairs
+ * @return array Array of not stored keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalAddItems(array & $normalizedKeyValuePairs)
+ {
+ $options = $this->getOptions();
+
+ if (!$this->hasAvailableSpace()) {
+ $memoryLimit = $options->getMemoryLimit();
+ throw new Exception\OutOfSpaceException(
+ "Memory usage exceeds limit ({$memoryLimit})."
+ );
+ }
+
+ $ns = $options->getNamespace();
+ if (!isset($this->data[$ns])) {
+ $this->data[$ns] = array();
+ }
+
+ $result = array();
+ $data = & $this->data[$ns];
+ $now = microtime(true);
+ foreach ($normalizedKeyValuePairs as $normalizedKey => $value) {
+ if (isset($data[$normalizedKey])) {
+ $result[] = $normalizedKey;
+ } else {
+ $data[$normalizedKey] = array($value, $now);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to replace an existing item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalReplaceItem(& $normalizedKey, & $value)
+ {
+ $ns = $this->getOptions()->getNamespace();
+ if (!isset($this->data[$ns][$normalizedKey])) {
+ return false;
+ }
+ $this->data[$ns][$normalizedKey] = array($value, microtime(true));
+
+ return true;
+ }
+
+ /**
+ * Internal method to replace multiple existing items.
+ *
+ * @param array $normalizedKeyValuePairs
+ * @return array Array of not stored keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalReplaceItems(array & $normalizedKeyValuePairs)
+ {
+ $ns = $this->getOptions()->getNamespace();
+ if (!isset($this->data[$ns])) {
+ return array_keys($normalizedKeyValuePairs);
+ }
+
+ $result = array();
+ $data = & $this->data[$ns];
+ foreach ($normalizedKeyValuePairs as $normalizedKey => $value) {
+ if (!isset($data[$normalizedKey])) {
+ $result[] = $normalizedKey;
+ } else {
+ $data[$normalizedKey] = array($value, microtime(true));
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to reset lifetime of an item
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalTouchItem(& $normalizedKey)
+ {
+ $ns = $this->getOptions()->getNamespace();
+
+ if (!isset($this->data[$ns][$normalizedKey])) {
+ return false;
+ }
+
+ $this->data[$ns][$normalizedKey][1] = microtime(true);
+ return true;
+ }
+
+ /**
+ * Internal method to remove an item.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalRemoveItem(& $normalizedKey)
+ {
+ $ns = $this->getOptions()->getNamespace();
+ if (!isset($this->data[$ns][$normalizedKey])) {
+ return false;
+ }
+
+ unset($this->data[$ns][$normalizedKey]);
+
+ // remove empty namespace
+ if (!$this->data[$ns]) {
+ unset($this->data[$ns]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Internal method to increment an item.
+ *
+ * @param string $normalizedKey
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalIncrementItem(& $normalizedKey, & $value)
+ {
+ $ns = $this->getOptions()->getNamespace();
+ if (isset($this->data[$ns][$normalizedKey])) {
+ $data = & $this->data[$ns][$normalizedKey];
+ $data[0]+= $value;
+ $data[1] = microtime(true);
+ $newValue = $data[0];
+ } else {
+ // initial value
+ $newValue = $value;
+ $this->data[$ns][$normalizedKey] = array($newValue, microtime(true));
+ }
+
+ return $newValue;
+ }
+
+ /**
+ * Internal method to decrement an item.
+ *
+ * @param string $normalizedKey
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalDecrementItem(& $normalizedKey, & $value)
+ {
+ $ns = $this->getOptions()->getNamespace();
+ if (isset($this->data[$ns][$normalizedKey])) {
+ $data = & $this->data[$ns][$normalizedKey];
+ $data[0]-= $value;
+ $data[1] = microtime(true);
+ $newValue = $data[0];
+ } else {
+ // initial value
+ $newValue = -$value;
+ $this->data[$ns][$normalizedKey] = array($newValue, microtime(true));
+ }
+
+ return $newValue;
+ }
+
+ /* status */
+
+ /**
+ * Internal method to get capabilities of this adapter
+ *
+ * @return Capabilities
+ */
+ protected function internalGetCapabilities()
+ {
+ if ($this->capabilities === null) {
+ $this->capabilityMarker = new stdClass();
+ $this->capabilities = new Capabilities(
+ $this,
+ $this->capabilityMarker,
+ array(
+ 'supportedDatatypes' => array(
+ 'NULL' => true,
+ 'boolean' => true,
+ 'integer' => true,
+ 'double' => true,
+ 'string' => true,
+ 'array' => true,
+ 'object' => true,
+ 'resource' => true,
+ ),
+ 'supportedMetadata' => array('mtime'),
+ 'minTtl' => 1,
+ 'maxTtl' => PHP_INT_MAX,
+ 'staticTtl' => false,
+ 'ttlPrecision' => 0.05,
+ 'expiredRead' => true,
+ 'maxKeyLength' => 0,
+ 'namespaceIsPrefix' => false,
+ 'namespaceSeparator' => '',
+ )
+ );
+ }
+
+ return $this->capabilities;
+ }
+
+ /* internal */
+
+ /**
+ * Has space available to store items?
+ *
+ * @return bool
+ */
+ protected function hasAvailableSpace()
+ {
+ $total = $this->getOptions()->getMemoryLimit();
+
+ // check memory limit disabled
+ if ($total <= 0) {
+ return true;
+ }
+
+ $free = $total - (float) memory_get_usage(true);
+ return ($free > 0);
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/MemoryOptions.php b/library/Zend/Cache/Storage/Adapter/MemoryOptions.php
new file mode 100755
index 0000000000..be48418c64
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/MemoryOptions.php
@@ -0,0 +1,112 @@
+normalizeMemoryLimit($memoryLimit);
+
+ if ($this->memoryLimit != $memoryLimit) {
+ $this->triggerOptionEvent('memory_limit', $memoryLimit);
+ $this->memoryLimit = $memoryLimit;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get memory limit
+ *
+ * If the used memory of PHP exceeds this limit an OutOfSpaceException
+ * will be thrown.
+ *
+ * @return int
+ */
+ public function getMemoryLimit()
+ {
+ if ($this->memoryLimit === null) {
+ // By default use half of PHP's memory limit if possible
+ $memoryLimit = $this->normalizeMemoryLimit(ini_get('memory_limit'));
+ if ($memoryLimit >= 0) {
+ $this->memoryLimit = (int) ($memoryLimit / 2);
+ } else {
+ // disable memory limit
+ $this->memoryLimit = 0;
+ }
+ }
+
+ return $this->memoryLimit;
+ }
+
+ /**
+ * Normalized a given value of memory limit into the number of bytes
+ *
+ * @param string|int $value
+ * @throws Exception\InvalidArgumentException
+ * @return int
+ */
+ protected function normalizeMemoryLimit($value)
+ {
+ if (is_numeric($value)) {
+ return (int) $value;
+ }
+
+ if (!preg_match('/(\-?\d+)\s*(\w*)/', ini_get('memory_limit'), $matches)) {
+ throw new Exception\InvalidArgumentException("Invalid memory limit '{$value}'");
+ }
+
+ $value = (int) $matches[1];
+ if ($value <= 0) {
+ return 0;
+ }
+
+ switch (strtoupper($matches[2])) {
+ case 'G':
+ $value*= 1024;
+ // no break
+
+ case 'M':
+ $value*= 1024;
+ // no break
+
+ case 'K':
+ $value*= 1024;
+ // no break
+ }
+
+ return $value;
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/Redis.php b/library/Zend/Cache/Storage/Adapter/Redis.php
new file mode 100755
index 0000000000..1adb3d4a41
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/Redis.php
@@ -0,0 +1,433 @@
+initialized;
+ $this->getEventManager()->attach('option', function ($event) use (& $initialized) {
+ $initialized = false;
+ });
+ }
+
+ /**
+ * Get Redis resource
+ *
+ * @return RedisResource
+ */
+ protected function getRedisResource()
+ {
+ if (!$this->initialized) {
+ $options = $this->getOptions();
+
+ // get resource manager and resource id
+ $this->resourceManager = $options->getResourceManager();
+ $this->resourceId = $options->getResourceId();
+
+ // init namespace prefix
+ $namespace = $options->getNamespace();
+ if ($namespace !== '') {
+ $this->namespacePrefix = $namespace . $options->getNamespaceSeparator();
+ } else {
+ $this->namespacePrefix = '';
+ }
+
+ // update initialized flag
+ $this->initialized = true;
+ }
+
+ return $this->resourceManager->getResource($this->resourceId);
+ }
+
+ /* options */
+
+ /**
+ * Set options.
+ *
+ * @param array|Traversable|RedisOptions $options
+ * @return Redis
+ * @see getOptions()
+ */
+ public function setOptions($options)
+ {
+ if (!$options instanceof RedisOptions) {
+ $options = new RedisOptions($options);
+ }
+ return parent::setOptions($options);
+ }
+
+ /**
+ * Get options.
+ *
+ * @return RedisOptions
+ * @see setOptions()
+ */
+ public function getOptions()
+ {
+ if (!$this->options) {
+ $this->setOptions(new RedisOptions());
+ }
+ return $this->options;
+ }
+
+ /**
+ * Internal method to get an item.
+ *
+ * @param string &$normalizedKey Key where to store data
+ * @param bool &$success If the operation was successfull
+ * @param mixed &$casToken Token
+ * @return mixed Data on success, false on key not found
+ * @throws Exception\RuntimeException
+ */
+ protected function internalGetItem(& $normalizedKey, & $success = null, & $casToken = null)
+ {
+ $redis = $this->getRedisResource();
+ try {
+ $value = $redis->get($this->namespacePrefix . $normalizedKey);
+ } catch (RedisResourceException $e) {
+ throw new Exception\RuntimeException($redis->getLastError(), $e->getCode(), $e);
+ }
+
+ if ($value === false) {
+ $success = false;
+ return null;
+ }
+
+ $success = true;
+ $casToken = $value;
+ return $value;
+ }
+
+ /**
+ * Internal method to get multiple items.
+ *
+ * @param array &$normalizedKeys Array of keys to be obtained
+ *
+ * @return array Associative array of keys and values
+ * @throws Exception\RuntimeException
+ */
+ protected function internalGetItems(array & $normalizedKeys)
+ {
+ $redis = $this->getRedisResource();
+
+ $namespacedKeys = array();
+ foreach ($normalizedKeys as $normalizedKey) {
+ $namespacedKeys[] = $this->namespacePrefix . $normalizedKey;
+ }
+
+ try {
+ $results = $redis->mGet($namespacedKeys);
+ } catch (RedisResourceException $e) {
+ throw new Exception\RuntimeException($redis->getLastError(), $e->getCode(), $e);
+ }
+ //combine the key => value pairs and remove all missing values
+ return array_filter(
+ array_combine($normalizedKeys, $results),
+ function ($value) {
+ return $value !== false;
+ }
+ );
+ }
+
+ /**
+ * Internal method to test if an item exists.
+ *
+ * @param string &$normalizedKey Normalized key which will be checked
+ *
+ * @return bool
+ * @throws Exception\RuntimeException
+ */
+ protected function internalHasItem(& $normalizedKey)
+ {
+ $redis = $this->getRedisResource();
+ try {
+ return $redis->exists($this->namespacePrefix . $normalizedKey);
+ } catch (RedisResourceException $e) {
+ throw new Exception\RuntimeException($redis->getLastError(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Internal method to store an item.
+ *
+ * @param string &$normalizedKey Key in Redis under which value will be saved
+ * @param mixed &$value Value to store under cache key
+ *
+ * @return bool
+ * @throws Exception\RuntimeException
+ */
+ protected function internalSetItem(& $normalizedKey, & $value)
+ {
+ $redis = $this->getRedisResource();
+ $ttl = $this->getOptions()->getTtl();
+
+ try {
+ if ($ttl) {
+ if ($this->resourceManager->getMajorVersion($this->resourceId) < 2) {
+ throw new Exception\UnsupportedMethodCallException("To use ttl you need version >= 2.0.0");
+ }
+ $success = $redis->setex($this->namespacePrefix . $normalizedKey, $ttl, $value);
+ } else {
+ $success = $redis->set($this->namespacePrefix . $normalizedKey, $value);
+ }
+ } catch (RedisResourceException $e) {
+ throw new Exception\RuntimeException($redis->getLastError(), $e->getCode(), $e);
+ }
+
+ return $success;
+ }
+
+ /**
+ * Internal method to store multiple items.
+ *
+ * @param array &$normalizedKeyValuePairs An array of normalized key/value pairs
+ *
+ * @return array Array of not stored keys
+ * @throws Exception\RuntimeException
+ */
+ protected function internalSetItems(array & $normalizedKeyValuePairs)
+ {
+ $redis = $this->getRedisResource();
+ $ttl = $this->getOptions()->getTtl();
+
+ $namespacedKeyValuePairs = array();
+ foreach ($normalizedKeyValuePairs as $normalizedKey => $value) {
+ $namespacedKeyValuePairs[$this->namespacePrefix . $normalizedKey] = $value;
+ }
+ try {
+ if ($ttl > 0) {
+ //check if ttl is supported
+ if ($this->resourceManager->getMajorVersion($this->resourceId) < 2) {
+ throw new Exception\UnsupportedMethodCallException("To use ttl you need version >= 2.0.0");
+ }
+ //mSet does not allow ttl, so use transaction
+ $transaction = $redis->multi();
+ foreach ($namespacedKeyValuePairs as $key => $value) {
+ $transaction->setex($key, $ttl, $value);
+ }
+ $success = $transaction->exec();
+ } else {
+ $success = $redis->mSet($namespacedKeyValuePairs);
+ }
+ } catch (RedisResourceException $e) {
+ throw new Exception\RuntimeException($redis->getLastError(), $e->getCode(), $e);
+ }
+ if (!$success) {
+ throw new Exception\RuntimeException($redis->getLastError());
+ }
+
+ return array();
+ }
+
+ /**
+ * Add an item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\RuntimeException
+ */
+ protected function internalAddItem(& $normalizedKey, & $value)
+ {
+ $redis = $this->getRedisResource();
+ try {
+ return $redis->setnx($this->namespacePrefix . $normalizedKey, $value);
+ } catch (RedisResourceException $e) {
+ throw new Exception\RuntimeException($redis->getLastError(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Internal method to remove an item.
+ *
+ * @param string &$normalizedKey Key which will be removed
+ *
+ * @return bool
+ * @throws Exception\RuntimeException
+ */
+ protected function internalRemoveItem(& $normalizedKey)
+ {
+ $redis = $this->getRedisResource();
+ try {
+ return (bool) $redis->delete($this->namespacePrefix . $normalizedKey);
+ } catch (RedisResourceException $e) {
+ throw new Exception\RuntimeException($redis->getLastError(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Internal method to increment an item.
+ *
+ * @param string $normalizedKey
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ * @throws Exception\RuntimeException
+ */
+ protected function internalIncrementItem(& $normalizedKey, & $value)
+ {
+ $redis = $this->getRedisResource();
+ try {
+ return $redis->incrBy($this->namespacePrefix . $normalizedKey, $value);
+ } catch (RedisResourceException $e) {
+ throw new Exception\RuntimeException($redis->getLastError(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Internal method to decrement an item.
+ *
+ * @param string $normalizedKey
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ * @throws Exception\RuntimeException
+ */
+ protected function internalDecrementItem(& $normalizedKey, & $value)
+ {
+ $redis = $this->getRedisResource();
+ try {
+ return $redis->decrBy($this->namespacePrefix . $normalizedKey, $value);
+ } catch (RedisResourceException $e) {
+ throw new Exception\RuntimeException($redis->getLastError(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Flush currently set DB
+ *
+ * @return bool
+ * @throws Exception\RuntimeException
+ */
+ public function flush()
+ {
+ $redis = $this->getRedisResource();
+ try {
+ return $redis->flushDB();
+ } catch (RedisResourceException $e) {
+ throw new Exception\RuntimeException($redis->getLastError(), $e->getCode(), $e);
+ }
+ }
+
+ /* TotalSpaceCapableInterface */
+
+ /**
+ * Get total space in bytes
+ *
+ * @return int|float
+ */
+ public function getTotalSpace()
+ {
+ $redis = $this->getRedisResource();
+ try {
+ $info = $redis->info();
+ } catch (RedisResourceException $e) {
+ throw new Exception\RuntimeException($redis->getLastError(), $e->getCode(), $e);
+ }
+
+ return $info['used_memory'];
+ }
+
+ /* status */
+
+ /**
+ * Internal method to get capabilities of this adapter
+ *
+ * @return Capabilities
+ */
+ protected function internalGetCapabilities()
+ {
+ if ($this->capabilities === null) {
+ $this->capabilityMarker = new stdClass();
+ $minTtl = $this->resourceManager->getMajorVersion($this->resourceId) < 2 ? 0 : 1;
+ //without serialization redis supports only strings for simple
+ //get/set methods
+ $this->capabilities = new Capabilities(
+ $this,
+ $this->capabilityMarker,
+ array(
+ 'supportedDatatypes' => array(
+ 'NULL' => 'string',
+ 'boolean' => 'string',
+ 'integer' => 'string',
+ 'double' => 'string',
+ 'string' => true,
+ 'array' => false,
+ 'object' => false,
+ 'resource' => false,
+ ),
+ 'supportedMetadata' => array(),
+ 'minTtl' => $minTtl,
+ 'maxTtl' => 0,
+ 'staticTtl' => true,
+ 'ttlPrecision' => 1,
+ 'useRequestTime' => false,
+ 'expiredRead' => false,
+ 'maxKeyLength' => 255,
+ 'namespaceIsPrefix' => true,
+ )
+ );
+ }
+
+ return $this->capabilities;
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/RedisOptions.php b/library/Zend/Cache/Storage/Adapter/RedisOptions.php
new file mode 100755
index 0000000000..f5e6748750
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/RedisOptions.php
@@ -0,0 +1,263 @@
+namespaceSeparator !== $namespaceSeparator) {
+ $this->triggerOptionEvent('namespace_separator', $namespaceSeparator);
+ $this->namespaceSeparator = $namespaceSeparator;
+ }
+ return $this;
+ }
+
+ /**
+ * Get namespace separator
+ *
+ * @return string
+ */
+ public function getNamespaceSeparator()
+ {
+ return $this->namespaceSeparator;
+ }
+
+ /**
+ * Set the redis resource manager to use
+ *
+ * @param null|RedisResourceManager $resourceManager
+ * @return RedisOptions
+ */
+ public function setResourceManager(RedisResourceManager $resourceManager = null)
+ {
+ if ($this->resourceManager !== $resourceManager) {
+ $this->triggerOptionEvent('resource_manager', $resourceManager);
+ $this->resourceManager = $resourceManager;
+ }
+ return $this;
+ }
+
+ /**
+ * Get the redis resource manager
+ *
+ * @return RedisResourceManager
+ */
+ public function getResourceManager()
+ {
+ if (!$this->resourceManager) {
+ $this->resourceManager = new RedisResourceManager();
+ }
+ return $this->resourceManager;
+ }
+
+ /**
+ * Get the redis resource id
+ *
+ * @return string
+ */
+ public function getResourceId()
+ {
+ return $this->resourceId;
+ }
+
+ /**
+ * Set the redis resource id
+ *
+ * @param string $resourceId
+ * @return RedisOptions
+ */
+ public function setResourceId($resourceId)
+ {
+ $resourceId = (string) $resourceId;
+ if ($this->resourceId !== $resourceId) {
+ $this->triggerOptionEvent('resource_id', $resourceId);
+ $this->resourceId = $resourceId;
+ }
+ return $this;
+ }
+
+ /**
+ * Get the persistent id
+ *
+ * @return string
+ */
+ public function getPersistentId()
+ {
+ return $this->getResourceManager()->getPersistentId($this->getResourceId());
+ }
+
+ /**
+ * Set the persistent id
+ *
+ * @param string $persistentId
+ * @return RedisOptions
+ */
+ public function setPersistentId($persistentId)
+ {
+ $this->triggerOptionEvent('persistent_id', $persistentId);
+ $this->getResourceManager()->setPersistentId($this->getResourceId(), $persistentId);
+ return $this;
+ }
+
+ /**
+ * Set redis options
+ *
+ * @param array $libOptions
+ * @return RedisOptions
+ * @link http://github.com/nicolasff/phpredis#setoption
+ */
+ public function setLibOptions(array $libOptions)
+ {
+ $this->triggerOptionEvent('lib_option', $libOptions);
+ $this->getResourceManager()->setLibOptions($this->getResourceId(), $libOptions);
+ return $this;
+ }
+
+ /**
+ * Get redis options
+ *
+ * @return array
+ * @link http://github.com/nicolasff/phpredis#setoption
+ */
+ public function getLibOptions()
+ {
+ return $this->getResourceManager()->getLibOptions($this->getResourceId());
+ }
+
+ /**
+ * Set server
+ *
+ * Server can be described as follows:
+ * - URI: /path/to/sock.sock
+ * - Assoc: array('host' => [, 'port' => [, 'timeout' => ]])
+ * - List: array([, , [, ]])
+ *
+ * @param string|array $server
+ *
+ * @return RedisOptions
+ */
+ public function setServer($server)
+ {
+ $this->getResourceManager()->setServer($this->getResourceId(), $server);
+ return $this;
+ }
+
+ /**
+ * Get server
+ *
+ * @return array array('host' => [, 'port' => [, 'timeout' => ]])
+ */
+ public function getServer()
+ {
+ return $this->getResourceManager()->getServer($this->getResourceId());
+ }
+
+ /**
+ * Set resource database number
+ *
+ * @param int $database Database number
+ *
+ * @return RedisOptions
+ */
+ public function setDatabase($database)
+ {
+ $this->getResourceManager()->setDatabase($this->getResourceId(), $database);
+ return $this;
+ }
+
+ /**
+ * Get resource database number
+ *
+ * @return int Database number
+ */
+ public function getDatabase()
+ {
+ return $this->getResourceManager()->getDatabase($this->getResourceId());
+ }
+
+ /**
+ * Set resource password
+ *
+ * @param string $password Password
+ *
+ * @return RedisOptions
+ */
+ public function setPassword($password)
+ {
+ $this->getResourceManager()->setPassword($this->getResourceId(), $password);
+ return $this;
+ }
+
+ /**
+ * Get resource password
+ *
+ * @return string
+ */
+ public function getPassword()
+ {
+ return $this->getResourceManager()->getPassword($this->getResourceId());
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/RedisResourceManager.php b/library/Zend/Cache/Storage/Adapter/RedisResourceManager.php
new file mode 100755
index 0000000000..ed8ee21479
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/RedisResourceManager.php
@@ -0,0 +1,645 @@
+resources[$id]);
+ }
+
+ /**
+ * Get redis server version
+ *
+ * @param string $id
+ * @return int
+ * @throws Exception\RuntimeException
+ */
+ public function getMajorVersion($id)
+ {
+ if (!$this->hasResource($id)) {
+ throw new Exception\RuntimeException("No resource with id '{$id}'");
+ }
+
+ $resource = & $this->resources[$id];
+ return (int) $resource['version'];
+ }
+
+ /**
+ * Get redis server version
+ *
+ * @deprecated 2.2.2 Use getMajorVersion instead
+ *
+ * @param string $id
+ * @return int
+ * @throws Exception\RuntimeException
+ */
+ public function getMayorVersion($id)
+ {
+ return $this->getMajorVersion($id);
+ }
+
+ /**
+ * Get redis resource database
+ *
+ * @param string $id
+ * @return string
+ */
+ public function getDatabase($id)
+ {
+ if (!$this->hasResource($id)) {
+ throw new Exception\RuntimeException("No resource with id '{$id}'");
+ }
+
+ $resource = & $this->resources[$id];
+ return $resource['database'];
+ }
+
+ /**
+ * Get redis resource password
+ *
+ * @param string $id
+ * @return string
+ */
+ public function getPassword($id)
+ {
+ if (!$this->hasResource($id)) {
+ throw new Exception\RuntimeException("No resource with id '{$id}'");
+ }
+
+ $resource = & $this->resources[$id];
+ return $resource['password'];
+ }
+
+ /**
+ * Gets a redis resource
+ *
+ * @param string $id
+ * @return RedisResource
+ * @throws Exception\RuntimeException
+ */
+ public function getResource($id)
+ {
+ if (!$this->hasResource($id)) {
+ throw new Exception\RuntimeException("No resource with id '{$id}'");
+ }
+
+ $resource = & $this->resources[$id];
+ if ($resource['resource'] instanceof RedisResource) {
+ //in case new server was set then connect
+ if (!$resource['initialized']) {
+ $this->connect($resource);
+ }
+ $info = $resource['resource']->info();
+ $resource['version'] = $info['redis_version'];
+ return $resource['resource'];
+ }
+
+ $redis = new RedisResource();
+
+ $resource['resource'] = $redis;
+ $this->connect($resource);
+
+ foreach ($resource['lib_options'] as $k => $v) {
+ $redis->setOption($k, $v);
+ }
+
+ $info = $redis->info();
+ $resource['version'] = $info['redis_version'];
+ $this->resources[$id]['resource'] = $redis;
+ return $redis;
+ }
+
+ /**
+ * Get server
+ * @param string $id
+ * @throws Exception\RuntimeException
+ * @return array array('host' => [, 'port' => [, 'timeout' => ]])
+ */
+ public function getServer($id)
+ {
+ if (!$this->hasResource($id)) {
+ throw new Exception\RuntimeException("No resource with id '{$id}'");
+ }
+
+ $resource = & $this->resources[$id];
+ return $resource['server'];
+ }
+
+ /**
+ * Normalize one server into the following format:
+ * array('host' => [, 'port' => [, 'timeout' => ]])
+ *
+ * @param string|array $server
+ *
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function normalizeServer(&$server)
+ {
+ $host = null;
+ $port = null;
+ $timeout = 0;
+
+ // convert a single server into an array
+ if ($server instanceof Traversable) {
+ $server = ArrayUtils::iteratorToArray($server);
+ }
+
+ if (is_array($server)) {
+ // array([, [, ]])
+ if (isset($server[0])) {
+ $host = (string) $server[0];
+ $port = isset($server[1]) ? (int) $server[1] : $port;
+ $timeout = isset($server[2]) ? (int) $server[2] : $timeout;
+ }
+
+ // array('host' => [, 'port' => , ['timeout' => ]])
+ if (!isset($server[0]) && isset($server['host'])) {
+ $host = (string) $server['host'];
+ $port = isset($server['port']) ? (int) $server['port'] : $port;
+ $timeout = isset($server['timeout']) ? (int) $server['timeout'] : $timeout;
+ }
+ } else {
+ // parse server from URI host{:?port}
+ $server = trim($server);
+ if (strpos($server, '/') !== 0) {
+ //non unix domain socket connection
+ $server = parse_url($server);
+ } else {
+ $server = array('host' => $server);
+ }
+ if (!$server) {
+ throw new Exception\InvalidArgumentException("Invalid server given");
+ }
+
+ $host = $server['host'];
+ $port = isset($server['port']) ? (int) $server['port'] : $port;
+ $timeout = isset($server['timeout']) ? (int) $server['timeout'] : $timeout;
+ }
+
+ if (!$host) {
+ throw new Exception\InvalidArgumentException('Missing required server host');
+ }
+
+ $server = array(
+ 'host' => $host,
+ 'port' => $port,
+ 'timeout' => $timeout,
+ );
+ }
+
+ /**
+ * Extract password to be used on connection
+ *
+ * @param mixed $resource
+ * @param mixed $serverUri
+ *
+ * @return string|null
+ */
+ protected function extractPassword($resource, $serverUri)
+ {
+ if (! empty($resource['password'])) {
+ return $resource['password'];
+ }
+
+ if (! is_string($serverUri)) {
+ return null;
+ }
+
+ // parse server from URI host{:?port}
+ $server = trim($serverUri);
+
+ if (strpos($server, '/') === 0) {
+ return null;
+ }
+
+ //non unix domain socket connection
+ $server = parse_url($server);
+
+ return isset($server['pass']) ? $server['pass'] : null;
+ }
+
+ /**
+ * Connects to redis server
+ *
+ *
+ * @param array & $resource
+ *
+ * @return null
+ * @throws Exception\RuntimeException
+ */
+ protected function connect(array & $resource)
+ {
+ $server = $resource['server'];
+ $redis = $resource['resource'];
+ if ($resource['persistent_id'] !== '') {
+ //connect or reuse persistent connection
+ $success = $redis->pconnect($server['host'], $server['port'], $server['timeout'], $server['persistent_id']);
+ } elseif ($server['port']) {
+ $success = $redis->connect($server['host'], $server['port'], $server['timeout']);
+ } elseif ($server['timeout']) {
+ //connect through unix domain socket
+ $success = $redis->connect($server['host'], $server['timeout']);
+ } else {
+ $success = $redis->connect($server['host']);
+ }
+
+ if (!$success) {
+ throw new Exception\RuntimeException('Could not estabilish connection with Redis instance');
+ }
+
+ $resource['initialized'] = true;
+ if ($resource['password']) {
+ $redis->auth($resource['password']);
+ }
+ $redis->select($resource['database']);
+ }
+
+ /**
+ * Set a resource
+ *
+ * @param string $id
+ * @param array|Traversable|RedisResource $resource
+ * @return RedisResourceManager Fluent interface
+ */
+ public function setResource($id, $resource)
+ {
+ $id = (string) $id;
+ //TODO: how to get back redis connection info from resource?
+ $defaults = array(
+ 'persistent_id' => '',
+ 'lib_options' => array(),
+ 'server' => array(),
+ 'password' => '',
+ 'database' => 0,
+ 'resource' => null,
+ 'initialized' => false,
+ 'version' => 0,
+ );
+ if (!$resource instanceof RedisResource) {
+ if ($resource instanceof Traversable) {
+ $resource = ArrayUtils::iteratorToArray($resource);
+ } elseif (!is_array($resource)) {
+ throw new Exception\InvalidArgumentException(
+ 'Resource must be an instance of an array or Traversable'
+ );
+ }
+
+ $resource = array_merge($defaults, $resource);
+ // normalize and validate params
+ $this->normalizePersistentId($resource['persistent_id']);
+ $this->normalizeLibOptions($resource['lib_options']);
+
+ // #6495 note: order is important here, as `normalizeServer` applies destructive
+ // transformations on $resource['server']
+ $resource['password'] = $this->extractPassword($resource, $resource['server']);
+
+ $this->normalizeServer($resource['server']);
+ } else {
+ //there are two ways of determining if redis is already initialized
+ //with connect function:
+ //1) pinging server
+ //2) checking undocumented property socket which is available only
+ //after successful connect
+ $resource = array_merge(
+ $defaults,
+ array(
+ 'resource' => $resource,
+ 'initialized' => isset($resource->socket),
+ )
+ );
+ }
+ $this->resources[$id] = $resource;
+ return $this;
+ }
+
+ /**
+ * Remove a resource
+ *
+ * @param string $id
+ * @return RedisResourceManager Fluent interface
+ */
+ public function removeResource($id)
+ {
+ unset($this->resources[$id]);
+ return $this;
+ }
+
+ /**
+ * Set the persistent id
+ *
+ * @param string $id
+ * @param string $persistentId
+ * @return RedisResourceManager Fluent interface
+ * @throws Exception\RuntimeException
+ */
+ public function setPersistentId($id, $persistentId)
+ {
+ if (!$this->hasResource($id)) {
+ return $this->setResource($id, array(
+ 'persistent_id' => $persistentId
+ ));
+ }
+
+ $resource = & $this->resources[$id];
+ if ($resource instanceof RedisResource) {
+ throw new Exception\RuntimeException(
+ "Can't change persistent id of resource {$id} after instanziation"
+ );
+ }
+
+ $this->normalizePersistentId($persistentId);
+ $resource['persistent_id'] = $persistentId;
+
+ return $this;
+ }
+
+ /**
+ * Get the persistent id
+ *
+ * @param string $id
+ * @return string
+ * @throws Exception\RuntimeException
+ */
+ public function getPersistentId($id)
+ {
+ if (!$this->hasResource($id)) {
+ throw new Exception\RuntimeException("No resource with id '{$id}'");
+ }
+
+ $resource = & $this->resources[$id];
+
+ if ($resource instanceof RedisResource) {
+ throw new Exception\RuntimeException(
+ "Can't get persistent id of an instantiated redis resource"
+ );
+ }
+
+ return $resource['persistent_id'];
+ }
+
+ /**
+ * Normalize the persistent id
+ *
+ * @param string $persistentId
+ */
+ protected function normalizePersistentId(& $persistentId)
+ {
+ $persistentId = (string) $persistentId;
+ }
+
+ /**
+ * Set Redis options
+ *
+ * @param string $id
+ * @param array $libOptions
+ * @return RedisResourceManager Fluent interface
+ */
+ public function setLibOptions($id, array $libOptions)
+ {
+ if (!$this->hasResource($id)) {
+ return $this->setResource($id, array(
+ 'lib_options' => $libOptions
+ ));
+ }
+
+ $this->normalizeLibOptions($libOptions);
+ $resource = & $this->resources[$id];
+
+ $resource['lib_options'] = $libOptions;
+
+ if ($resource['resource'] instanceof RedisResource) {
+ $redis = & $resource['resource'];
+ if (method_exists($redis, 'setOptions')) {
+ $redis->setOptions($libOptions);
+ } else {
+ foreach ($libOptions as $key => $value) {
+ $redis->setOption($key, $value);
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Redis options
+ *
+ * @param string $id
+ * @return array
+ * @throws Exception\RuntimeException
+ */
+ public function getLibOptions($id)
+ {
+ if (!$this->hasResource($id)) {
+ throw new Exception\RuntimeException("No resource with id '{$id}'");
+ }
+
+ $resource = & $this->resources[$id];
+
+ if ($resource instanceof RedisResource) {
+ $libOptions = array();
+ $reflection = new ReflectionClass('Redis');
+ $constants = $reflection->getConstants();
+ foreach ($constants as $constName => $constValue) {
+ if (substr($constName, 0, 4) == 'OPT_') {
+ $libOptions[$constValue] = $resource->getOption($constValue);
+ }
+ }
+ return $libOptions;
+ }
+ return $resource['lib_options'];
+ }
+
+ /**
+ * Set one Redis option
+ *
+ * @param string $id
+ * @param string|int $key
+ * @param mixed $value
+ * @return RedisResourceManager Fluent interface
+ */
+ public function setLibOption($id, $key, $value)
+ {
+ return $this->setLibOptions($id, array($key => $value));
+ }
+
+ /**
+ * Get one Redis option
+ *
+ * @param string $id
+ * @param string|int $key
+ * @return mixed
+ * @throws Exception\RuntimeException
+ */
+ public function getLibOption($id, $key)
+ {
+ if (!$this->hasResource($id)) {
+ throw new Exception\RuntimeException("No resource with id '{$id}'");
+ }
+
+ $this->normalizeLibOptionKey($key);
+ $resource = & $this->resources[$id];
+
+ if ($resource instanceof RedisResource) {
+ return $resource->getOption($key);
+ }
+
+ return isset($resource['lib_options'][$key]) ? $resource['lib_options'][$key] : null;
+ }
+
+ /**
+ * Normalize Redis options
+ *
+ * @param array|Traversable $libOptions
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function normalizeLibOptions(& $libOptions)
+ {
+ if (!is_array($libOptions) && !($libOptions instanceof Traversable)) {
+ throw new Exception\InvalidArgumentException(
+ "Lib-Options must be an array or an instance of Traversable"
+ );
+ }
+
+ $result = array();
+ foreach ($libOptions as $key => $value) {
+ $this->normalizeLibOptionKey($key);
+ $result[$key] = $value;
+ }
+
+ $libOptions = $result;
+ }
+
+ /**
+ * Convert option name into it's constant value
+ *
+ * @param string|int $key
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function normalizeLibOptionKey(& $key)
+ {
+ // convert option name into it's constant value
+ if (is_string($key)) {
+ $const = 'Redis::OPT_' . str_replace(array(' ', '-'), '_', strtoupper($key));
+ if (!defined($const)) {
+ throw new Exception\InvalidArgumentException("Unknown redis option '{$key}' ({$const})");
+ }
+ $key = constant($const);
+ } else {
+ $key = (int) $key;
+ }
+ }
+
+ /**
+ * Set server
+ *
+ * Server can be described as follows:
+ * - URI: /path/to/sock.sock
+ * - Assoc: array('host' => [, 'port' => [, 'timeout' => ]])
+ * - List: array([, , [, ]])
+ *
+ * @param string $id
+ * @param string|array $server
+ * @return RedisResourceManager
+ */
+ public function setServer($id, $server)
+ {
+ if (!$this->hasResource($id)) {
+ return $this->setResource($id, array(
+ 'server' => $server
+ ));
+ }
+
+ $this->normalizeServer($server);
+
+ $resource = & $this->resources[$id];
+ $resource['password'] = $this->extractPassword($resource, $server);
+
+ if ($resource['resource'] instanceof RedisResource) {
+ $resourceParams = array('server' => $server);
+
+ if (! empty($resource['password'])) {
+ $resourceParams['password'] = $resource['password'];
+ }
+
+ $this->setResource($id, $resourceParams);
+ } else {
+ $resource['server'] = $server;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set redis password
+ *
+ * @param string $id
+ * @param string $password
+ * @return RedisResource
+ */
+ public function setPassword($id, $password)
+ {
+ if (!$this->hasResource($id)) {
+ return $this->setResource($id, array(
+ 'password' => $password,
+ ));
+ }
+
+ $resource = & $this->resources[$id];
+ $resource['password'] = $password;
+ $resource['initialized'] = false;
+ return $this;
+ }
+
+ /**
+ * Set redis database number
+ *
+ * @param string $id
+ * @param int $database
+ * @return RedisResource
+ */
+ public function setDatabase($id, $database)
+ {
+ if (!$this->hasResource($id)) {
+ return $this->setResource($id, array(
+ 'database' => (int) $database,
+ ));
+ }
+
+ $resource = & $this->resources[$id];
+ $resource['database'] = $database;
+ $resource['initialized'] = false;
+ return $this;
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/Session.php b/library/Zend/Cache/Storage/Adapter/Session.php
new file mode 100755
index 0000000000..4603c23149
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/Session.php
@@ -0,0 +1,546 @@
+options) {
+ $this->setOptions(new SessionOptions());
+ }
+ return $this->options;
+ }
+
+ /**
+ * Get the session container
+ *
+ * @return SessionContainer
+ */
+ protected function getSessionContainer()
+ {
+ $sessionContainer = $this->getOptions()->getSessionContainer();
+ if (!$sessionContainer) {
+ throw new Exception\RuntimeException("No session container configured");
+ }
+ return $sessionContainer;
+ }
+
+ /* IterableInterface */
+
+ /**
+ * Get the storage iterator
+ *
+ * @return KeyListIterator
+ */
+ public function getIterator()
+ {
+ $cntr = $this->getSessionContainer();
+ $ns = $this->getOptions()->getNamespace();
+
+ if ($cntr->offsetExists($ns)) {
+ $keys = array_keys($cntr->offsetGet($ns));
+ } else {
+ $keys = array();
+ }
+
+ return new KeyListIterator($this, $keys);
+ }
+
+ /* FlushableInterface */
+
+ /**
+ * Flush the whole session container
+ *
+ * @return bool
+ */
+ public function flush()
+ {
+ $this->getSessionContainer()->exchangeArray(array());
+ return true;
+ }
+
+ /* ClearByPrefixInterface */
+
+ /**
+ * Remove items matching given prefix
+ *
+ * @param string $prefix
+ * @return bool
+ */
+ public function clearByPrefix($prefix)
+ {
+ $prefix = (string) $prefix;
+ if ($prefix === '') {
+ throw new Exception\InvalidArgumentException('No prefix given');
+ }
+
+ $cntr = $this->getSessionContainer();
+ $ns = $this->getOptions()->getNamespace();
+
+ if (!$cntr->offsetExists($ns)) {
+ return true;
+ }
+
+ $data = $cntr->offsetGet($ns);
+ $prefixL = strlen($prefix);
+ foreach ($data as $key => & $item) {
+ if (substr($key, 0, $prefixL) === $prefix) {
+ unset($data[$key]);
+ }
+ }
+ $cntr->offsetSet($ns, $data);
+
+ return true;
+ }
+
+ /* reading */
+
+ /**
+ * Internal method to get an item.
+ *
+ * @param string $normalizedKey
+ * @param bool $success
+ * @param mixed $casToken
+ * @return mixed Data on success, null on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetItem(& $normalizedKey, & $success = null, & $casToken = null)
+ {
+ $cntr = $this->getSessionContainer();
+ $ns = $this->getOptions()->getNamespace();
+
+ if (!$cntr->offsetExists($ns)) {
+ $success = false;
+ return null;
+ }
+
+ $data = $cntr->offsetGet($ns);
+ $success = array_key_exists($normalizedKey, $data);
+ if (!$success) {
+ return null;
+ }
+
+ $casToken = $value = $data[$normalizedKey];
+ return $value;
+ }
+
+ /**
+ * Internal method to get multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Associative array of keys and values
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetItems(array & $normalizedKeys)
+ {
+ $cntr = $this->getSessionContainer();
+ $ns = $this->getOptions()->getNamespace();
+
+ if (!$cntr->offsetExists($ns)) {
+ return array();
+ }
+
+ $data = $cntr->offsetGet($ns);
+ $result = array();
+ foreach ($normalizedKeys as $normalizedKey) {
+ if (array_key_exists($normalizedKey, $data)) {
+ $result[$normalizedKey] = $data[$normalizedKey];
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to test if an item exists.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ */
+ protected function internalHasItem(& $normalizedKey)
+ {
+ $cntr = $this->getSessionContainer();
+ $ns = $this->getOptions()->getNamespace();
+
+ if (!$cntr->offsetExists($ns)) {
+ return false;
+ }
+
+ $data = $cntr->offsetGet($ns);
+ return array_key_exists($normalizedKey, $data);
+ }
+
+ /**
+ * Internal method to test multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Array of found keys
+ */
+ protected function internalHasItems(array & $normalizedKeys)
+ {
+ $cntr = $this->getSessionContainer();
+ $ns = $this->getOptions()->getNamespace();
+
+ if (!$cntr->offsetExists($ns)) {
+ return array();
+ }
+
+ $data = $cntr->offsetGet($ns);
+ $result = array();
+ foreach ($normalizedKeys as $normalizedKey) {
+ if (array_key_exists($normalizedKey, $data)) {
+ $result[] = $normalizedKey;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get metadata of an item.
+ *
+ * @param string $normalizedKey
+ * @return array|bool Metadata on success, false on failure
+ * @throws Exception\ExceptionInterface
+ *
+ * @triggers getMetadata.pre(PreEvent)
+ * @triggers getMetadata.post(PostEvent)
+ * @triggers getMetadata.exception(ExceptionEvent)
+ */
+ protected function internalGetMetadata(& $normalizedKey)
+ {
+ return $this->internalHasItem($normalizedKey) ? array() : false;
+ }
+
+ /* writing */
+
+ /**
+ * Internal method to store an item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalSetItem(& $normalizedKey, & $value)
+ {
+ $cntr = $this->getSessionContainer();
+ $ns = $this->getOptions()->getNamespace();
+ $data = $cntr->offsetExists($ns) ? $cntr->offsetGet($ns) : array();
+ $data[$normalizedKey] = $value;
+ $cntr->offsetSet($ns, $data);
+ return true;
+ }
+
+ /**
+ * Internal method to store multiple items.
+ *
+ * @param array $normalizedKeyValuePairs
+ * @return array Array of not stored keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalSetItems(array & $normalizedKeyValuePairs)
+ {
+ $cntr = $this->getSessionContainer();
+ $ns = $this->getOptions()->getNamespace();
+
+ if ($cntr->offsetExists($ns)) {
+ $data = array_merge($cntr->offsetGet($ns), $normalizedKeyValuePairs);
+ } else {
+ $data = $normalizedKeyValuePairs;
+ }
+ $cntr->offsetSet($ns, $data);
+
+ return array();
+ }
+
+ /**
+ * Add an item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalAddItem(& $normalizedKey, & $value)
+ {
+ $cntr = $this->getSessionContainer();
+ $ns = $this->getOptions()->getNamespace();
+
+ if ($cntr->offsetExists($ns)) {
+ $data = $cntr->offsetGet($ns);
+
+ if (array_key_exists($normalizedKey, $data)) {
+ return false;
+ }
+
+ $data[$normalizedKey] = $value;
+ } else {
+ $data = array($normalizedKey => $value);
+ }
+
+ $cntr->offsetSet($ns, $data);
+ return true;
+ }
+
+ /**
+ * Internal method to add multiple items.
+ *
+ * @param array $normalizedKeyValuePairs
+ * @return array Array of not stored keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalAddItems(array & $normalizedKeyValuePairs)
+ {
+ $cntr = $this->getSessionContainer();
+ $ns = $this->getOptions()->getNamespace();
+
+ $result = array();
+ if ($cntr->offsetExists($ns)) {
+ $data = $cntr->offsetGet($ns);
+
+ foreach ($normalizedKeyValuePairs as $normalizedKey => $value) {
+ if (array_key_exists($normalizedKey, $data)) {
+ $result[] = $normalizedKey;
+ } else {
+ $data[$normalizedKey] = $value;
+ }
+ }
+ } else {
+ $data = $normalizedKeyValuePairs;
+ }
+
+ $cntr->offsetSet($ns, $data);
+ return $result;
+ }
+
+ /**
+ * Internal method to replace an existing item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalReplaceItem(& $normalizedKey, & $value)
+ {
+ $cntr = $this->getSessionContainer();
+ $ns = $this->getOptions()->getNamespace();
+
+ if (!$cntr->offsetExists($ns)) {
+ return false;
+ }
+
+ $data = $cntr->offsetGet($ns);
+ if (!array_key_exists($normalizedKey, $data)) {
+ return false;
+ }
+ $data[$normalizedKey] = $value;
+ $cntr->offsetSet($ns, $data);
+
+ return true;
+ }
+
+ /**
+ * Internal method to replace multiple existing items.
+ *
+ * @param array $normalizedKeyValuePairs
+ * @return array Array of not stored keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalReplaceItems(array & $normalizedKeyValuePairs)
+ {
+ $cntr = $this->getSessionContainer();
+ $ns = $this->getOptions()->getNamespace();
+ if (!$cntr->offsetExists($ns)) {
+ return array_keys($normalizedKeyValuePairs);
+ }
+
+ $data = $cntr->offsetGet($ns);
+ $result = array();
+ foreach ($normalizedKeyValuePairs as $normalizedKey => $value) {
+ if (!array_key_exists($normalizedKey, $data)) {
+ $result[] = $normalizedKey;
+ } else {
+ $data[$normalizedKey] = $value;
+ }
+ }
+ $cntr->offsetSet($ns, $data);
+
+ return $result;
+ }
+
+ /**
+ * Internal method to remove an item.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalRemoveItem(& $normalizedKey)
+ {
+ $cntr = $this->getSessionContainer();
+ $ns = $this->getOptions()->getNamespace();
+
+ if (!$cntr->offsetExists($ns)) {
+ return false;
+ }
+
+ $data = $cntr->offsetGet($ns);
+ if (!array_key_exists($normalizedKey, $data)) {
+ return false;
+ }
+
+ unset($data[$normalizedKey]);
+
+ if (!$data) {
+ $cntr->offsetUnset($ns);
+ } else {
+ $cntr->offsetSet($ns, $data);
+ }
+
+ return true;
+ }
+
+ /**
+ * Internal method to increment an item.
+ *
+ * @param string $normalizedKey
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalIncrementItem(& $normalizedKey, & $value)
+ {
+ $cntr = $this->getSessionContainer();
+ $ns = $this->getOptions()->getNamespace();
+
+ if ($cntr->offsetExists($ns)) {
+ $data = $cntr->offsetGet($ns);
+ } else {
+ $data = array();
+ }
+
+ if (array_key_exists($normalizedKey, $data)) {
+ $data[$normalizedKey]+= $value;
+ $newValue = $data[$normalizedKey];
+ } else {
+ // initial value
+ $newValue = $value;
+ $data[$normalizedKey] = $newValue;
+ }
+
+ $cntr->offsetSet($ns, $data);
+ return $newValue;
+ }
+
+ /**
+ * Internal method to decrement an item.
+ *
+ * @param string $normalizedKey
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalDecrementItem(& $normalizedKey, & $value)
+ {
+ $cntr = $this->getSessionContainer();
+ $ns = $this->getOptions()->getNamespace();
+
+ if ($cntr->offsetExists($ns)) {
+ $data = $cntr->offsetGet($ns);
+ } else {
+ $data = array();
+ }
+
+ if (array_key_exists($normalizedKey, $data)) {
+ $data[$normalizedKey]-= $value;
+ $newValue = $data[$normalizedKey];
+ } else {
+ // initial value
+ $newValue = -$value;
+ $data[$normalizedKey] = $newValue;
+ }
+
+ $cntr->offsetSet($ns, $data);
+ return $newValue;
+ }
+
+ /* status */
+
+ /**
+ * Internal method to get capabilities of this adapter
+ *
+ * @return Capabilities
+ */
+ protected function internalGetCapabilities()
+ {
+ if ($this->capabilities === null) {
+ $this->capabilityMarker = new stdClass();
+ $this->capabilities = new Capabilities(
+ $this,
+ $this->capabilityMarker,
+ array(
+ 'supportedDatatypes' => array(
+ 'NULL' => true,
+ 'boolean' => true,
+ 'integer' => true,
+ 'double' => true,
+ 'string' => true,
+ 'array' => 'array',
+ 'object' => 'object',
+ 'resource' => false,
+ ),
+ 'supportedMetadata' => array(),
+ 'minTtl' => 0,
+ 'maxKeyLength' => 0,
+ 'namespaceIsPrefix' => false,
+ 'namespaceSeparator' => '',
+ )
+ );
+ }
+
+ return $this->capabilities;
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/SessionOptions.php b/library/Zend/Cache/Storage/Adapter/SessionOptions.php
new file mode 100755
index 0000000000..8e4c6d5c90
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/SessionOptions.php
@@ -0,0 +1,51 @@
+sessionContainer != $sessionContainer) {
+ $this->triggerOptionEvent('session_container', $sessionContainer);
+ $this->sessionContainer = $sessionContainer;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the session container
+ *
+ * @return null|SessionContainer
+ */
+ public function getSessionContainer()
+ {
+ return $this->sessionContainer;
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/WinCache.php b/library/Zend/Cache/Storage/Adapter/WinCache.php
new file mode 100755
index 0000000000..b911e04000
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/WinCache.php
@@ -0,0 +1,533 @@
+options) {
+ $this->setOptions(new WinCacheOptions());
+ }
+ return $this->options;
+ }
+
+ /* TotalSpaceCapableInterface */
+
+ /**
+ * Get total space in bytes
+ *
+ * @return int|float
+ */
+ public function getTotalSpace()
+ {
+ $mem = wincache_ucache_meminfo();
+ return $mem['memory_total'];
+ }
+
+ /* AvailableSpaceCapableInterface */
+
+ /**
+ * Get available space in bytes
+ *
+ * @return int|float
+ */
+ public function getAvailableSpace()
+ {
+ $mem = wincache_ucache_meminfo();
+ return $mem['memory_free'];
+ }
+
+ /* FlushableInterface */
+
+ /**
+ * Flush the whole storage
+ *
+ * @return bool
+ */
+ public function flush()
+ {
+ return wincache_ucache_clear();
+ }
+
+ /* reading */
+
+ /**
+ * Internal method to get an item.
+ *
+ * @param string $normalizedKey
+ * @param bool $success
+ * @param mixed $casToken
+ * @return mixed Data on success, null on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetItem(& $normalizedKey, & $success = null, & $casToken = null)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+ $result = wincache_ucache_get($internalKey, $success);
+
+ if ($success) {
+ $casToken = $result;
+ } else {
+ $result = null;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to get multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Associative array of keys and values
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetItems(array & $normalizedKeys)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ if ($namespace === '') {
+ return wincache_ucache_get($normalizedKeys);
+ }
+
+ $prefix = $namespace . $options->getNamespaceSeparator();
+ $internalKeys = array();
+ foreach ($normalizedKeys as $normalizedKey) {
+ $internalKeys[] = $prefix . $normalizedKey;
+ }
+
+ $fetch = wincache_ucache_get($internalKeys);
+
+ // remove namespace prefix
+ $prefixL = strlen($prefix);
+ $result = array();
+ foreach ($fetch as $internalKey => & $value) {
+ $result[substr($internalKey, $prefixL)] = & $value;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to test if an item exists.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalHasItem(& $normalizedKey)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ return wincache_ucache_exists($prefix . $normalizedKey);
+ }
+
+ /**
+ * Get metadata of an item.
+ *
+ * @param string $normalizedKey
+ * @return array|bool Metadata on success, false on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetMetadata(& $normalizedKey)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+
+ $info = wincache_ucache_info(true, $internalKey);
+ if (isset($info['ucache_entries'][1])) {
+ $metadata = $info['ucache_entries'][1];
+ $this->normalizeMetadata($metadata);
+ return $metadata;
+ }
+
+ return false;
+ }
+
+ /* writing */
+
+ /**
+ * Internal method to store an item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalSetItem(& $normalizedKey, & $value)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+ $ttl = $options->getTtl();
+
+ if (!wincache_ucache_set($internalKey, $value, $ttl)) {
+ $type = is_object($value) ? get_class($value) : gettype($value);
+ throw new Exception\RuntimeException(
+ "wincache_ucache_set('{$internalKey}', <{$type}>, {$ttl}) failed"
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Internal method to store multiple items.
+ *
+ * @param array $normalizedKeyValuePairs
+ * @return array Array of not stored keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalSetItems(array & $normalizedKeyValuePairs)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ if ($namespace === '') {
+ return wincache_ucache_set($normalizedKeyValuePairs, null, $options->getTtl());
+ }
+
+ $prefix = $namespace . $options->getNamespaceSeparator();
+ $internalKeyValuePairs = array();
+ foreach ($normalizedKeyValuePairs as $normalizedKey => & $value) {
+ $internalKey = $prefix . $normalizedKey;
+ $internalKeyValuePairs[$internalKey] = & $value;
+ }
+
+ $result = wincache_ucache_set($internalKeyValuePairs, null, $options->getTtl());
+
+ // remove key prefic
+ $prefixL = strlen($prefix);
+ foreach ($result as & $key) {
+ $key = substr($key, $prefixL);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Add an item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalAddItem(& $normalizedKey, & $value)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+ $ttl = $options->getTtl();
+
+ if (!wincache_ucache_add($internalKey, $value, $ttl)) {
+ $type = is_object($value) ? get_class($value) : gettype($value);
+ throw new Exception\RuntimeException(
+ "wincache_ucache_add('{$internalKey}', <{$type}>, {$ttl}) failed"
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Internal method to add multiple items.
+ *
+ * @param array $normalizedKeyValuePairs
+ * @return array Array of not stored keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalAddItems(array & $normalizedKeyValuePairs)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ if ($namespace === '') {
+ return wincache_ucache_add($normalizedKeyValuePairs, null, $options->getTtl());
+ }
+
+ $prefix = $namespace . $options->getNamespaceSeparator();
+ $internalKeyValuePairs = array();
+ foreach ($normalizedKeyValuePairs as $normalizedKey => $value) {
+ $internalKey = $prefix . $normalizedKey;
+ $internalKeyValuePairs[$internalKey] = $value;
+ }
+
+ $result = wincache_ucache_add($internalKeyValuePairs, null, $options->getTtl());
+
+ // remove key prefic
+ $prefixL = strlen($prefix);
+ foreach ($result as & $key) {
+ $key = substr($key, $prefixL);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to replace an existing item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalReplaceItem(& $normalizedKey, & $value)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+ if (!wincache_ucache_exists($internalKey)) {
+ return false;
+ }
+
+ $ttl = $options->getTtl();
+ if (!wincache_ucache_set($internalKey, $value, $ttl)) {
+ $type = is_object($value) ? get_class($value) : gettype($value);
+ throw new Exception\RuntimeException(
+ "wincache_ucache_set('{$internalKey}', <{$type}>, {$ttl}) failed"
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Internal method to remove an item.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalRemoveItem(& $normalizedKey)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+ return wincache_ucache_delete($internalKey);
+ }
+
+ /**
+ * Internal method to remove multiple items.
+ *
+ * @param array $normalizedKeys
+ * @return array Array of not removed keys
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalRemoveItems(array & $normalizedKeys)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ if ($namespace === '') {
+ $result = wincache_ucache_delete($normalizedKeys);
+ return ($result === false) ? $normalizedKeys : $result;
+ }
+
+ $prefix = $namespace . $options->getNamespaceSeparator();
+ $internalKeys = array();
+ foreach ($normalizedKeys as $normalizedKey) {
+ $internalKeys[] = $prefix . $normalizedKey;
+ }
+
+ $result = wincache_ucache_delete($internalKeys);
+ if ($result === false) {
+ return $normalizedKeys;
+ } elseif ($result) {
+ // remove key prefix
+ $prefixL = strlen($prefix);
+ foreach ($result as & $key) {
+ $key = substr($key, $prefixL);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to increment an item.
+ *
+ * @param string $normalizedKey
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalIncrementItem(& $normalizedKey, & $value)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+ return wincache_ucache_inc($internalKey, (int) $value);
+ }
+
+ /**
+ * Internal method to decrement an item.
+ *
+ * @param string $normalizedKey
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalDecrementItem(& $normalizedKey, & $value)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+ return wincache_ucache_dec($internalKey, (int) $value);
+ }
+
+ /* status */
+
+ /**
+ * Internal method to get capabilities of this adapter
+ *
+ * @return Capabilities
+ */
+ protected function internalGetCapabilities()
+ {
+ if ($this->capabilities === null) {
+ $marker = new stdClass();
+ $capabilities = new Capabilities(
+ $this,
+ $marker,
+ array(
+ 'supportedDatatypes' => array(
+ 'NULL' => true,
+ 'boolean' => true,
+ 'integer' => true,
+ 'double' => true,
+ 'string' => true,
+ 'array' => true,
+ 'object' => 'object',
+ 'resource' => false,
+ ),
+ 'supportedMetadata' => array(
+ 'internal_key', 'ttl', 'hits', 'size'
+ ),
+ 'minTtl' => 1,
+ 'maxTtl' => 0,
+ 'staticTtl' => true,
+ 'ttlPrecision' => 1,
+ 'useRequestTime' => false,
+ 'expiredRead' => false,
+ 'namespaceIsPrefix' => true,
+ 'namespaceSeparator' => $this->getOptions()->getNamespaceSeparator(),
+ )
+ );
+
+ // update namespace separator on change option
+ $this->getEventManager()->attach('option', function ($event) use ($capabilities, $marker) {
+ $params = $event->getParams();
+
+ if (isset($params['namespace_separator'])) {
+ $capabilities->setNamespaceSeparator($marker, $params['namespace_separator']);
+ }
+ });
+
+ $this->capabilities = $capabilities;
+ $this->capabilityMarker = $marker;
+ }
+
+ return $this->capabilities;
+ }
+
+ /* internal */
+
+ /**
+ * Normalize metadata to work with WinCache
+ *
+ * @param array $metadata
+ * @return void
+ */
+ protected function normalizeMetadata(array & $metadata)
+ {
+ $metadata['internal_key'] = $metadata['key_name'];
+ $metadata['hits'] = $metadata['hitcount'];
+ $metadata['ttl'] = $metadata['ttl_seconds'];
+ $metadata['size'] = $metadata['value_size'];
+
+ unset(
+ $metadata['key_name'],
+ $metadata['hitcount'],
+ $metadata['ttl_seconds'],
+ $metadata['value_size']
+ );
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/WinCacheOptions.php b/library/Zend/Cache/Storage/Adapter/WinCacheOptions.php
new file mode 100755
index 0000000000..8c9789ae76
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/WinCacheOptions.php
@@ -0,0 +1,47 @@
+triggerOptionEvent('namespace_separator', $namespaceSeparator);
+ $this->namespaceSeparator = $namespaceSeparator;
+ return $this;
+ }
+
+ /**
+ * Get namespace separator
+ *
+ * @return string
+ */
+ public function getNamespaceSeparator()
+ {
+ return $this->namespaceSeparator;
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/XCache.php b/library/Zend/Cache/Storage/Adapter/XCache.php
new file mode 100755
index 0000000000..18073e9fed
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/XCache.php
@@ -0,0 +1,524 @@
+options) {
+ $this->setOptions(new XCacheOptions());
+ }
+ return $this->options;
+ }
+
+ /* TotalSpaceCapableInterface */
+
+ /**
+ * Get total space in bytes
+ *
+ * @return int|float
+ */
+ public function getTotalSpace()
+ {
+ if ($this->totalSpace === null) {
+ $this->totalSpace = 0;
+
+ $this->initAdminAuth();
+ $cnt = xcache_count(XC_TYPE_VAR);
+ for ($i=0; $i < $cnt; $i++) {
+ $info = xcache_info(XC_TYPE_VAR, $i);
+ $this->totalSpace+= $info['size'];
+ }
+ $this->resetAdminAuth();
+ }
+
+ return $this->totalSpace;
+ }
+
+ /* AvailableSpaceCapableInterface */
+
+ /**
+ * Get available space in bytes
+ *
+ * @return int|float
+ */
+ public function getAvailableSpace()
+ {
+ $availableSpace = 0;
+
+ $this->initAdminAuth();
+ $cnt = xcache_count(XC_TYPE_VAR);
+ for ($i = 0; $i < $cnt; $i++) {
+ $info = xcache_info(XC_TYPE_VAR, $i);
+ $availableSpace+= $info['avail'];
+ }
+ $this->resetAdminAuth();
+
+ return $availableSpace;
+ }
+
+ /* ClearByNamespaceInterface */
+
+ /**
+ * Remove items by given namespace
+ *
+ * @param string $namespace
+ * @return bool
+ */
+ public function clearByNamespace($namespace)
+ {
+ $namespace = (string) $namespace;
+ if ($namespace === '') {
+ throw new Exception\InvalidArgumentException('No namespace given');
+ }
+
+ $options = $this->getOptions();
+ $prefix = $namespace . $options->getNamespaceSeparator();
+
+ xcache_unset_by_prefix($prefix);
+ return true;
+ }
+
+ /* ClearByPrefixInterface */
+
+ /**
+ * Remove items matching given prefix
+ *
+ * @param string $prefix
+ * @return bool
+ */
+ public function clearByPrefix($prefix)
+ {
+ $prefix = (string) $prefix;
+ if ($prefix === '') {
+ throw new Exception\InvalidArgumentException('No prefix given');
+ }
+
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator() . $prefix;
+
+ xcache_unset_by_prefix($prefix);
+ return true;
+ }
+
+ /* FlushableInterface */
+
+ /**
+ * Flush the whole storage
+ *
+ * @return bool
+ */
+ public function flush()
+ {
+ $this->initAdminAuth();
+ $cnt = xcache_count(XC_TYPE_VAR);
+ for ($i = 0; $i < $cnt; $i++) {
+ xcache_clear_cache(XC_TYPE_VAR, $i);
+ }
+ $this->resetAdminAuth();
+
+ return true;
+ }
+
+ /* IterableInterface */
+
+ /**
+ * Get the storage iterator
+ *
+ * @return KeyListIterator
+ */
+ public function getIterator()
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $keys = array();
+
+ $this->initAdminAuth();
+
+ if ($namespace === '') {
+ $cnt = xcache_count(XC_TYPE_VAR);
+ for ($i=0; $i < $cnt; $i++) {
+ $list = xcache_list(XC_TYPE_VAR, $i);
+ foreach ($list['cache_list'] as & $item) {
+ $keys[] = $item['name'];
+ }
+ }
+ } else {
+ $prefix = $namespace . $options->getNamespaceSeparator();
+ $prefixL = strlen($prefix);
+
+ $cnt = xcache_count(XC_TYPE_VAR);
+ for ($i=0; $i < $cnt; $i++) {
+ $list = xcache_list(XC_TYPE_VAR, $i);
+ foreach ($list['cache_list'] as & $item) {
+ $keys[] = substr($item['name'], $prefixL);
+ }
+ }
+ }
+
+ $this->resetAdminAuth();
+
+ return new KeyListIterator($this, $keys);
+ }
+
+ /* reading */
+
+ /**
+ * Internal method to get an item.
+ *
+ * @param string $normalizedKey
+ * @param bool $success
+ * @param mixed $casToken
+ * @return mixed Data on success, null on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetItem(& $normalizedKey, & $success = null, & $casToken = null)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+
+ $result = xcache_get($internalKey);
+ $success = ($result !== null);
+
+ if ($success) {
+ $casToken = $result;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to test if an item exists.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalHasItem(& $normalizedKey)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ return xcache_isset($prefix . $normalizedKey);
+ }
+
+ /**
+ * Get metadata of an item.
+ *
+ * @param string $normalizedKey
+ * @return array|bool Metadata on success, false on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalGetMetadata(& $normalizedKey)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+
+ if (xcache_isset($internalKey)) {
+ $this->initAdminAuth();
+ $cnt = xcache_count(XC_TYPE_VAR);
+ for ($i=0; $i < $cnt; $i++) {
+ $list = xcache_list(XC_TYPE_VAR, $i);
+ foreach ($list['cache_list'] as & $metadata) {
+ if ($metadata['name'] === $internalKey) {
+ $this->normalizeMetadata($metadata);
+ return $metadata;
+ }
+ }
+ }
+ $this->resetAdminAuth();
+ }
+
+ return false;
+ }
+
+ /* writing */
+
+ /**
+ * Internal method to store an item.
+ *
+ * @param string $normalizedKey
+ * @param mixed $value
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalSetItem(& $normalizedKey, & $value)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($options === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+ $ttl = $options->getTtl();
+
+ if (!xcache_set($internalKey, $value, $ttl)) {
+ $type = is_object($value) ? get_class($value) : gettype($value);
+ throw new Exception\RuntimeException(
+ "xcache_set('{$internalKey}', <{$type}>, {$ttl}) failed"
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Internal method to remove an item.
+ *
+ * @param string $normalizedKey
+ * @return bool
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalRemoveItem(& $normalizedKey)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+
+ return xcache_unset($internalKey);
+ }
+
+ /**
+ * Internal method to increment an item.
+ *
+ * @param string $normalizedKey
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalIncrementItem(& $normalizedKey, & $value)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+ $ttl = $options->getTtl();
+ $value = (int) $value;
+
+ return xcache_inc($internalKey, $value, $ttl);
+ }
+
+ /**
+ * Internal method to decrement an item.
+ *
+ * @param string $normalizedKey
+ * @param int $value
+ * @return int|bool The new value on success, false on failure
+ * @throws Exception\ExceptionInterface
+ */
+ protected function internalDecrementItem(& $normalizedKey, & $value)
+ {
+ $options = $this->getOptions();
+ $namespace = $options->getNamespace();
+ $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator();
+ $internalKey = $prefix . $normalizedKey;
+ $ttl = $options->getTtl();
+ $value = (int) $value;
+
+ return xcache_dec($internalKey, $value, $ttl);
+ }
+
+ /* status */
+
+ /**
+ * Internal method to get capabilities of this adapter
+ *
+ * @return Capabilities
+ */
+ protected function internalGetCapabilities()
+ {
+ if ($this->capabilities === null) {
+ $marker = new stdClass();
+ $capabilities = new Capabilities(
+ $this,
+ $marker,
+ array(
+ 'supportedDatatypes' => array(
+ 'NULL' => false,
+ 'boolean' => true,
+ 'integer' => true,
+ 'double' => true,
+ 'string' => true,
+ 'array' => true,
+ 'object' => 'object',
+ 'resource' => false,
+ ),
+ 'supportedMetadata' => array(
+ 'internal_key',
+ 'size', 'refcount', 'hits',
+ 'ctime', 'atime', 'hvalue',
+ ),
+ 'minTtl' => 1,
+ 'maxTtl' => (int)ini_get('xcache.var_maxttl'),
+ 'staticTtl' => true,
+ 'ttlPrecision' => 1,
+ 'useRequestTime' => true,
+ 'expiredRead' => false,
+ 'maxKeyLength' => 5182,
+ 'namespaceIsPrefix' => true,
+ 'namespaceSeparator' => $this->getOptions()->getNamespaceSeparator(),
+ )
+ );
+
+ // update namespace separator on change option
+ $this->getEventManager()->attach('option', function ($event) use ($capabilities, $marker) {
+ $params = $event->getParams();
+
+ if (isset($params['namespace_separator'])) {
+ $capabilities->setNamespaceSeparator($marker, $params['namespace_separator']);
+ }
+ });
+
+ $this->capabilities = $capabilities;
+ $this->capabilityMarker = $marker;
+ }
+
+ return $this->capabilities;
+ }
+
+ /* internal */
+
+ /**
+ * Init authentication before calling admin functions
+ *
+ * @return void
+ */
+ protected function initAdminAuth()
+ {
+ $options = $this->getOptions();
+
+ if ($options->getAdminAuth()) {
+ $adminUser = $options->getAdminUser();
+ $adminPass = $options->getAdminPass();
+
+ // backup HTTP authentication properties
+ if (isset($_SERVER['PHP_AUTH_USER'])) {
+ $this->backupAuth['PHP_AUTH_USER'] = $_SERVER['PHP_AUTH_USER'];
+ }
+ if (isset($_SERVER['PHP_AUTH_PW'])) {
+ $this->backupAuth['PHP_AUTH_PW'] = $_SERVER['PHP_AUTH_PW'];
+ }
+
+ // set authentication
+ $_SERVER['PHP_AUTH_USER'] = $adminUser;
+ $_SERVER['PHP_AUTH_PW'] = $adminPass;
+ }
+ }
+
+ /**
+ * Reset authentication after calling admin functions
+ *
+ * @return void
+ */
+ protected function resetAdminAuth()
+ {
+ unset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']);
+ $_SERVER = $this->backupAuth + $_SERVER;
+ $this->backupAuth = array();
+ }
+
+ /**
+ * Normalize metadata to work with XCache
+ *
+ * @param array $metadata
+ */
+ protected function normalizeMetadata(array & $metadata)
+ {
+ $metadata['internal_key'] = &$metadata['name'];
+ unset($metadata['name']);
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/XCacheOptions.php b/library/Zend/Cache/Storage/Adapter/XCacheOptions.php
new file mode 100755
index 0000000000..4c97136b75
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/XCacheOptions.php
@@ -0,0 +1,146 @@
+triggerOptionEvent('namespace_separator', $namespaceSeparator);
+ $this->namespaceSeparator = $namespaceSeparator;
+ return $this;
+ }
+
+ /**
+ * Get namespace separator
+ *
+ * @return string
+ */
+ public function getNamespaceSeparator()
+ {
+ return $this->namespaceSeparator;
+ }
+
+ /**
+ * Set username to call admin functions
+ *
+ * @param null|string $adminUser
+ * @return XCacheOptions
+ */
+ public function setAdminUser($adminUser)
+ {
+ $adminUser = ($adminUser === null) ? null : (string) $adminUser;
+ if ($this->adminUser !== $adminUser) {
+ $this->triggerOptionEvent('admin_user', $adminUser);
+ $this->adminUser = $adminUser;
+ }
+ return $this;
+ }
+
+ /**
+ * Get username to call admin functions
+ *
+ * @return string
+ */
+ public function getAdminUser()
+ {
+ return $this->adminUser;
+ }
+
+ /**
+ * Enable/Disable admin authentication handling
+ *
+ * @param bool $adminAuth
+ * @return XCacheOptions
+ */
+ public function setAdminAuth($adminAuth)
+ {
+ $adminAuth = (bool) $adminAuth;
+ if ($this->adminAuth !== $adminAuth) {
+ $this->triggerOptionEvent('admin_auth', $adminAuth);
+ $this->adminAuth = $adminAuth;
+ }
+ return $this;
+ }
+
+ /**
+ * Get admin authentication enabled
+ *
+ * @return bool
+ */
+ public function getAdminAuth()
+ {
+ return $this->adminAuth;
+ }
+
+ /**
+ * Set password to call admin functions
+ *
+ * @param null|string $adminPass
+ * @return XCacheOptions
+ */
+ public function setAdminPass($adminPass)
+ {
+ $adminPass = ($adminPass === null) ? null : (string) $adminPass;
+ if ($this->adminPass !== $adminPass) {
+ $this->triggerOptionEvent('admin_pass', $adminPass);
+ $this->adminPass = $adminPass;
+ }
+ return $this;
+ }
+
+ /**
+ * Get password to call admin functions
+ *
+ * @return string
+ */
+ public function getAdminPass()
+ {
+ return $this->adminPass;
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/ZendServerDisk.php b/library/Zend/Cache/Storage/Adapter/ZendServerDisk.php
new file mode 100755
index 0000000000..c48992a59b
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/ZendServerDisk.php
@@ -0,0 +1,186 @@
+totalSpace === null) {
+ $path = ini_get('zend_datacache.disk.save_path');
+
+ ErrorHandler::start();
+ $total = disk_total_space($path);
+ $error = ErrorHandler::stop();
+ if ($total === false) {
+ throw new Exception\RuntimeException("Can't detect total space of '{$path}'", 0, $error);
+ }
+
+ $this->totalSpace = $total;
+ }
+ return $this->totalSpace;
+ }
+
+ /* AvailableSpaceCapableInterface */
+
+ /**
+ * Get available space in bytes
+ *
+ * @throws Exception\RuntimeException
+ * @return int|float
+ */
+ public function getAvailableSpace()
+ {
+ $path = ini_get('zend_datacache.disk.save_path');
+
+ ErrorHandler::start();
+ $avail = disk_free_space($path);
+ $error = ErrorHandler::stop();
+ if ($avail === false) {
+ throw new Exception\RuntimeException("Can't detect free space of '{$path}'", 0, $error);
+ }
+
+ return $avail;
+ }
+
+ /* internal */
+
+ /**
+ * Store data into Zend Data Disk Cache
+ *
+ * @param string $internalKey
+ * @param mixed $value
+ * @param int $ttl
+ * @return void
+ * @throws Exception\RuntimeException
+ */
+ protected function zdcStore($internalKey, $value, $ttl)
+ {
+ if (!zend_disk_cache_store($internalKey, $value, $ttl)) {
+ $valueType = gettype($value);
+ throw new Exception\RuntimeException(
+ "zend_disk_cache_store($internalKey, <{$valueType}>, {$ttl}) failed"
+ );
+ }
+ }
+
+ /**
+ * Fetch a single item from Zend Data Disk Cache
+ *
+ * @param string $internalKey
+ * @return mixed The stored value or NULL if item wasn't found
+ * @throws Exception\RuntimeException
+ */
+ protected function zdcFetch($internalKey)
+ {
+ return zend_disk_cache_fetch((string) $internalKey);
+ }
+
+ /**
+ * Fetch multiple items from Zend Data Disk Cache
+ *
+ * @param array $internalKeys
+ * @return array All found items
+ * @throws Exception\RuntimeException
+ */
+ protected function zdcFetchMulti(array $internalKeys)
+ {
+ $items = zend_disk_cache_fetch($internalKeys);
+ if ($items === false) {
+ throw new Exception\RuntimeException("zend_disk_cache_fetch() failed");
+ }
+ return $items;
+ }
+
+ /**
+ * Delete data from Zend Data Disk Cache
+ *
+ * @param string $internalKey
+ * @return bool
+ * @throws Exception\RuntimeException
+ */
+ protected function zdcDelete($internalKey)
+ {
+ return zend_disk_cache_delete($internalKey);
+ }
+}
diff --git a/library/Zend/Cache/Storage/Adapter/ZendServerShm.php b/library/Zend/Cache/Storage/Adapter/ZendServerShm.php
new file mode 100755
index 0000000000..6b226d7c0e
--- /dev/null
+++ b/library/Zend/Cache/Storage/Adapter/ZendServerShm.php
@@ -0,0 +1,141 @@
+, {$ttl}) failed"
+ );
+ }
+ }
+
+ /**
+ * Fetch a single item from Zend Data SHM Cache
+ *
+ * @param string $internalKey
+ * @return mixed The stored value or NULL if item wasn't found
+ * @throws Exception\RuntimeException
+ */
+ protected function zdcFetch($internalKey)
+ {
+ return zend_shm_cache_fetch((string) $internalKey);
+ }
+
+ /**
+ * Fetch multiple items from Zend Data SHM Cache
+ *
+ * @param array $internalKeys
+ * @return array All found items
+ * @throws Exception\RuntimeException
+ */
+ protected function zdcFetchMulti(array $internalKeys)
+ {
+ $items = zend_shm_cache_fetch($internalKeys);
+ if ($items === false) {
+ throw new Exception\RuntimeException("zend_shm_cache_fetch() failed");
+ }
+ return $items;
+ }
+
+ /**
+ * Delete data from Zend Data SHM Cache
+ *
+ * @param string $internalKey
+ * @return bool
+ * @throws Exception\RuntimeException
+ */
+ protected function zdcDelete($internalKey)
+ {
+ return zend_shm_cache_delete($internalKey);
+ }
+}
diff --git a/library/Zend/Cache/Storage/AdapterPluginManager.php b/library/Zend/Cache/Storage/AdapterPluginManager.php
new file mode 100755
index 0000000000..665b36c813
--- /dev/null
+++ b/library/Zend/Cache/Storage/AdapterPluginManager.php
@@ -0,0 +1,74 @@
+ 'Zend\Cache\Storage\Adapter\Apc',
+ 'blackhole' => 'Zend\Cache\Storage\Adapter\BlackHole',
+ 'dba' => 'Zend\Cache\Storage\Adapter\Dba',
+ 'filesystem' => 'Zend\Cache\Storage\Adapter\Filesystem',
+ 'memcache' => 'Zend\Cache\Storage\Adapter\Memcache',
+ 'memcached' => 'Zend\Cache\Storage\Adapter\Memcached',
+ 'memory' => 'Zend\Cache\Storage\Adapter\Memory',
+ 'redis' => 'Zend\Cache\Storage\Adapter\Redis',
+ 'session' => 'Zend\Cache\Storage\Adapter\Session',
+ 'xcache' => 'Zend\Cache\Storage\Adapter\XCache',
+ 'wincache' => 'Zend\Cache\Storage\Adapter\WinCache',
+ 'zendserverdisk' => 'Zend\Cache\Storage\Adapter\ZendServerDisk',
+ 'zendservershm' => 'Zend\Cache\Storage\Adapter\ZendServerShm',
+ );
+
+ /**
+ * Do not share by default
+ *
+ * @var array
+ */
+ protected $shareByDefault = false;
+
+ /**
+ * Validate the plugin
+ *
+ * Checks that the adapter loaded is an instance of StorageInterface.
+ *
+ * @param mixed $plugin
+ * @return void
+ * @throws Exception\RuntimeException if invalid
+ */
+ public function validatePlugin($plugin)
+ {
+ if ($plugin instanceof StorageInterface) {
+ // we're okay
+ return;
+ }
+
+ throw new Exception\RuntimeException(sprintf(
+ 'Plugin of type %s is invalid; must implement %s\StorageInterface',
+ (is_object($plugin) ? get_class($plugin) : gettype($plugin)),
+ __NAMESPACE__
+ ));
+ }
+}
diff --git a/library/Zend/Cache/Storage/AvailableSpaceCapableInterface.php b/library/Zend/Cache/Storage/AvailableSpaceCapableInterface.php
new file mode 100755
index 0000000000..5088fe255a
--- /dev/null
+++ b/library/Zend/Cache/Storage/AvailableSpaceCapableInterface.php
@@ -0,0 +1,20 @@
+storage = $storage;
+ $this->marker = $marker;
+ $this->baseCapabilities = $baseCapabilities;
+
+ foreach ($capabilities as $name => $value) {
+ $this->setCapability($marker, $name, $value);
+ }
+ }
+
+ /**
+ * Get the storage adapter
+ *
+ * @return StorageInterface
+ */
+ public function getAdapter()
+ {
+ return $this->storage;
+ }
+
+ /**
+ * Get supported datatypes
+ *
+ * @return array
+ */
+ public function getSupportedDatatypes()
+ {
+ return $this->getCapability('supportedDatatypes', array(
+ 'NULL' => false,
+ 'boolean' => false,
+ 'integer' => false,
+ 'double' => false,
+ 'string' => true,
+ 'array' => false,
+ 'object' => false,
+ 'resource' => false,
+ ));
+ }
+
+ /**
+ * Set supported datatypes
+ *
+ * @param stdClass $marker
+ * @param array $datatypes
+ * @throws Exception\InvalidArgumentException
+ * @return Capabilities Fluent interface
+ */
+ public function setSupportedDatatypes(stdClass $marker, array $datatypes)
+ {
+ $allTypes = array(
+ 'array',
+ 'boolean',
+ 'double',
+ 'integer',
+ 'NULL',
+ 'object',
+ 'resource',
+ 'string',
+ );
+
+ // check/normalize datatype values
+ foreach ($datatypes as $type => &$toType) {
+ if (!in_array($type, $allTypes)) {
+ throw new Exception\InvalidArgumentException("Unknown datatype '{$type}'");
+ }
+
+ if (is_string($toType)) {
+ $toType = strtolower($toType);
+ if (!in_array($toType, $allTypes)) {
+ throw new Exception\InvalidArgumentException("Unknown datatype '{$toType}'");
+ }
+ } else {
+ $toType = (bool) $toType;
+ }
+ }
+
+ // add missing datatypes as not supported
+ $missingTypes = array_diff($allTypes, array_keys($datatypes));
+ foreach ($missingTypes as $type) {
+ $datatypes[$type] = false;
+ }
+
+ return $this->setCapability($marker, 'supportedDatatypes', $datatypes);
+ }
+
+ /**
+ * Get supported metadata
+ *
+ * @return array
+ */
+ public function getSupportedMetadata()
+ {
+ return $this->getCapability('supportedMetadata', array());
+ }
+
+ /**
+ * Set supported metadata
+ *
+ * @param stdClass $marker
+ * @param string[] $metadata
+ * @throws Exception\InvalidArgumentException
+ * @return Capabilities Fluent interface
+ */
+ public function setSupportedMetadata(stdClass $marker, array $metadata)
+ {
+ foreach ($metadata as $name) {
+ if (!is_string($name)) {
+ throw new Exception\InvalidArgumentException('$metadata must be an array of strings');
+ }
+ }
+ return $this->setCapability($marker, 'supportedMetadata', $metadata);
+ }
+
+ /**
+ * Get minimum supported time-to-live
+ *
+ * @return int 0 means items never expire
+ */
+ public function getMinTtl()
+ {
+ return $this->getCapability('minTtl', 0);
+ }
+
+ /**
+ * Set minimum supported time-to-live
+ *
+ * @param stdClass $marker
+ * @param int $minTtl
+ * @throws Exception\InvalidArgumentException
+ * @return Capabilities Fluent interface
+ */
+ public function setMinTtl(stdClass $marker, $minTtl)
+ {
+ $minTtl = (int) $minTtl;
+ if ($minTtl < 0) {
+ throw new Exception\InvalidArgumentException('$minTtl must be greater or equal 0');
+ }
+ return $this->setCapability($marker, 'minTtl', $minTtl);
+ }
+
+ /**
+ * Get maximum supported time-to-live
+ *
+ * @return int 0 means infinite
+ */
+ public function getMaxTtl()
+ {
+ return $this->getCapability('maxTtl', 0);
+ }
+
+ /**
+ * Set maximum supported time-to-live
+ *
+ * @param stdClass $marker
+ * @param int $maxTtl
+ * @throws Exception\InvalidArgumentException
+ * @return Capabilities Fluent interface
+ */
+ public function setMaxTtl(stdClass $marker, $maxTtl)
+ {
+ $maxTtl = (int) $maxTtl;
+ if ($maxTtl < 0) {
+ throw new Exception\InvalidArgumentException('$maxTtl must be greater or equal 0');
+ }
+ return $this->setCapability($marker, 'maxTtl', $maxTtl);
+ }
+
+ /**
+ * Is the time-to-live handled static (on write)
+ * or dynamic (on read)
+ *
+ * @return bool
+ */
+ public function getStaticTtl()
+ {
+ return $this->getCapability('staticTtl', false);
+ }
+
+ /**
+ * Set if the time-to-live handled static (on write) or dynamic (on read)
+ *
+ * @param stdClass $marker
+ * @param bool $flag
+ * @return Capabilities Fluent interface
+ */
+ public function setStaticTtl(stdClass $marker, $flag)
+ {
+ return $this->setCapability($marker, 'staticTtl', (bool) $flag);
+ }
+
+ /**
+ * Get time-to-live precision
+ *
+ * @return float
+ */
+ public function getTtlPrecision()
+ {
+ return $this->getCapability('ttlPrecision', 1);
+ }
+
+ /**
+ * Set time-to-live precision
+ *
+ * @param stdClass $marker
+ * @param float $ttlPrecision
+ * @throws Exception\InvalidArgumentException
+ * @return Capabilities Fluent interface
+ */
+ public function setTtlPrecision(stdClass $marker, $ttlPrecision)
+ {
+ $ttlPrecision = (float) $ttlPrecision;
+ if ($ttlPrecision <= 0) {
+ throw new Exception\InvalidArgumentException('$ttlPrecision must be greater than 0');
+ }
+ return $this->setCapability($marker, 'ttlPrecision', $ttlPrecision);
+ }
+
+ /**
+ * Get use request time
+ *
+ * @return bool
+ */
+ public function getUseRequestTime()
+ {
+ return $this->getCapability('useRequestTime', false);
+ }
+
+ /**
+ * Set use request time
+ *
+ * @param stdClass $marker
+ * @param bool $flag
+ * @return Capabilities Fluent interface
+ */
+ public function setUseRequestTime(stdClass $marker, $flag)
+ {
+ return $this->setCapability($marker, 'useRequestTime', (bool) $flag);
+ }
+
+ /**
+ * Get if expired items are readable
+ *
+ * @return bool
+ */
+ public function getExpiredRead()
+ {
+ return $this->getCapability('expiredRead', false);
+ }
+
+ /**
+ * Set if expired items are readable
+ *
+ * @param stdClass $marker
+ * @param bool $flag
+ * @return Capabilities Fluent interface
+ */
+ public function setExpiredRead(stdClass $marker, $flag)
+ {
+ return $this->setCapability($marker, 'expiredRead', (bool) $flag);
+ }
+
+ /**
+ * Get maximum key lenth
+ *
+ * @return int -1 means unknown, 0 means infinite
+ */
+ public function getMaxKeyLength()
+ {
+ return $this->getCapability('maxKeyLength', -1);
+ }
+
+ /**
+ * Set maximum key length
+ *
+ * @param stdClass $marker
+ * @param int $maxKeyLength
+ * @throws Exception\InvalidArgumentException
+ * @return Capabilities Fluent interface
+ */
+ public function setMaxKeyLength(stdClass $marker, $maxKeyLength)
+ {
+ $maxKeyLength = (int) $maxKeyLength;
+ if ($maxKeyLength < -1) {
+ throw new Exception\InvalidArgumentException('$maxKeyLength must be greater or equal than -1');
+ }
+ return $this->setCapability($marker, 'maxKeyLength', $maxKeyLength);
+ }
+
+ /**
+ * Get if namespace support is implemented as prefix
+ *
+ * @return bool
+ */
+ public function getNamespaceIsPrefix()
+ {
+ return $this->getCapability('namespaceIsPrefix', true);
+ }
+
+ /**
+ * Set if namespace support is implemented as prefix
+ *
+ * @param stdClass $marker
+ * @param bool $flag
+ * @return Capabilities Fluent interface
+ */
+ public function setNamespaceIsPrefix(stdClass $marker, $flag)
+ {
+ return $this->setCapability($marker, 'namespaceIsPrefix', (bool) $flag);
+ }
+
+ /**
+ * Get namespace separator if namespace is implemented as prefix
+ *
+ * @return string
+ */
+ public function getNamespaceSeparator()
+ {
+ return $this->getCapability('namespaceSeparator', '');
+ }
+
+ /**
+ * Set the namespace separator if namespace is implemented as prefix
+ *
+ * @param stdClass $marker
+ * @param string $separator
+ * @return Capabilities Fluent interface
+ */
+ public function setNamespaceSeparator(stdClass $marker, $separator)
+ {
+ return $this->setCapability($marker, 'namespaceSeparator', (string) $separator);
+ }
+
+ /**
+ * Get a capability
+ *
+ * @param string $property
+ * @param mixed $default
+ * @return mixed
+ */
+ protected function getCapability($property, $default = null)
+ {
+ if ($this->$property !== null) {
+ return $this->$property;
+ } elseif ($this->baseCapabilities) {
+ $getMethod = 'get' . $property;
+ return $this->baseCapabilities->$getMethod();
+ }
+ return $default;
+ }
+
+ /**
+ * Change a capability
+ *
+ * @param stdClass $marker
+ * @param string $property
+ * @param mixed $value
+ * @return Capabilities Fluent interface
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function setCapability(stdClass $marker, $property, $value)
+ {
+ if ($this->marker !== $marker) {
+ throw new Exception\InvalidArgumentException('Invalid marker');
+ }
+
+ if ($this->$property !== $value) {
+ $this->$property = $value;
+
+ // trigger event
+ if ($this->storage instanceof EventsCapableInterface) {
+ $this->storage->getEventManager()->trigger('capability', $this->storage, new ArrayObject(array(
+ $property => $value
+ )));
+ }
+ }
+
+ return $this;
+ }
+}
diff --git a/library/Zend/Cache/Storage/ClearByNamespaceInterface.php b/library/Zend/Cache/Storage/ClearByNamespaceInterface.php
new file mode 100755
index 0000000000..15fb4e7566
--- /dev/null
+++ b/library/Zend/Cache/Storage/ClearByNamespaceInterface.php
@@ -0,0 +1,21 @@
+setStorage($target);
+ }
+
+ /**
+ * Alias of setTarget
+ *
+ * @param StorageInterface $storage
+ * @return Event
+ * @see Zend\EventManager\Event::setTarget()
+ */
+ public function setStorage(StorageInterface $storage)
+ {
+ $this->target = $storage;
+ return $this;
+ }
+
+ /**
+ * Alias of getTarget
+ *
+ * @return StorageInterface
+ */
+ public function getStorage()
+ {
+ return $this->getTarget();
+ }
+}
diff --git a/library/Zend/Cache/Storage/ExceptionEvent.php b/library/Zend/Cache/Storage/ExceptionEvent.php
new file mode 100755
index 0000000000..e9ffb4a0c2
--- /dev/null
+++ b/library/Zend/Cache/Storage/ExceptionEvent.php
@@ -0,0 +1,91 @@
+setException($exception);
+ }
+
+ /**
+ * Set the exception to be thrown
+ *
+ * @param Exception $exception
+ * @return ExceptionEvent
+ */
+ public function setException(Exception $exception)
+ {
+ $this->exception = $exception;
+ return $this;
+ }
+
+ /**
+ * Get the exception to be thrown
+ *
+ * @return Exception
+ */
+ public function getException()
+ {
+ return $this->exception;
+ }
+
+ /**
+ * Throw the exception or use the result
+ *
+ * @param bool $flag
+ * @return ExceptionEvent
+ */
+ public function setThrowException($flag)
+ {
+ $this->throwException = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Throw the exception or use the result
+ *
+ * @return bool
+ */
+ public function getThrowException()
+ {
+ return $this->throwException;
+ }
+}
diff --git a/library/Zend/Cache/Storage/FlushableInterface.php b/library/Zend/Cache/Storage/FlushableInterface.php
new file mode 100755
index 0000000000..6c72541e6b
--- /dev/null
+++ b/library/Zend/Cache/Storage/FlushableInterface.php
@@ -0,0 +1,20 @@
+options = $options;
+ return $this;
+ }
+
+ /**
+ * Get all pattern options
+ *
+ * @return PluginOptions
+ */
+ public function getOptions()
+ {
+ if (null === $this->options) {
+ $this->setOptions(new PluginOptions());
+ }
+ return $this->options;
+ }
+}
diff --git a/library/Zend/Cache/Storage/Plugin/ClearExpiredByFactor.php b/library/Zend/Cache/Storage/Plugin/ClearExpiredByFactor.php
new file mode 100755
index 0000000000..0b257be779
--- /dev/null
+++ b/library/Zend/Cache/Storage/Plugin/ClearExpiredByFactor.php
@@ -0,0 +1,49 @@
+listeners[] = $events->attach('setItem.post', $callback, $priority);
+ $this->listeners[] = $events->attach('setItems.post', $callback, $priority);
+ $this->listeners[] = $events->attach('addItem.post', $callback, $priority);
+ $this->listeners[] = $events->attach('addItems.post', $callback, $priority);
+ }
+
+ /**
+ * Clear expired items by factor after writing new item(s)
+ *
+ * @param PostEvent $event
+ * @return void
+ */
+ public function clearExpiredByFactor(PostEvent $event)
+ {
+ $storage = $event->getStorage();
+ if (!($storage instanceof ClearExpiredInterface)) {
+ return;
+ }
+
+ $factor = $this->getOptions()->getClearingFactor();
+ if ($factor && mt_rand(1, $factor) == 1) {
+ $storage->clearExpired();
+ }
+ }
+}
diff --git a/library/Zend/Cache/Storage/Plugin/ExceptionHandler.php b/library/Zend/Cache/Storage/Plugin/ExceptionHandler.php
new file mode 100755
index 0000000000..e6acace0c3
--- /dev/null
+++ b/library/Zend/Cache/Storage/Plugin/ExceptionHandler.php
@@ -0,0 +1,79 @@
+listeners[] = $events->attach('getItem.exception', $callback, $priority);
+ $this->listeners[] = $events->attach('getItems.exception', $callback, $priority);
+
+ $this->listeners[] = $events->attach('hasItem.exception', $callback, $priority);
+ $this->listeners[] = $events->attach('hasItems.exception', $callback, $priority);
+
+ $this->listeners[] = $events->attach('getMetadata.exception', $callback, $priority);
+ $this->listeners[] = $events->attach('getMetadatas.exception', $callback, $priority);
+
+ // write
+ $this->listeners[] = $events->attach('setItem.exception', $callback, $priority);
+ $this->listeners[] = $events->attach('setItems.exception', $callback, $priority);
+
+ $this->listeners[] = $events->attach('addItem.exception', $callback, $priority);
+ $this->listeners[] = $events->attach('addItems.exception', $callback, $priority);
+
+ $this->listeners[] = $events->attach('replaceItem.exception', $callback, $priority);
+ $this->listeners[] = $events->attach('replaceItems.exception', $callback, $priority);
+
+ $this->listeners[] = $events->attach('touchItem.exception', $callback, $priority);
+ $this->listeners[] = $events->attach('touchItems.exception', $callback, $priority);
+
+ $this->listeners[] = $events->attach('removeItem.exception', $callback, $priority);
+ $this->listeners[] = $events->attach('removeItems.exception', $callback, $priority);
+
+ $this->listeners[] = $events->attach('checkAndSetItem.exception', $callback, $priority);
+
+ // increment / decrement item(s)
+ $this->listeners[] = $events->attach('incrementItem.exception', $callback, $priority);
+ $this->listeners[] = $events->attach('incrementItems.exception', $callback, $priority);
+
+ $this->listeners[] = $events->attach('decrementItem.exception', $callback, $priority);
+ $this->listeners[] = $events->attach('decrementItems.exception', $callback, $priority);
+
+ // utility
+ $this->listeners[] = $events->attach('clearExpired.exception', $callback, $priority);
+ }
+
+ /**
+ * On exception
+ *
+ * @param ExceptionEvent $event
+ * @return void
+ */
+ public function onException(ExceptionEvent $event)
+ {
+ $options = $this->getOptions();
+ $callback = $options->getExceptionCallback();
+ if ($callback) {
+ call_user_func($callback, $event->getException());
+ }
+
+ $event->setThrowException($options->getThrowExceptions());
+ }
+}
diff --git a/library/Zend/Cache/Storage/Plugin/IgnoreUserAbort.php b/library/Zend/Cache/Storage/Plugin/IgnoreUserAbort.php
new file mode 100755
index 0000000000..ddec6354c6
--- /dev/null
+++ b/library/Zend/Cache/Storage/Plugin/IgnoreUserAbort.php
@@ -0,0 +1,117 @@
+listeners[] = $events->attach('setItem.pre', $cbOnBefore, $priority);
+ $this->listeners[] = $events->attach('setItem.post', $cbOnAfter, $priority);
+ $this->listeners[] = $events->attach('setItem.exception', $cbOnAfter, $priority);
+
+ $this->listeners[] = $events->attach('setItems.pre', $cbOnBefore, $priority);
+ $this->listeners[] = $events->attach('setItems.post', $cbOnAfter, $priority);
+ $this->listeners[] = $events->attach('setItems.exception', $cbOnAfter, $priority);
+
+ $this->listeners[] = $events->attach('addItem.pre', $cbOnBefore, $priority);
+ $this->listeners[] = $events->attach('addItem.post', $cbOnAfter, $priority);
+ $this->listeners[] = $events->attach('addItem.exception', $cbOnAfter, $priority);
+
+ $this->listeners[] = $events->attach('addItems.pre', $cbOnBefore, $priority);
+ $this->listeners[] = $events->attach('addItems.post', $cbOnAfter, $priority);
+ $this->listeners[] = $events->attach('addItems.exception', $cbOnAfter, $priority);
+
+ $this->listeners[] = $events->attach('replaceItem.pre', $cbOnBefore, $priority);
+ $this->listeners[] = $events->attach('replaceItem.post', $cbOnAfter, $priority);
+ $this->listeners[] = $events->attach('replaceItem.exception', $cbOnAfter, $priority);
+
+ $this->listeners[] = $events->attach('replaceItems.pre', $cbOnBefore, $priority);
+ $this->listeners[] = $events->attach('replaceItems.post', $cbOnAfter, $priority);
+ $this->listeners[] = $events->attach('replaceItems.exception', $cbOnAfter, $priority);
+
+ $this->listeners[] = $events->attach('checkAndSetItem.pre', $cbOnBefore, $priority);
+ $this->listeners[] = $events->attach('checkAndSetItem.post', $cbOnAfter, $priority);
+ $this->listeners[] = $events->attach('checkAndSetItem.exception', $cbOnAfter, $priority);
+
+ // increment / decrement item(s)
+ $this->listeners[] = $events->attach('incrementItem.pre', $cbOnBefore, $priority);
+ $this->listeners[] = $events->attach('incrementItem.post', $cbOnAfter, $priority);
+ $this->listeners[] = $events->attach('incrementItem.exception', $cbOnAfter, $priority);
+
+ $this->listeners[] = $events->attach('incrementItems.pre', $cbOnBefore, $priority);
+ $this->listeners[] = $events->attach('incrementItems.post', $cbOnAfter, $priority);
+ $this->listeners[] = $events->attach('incrementItems.exception', $cbOnAfter, $priority);
+
+ $this->listeners[] = $events->attach('decrementItem.pre', $cbOnBefore, $priority);
+ $this->listeners[] = $events->attach('decrementItem.post', $cbOnAfter, $priority);
+ $this->listeners[] = $events->attach('decrementItem.exception', $cbOnAfter, $priority);
+
+ $this->listeners[] = $events->attach('decrementItems.pre', $cbOnBefore, $priority);
+ $this->listeners[] = $events->attach('decrementItems.post', $cbOnAfter, $priority);
+ $this->listeners[] = $events->attach('decrementItems.exception', $cbOnAfter, $priority);
+ }
+
+ /**
+ * Activate ignore_user_abort if not already done
+ * and save the target who activated it.
+ *
+ * @param Event $event
+ * @return void
+ */
+ public function onBefore(Event $event)
+ {
+ if ($this->activatedTarget === null && !ignore_user_abort(true)) {
+ $this->activatedTarget = $event->getTarget();
+ }
+ }
+
+ /**
+ * Reset ignore_user_abort if it's activated and if it's the same target
+ * who activated it.
+ *
+ * If exit_on_abort is enabled and the connection has been aborted
+ * exit the script.
+ *
+ * @param Event $event
+ * @return void
+ */
+ public function onAfter(Event $event)
+ {
+ if ($this->activatedTarget === $event->getTarget()) {
+ // exit if connection aborted
+ if ($this->getOptions()->getExitOnAbort() && connection_aborted()) {
+ exit;
+ }
+
+ // reset ignore_user_abort
+ ignore_user_abort(false);
+
+ // remove activated target
+ $this->activatedTarget = null;
+ }
+ }
+}
diff --git a/library/Zend/Cache/Storage/Plugin/OptimizeByFactor.php b/library/Zend/Cache/Storage/Plugin/OptimizeByFactor.php
new file mode 100755
index 0000000000..42643df7a3
--- /dev/null
+++ b/library/Zend/Cache/Storage/Plugin/OptimizeByFactor.php
@@ -0,0 +1,46 @@
+listeners[] = $events->attach('removeItem.post', $callback, $priority);
+ $this->listeners[] = $events->attach('removeItems.post', $callback, $priority);
+ }
+
+ /**
+ * Optimize by factor on a success _RESULT_
+ *
+ * @param PostEvent $event
+ * @return void
+ */
+ public function optimizeByFactor(PostEvent $event)
+ {
+ $storage = $event->getStorage();
+ if (!($storage instanceof OptimizableInterface)) {
+ return;
+ }
+
+ $factor = $this->getOptions()->getOptimizingFactor();
+ if ($factor && mt_rand(1, $factor) == 1) {
+ $storage->optimize();
+ }
+ }
+}
diff --git a/library/Zend/Cache/Storage/Plugin/PluginInterface.php b/library/Zend/Cache/Storage/Plugin/PluginInterface.php
new file mode 100755
index 0000000000..af3d007ca4
--- /dev/null
+++ b/library/Zend/Cache/Storage/Plugin/PluginInterface.php
@@ -0,0 +1,30 @@
+clearingFactor = $this->normalizeFactor($clearingFactor);
+ return $this;
+ }
+
+ /**
+ * Get automatic clearing factor
+ *
+ * Used by:
+ * - ClearExpiredByFactor
+ *
+ * @return int
+ */
+ public function getClearingFactor()
+ {
+ return $this->clearingFactor;
+ }
+
+ /**
+ * Set callback to call on intercepted exception
+ *
+ * Used by:
+ * - ExceptionHandler
+ *
+ * @param callable $exceptionCallback
+ * @throws Exception\InvalidArgumentException
+ * @return PluginOptions
+ */
+ public function setExceptionCallback($exceptionCallback)
+ {
+ if ($exceptionCallback !== null && !is_callable($exceptionCallback, true)) {
+ throw new Exception\InvalidArgumentException('Not a valid callback');
+ }
+ $this->exceptionCallback = $exceptionCallback;
+ return $this;
+ }
+
+ /**
+ * Get callback to call on intercepted exception
+ *
+ * Used by:
+ * - ExceptionHandler
+ *
+ * @return callable
+ */
+ public function getExceptionCallback()
+ {
+ return $this->exceptionCallback;
+ }
+
+ /**
+ * Exit if connection aborted and ignore_user_abort is disabled.
+ *
+ * @param bool $exitOnAbort
+ * @return PluginOptions
+ */
+ public function setExitOnAbort($exitOnAbort)
+ {
+ $this->exitOnAbort = (bool) $exitOnAbort;
+ return $this;
+ }
+
+ /**
+ * Exit if connection aborted and ignore_user_abort is disabled.
+ *
+ * @return bool
+ */
+ public function getExitOnAbort()
+ {
+ return $this->exitOnAbort;
+ }
+
+ /**
+ * Set automatic optimizing factor
+ *
+ * Used by:
+ * - OptimizeByFactor
+ *
+ * @param int $optimizingFactor
+ * @return PluginOptions
+ */
+ public function setOptimizingFactor($optimizingFactor)
+ {
+ $this->optimizingFactor = $this->normalizeFactor($optimizingFactor);
+ return $this;
+ }
+
+ /**
+ * Set automatic optimizing factor
+ *
+ * Used by:
+ * - OptimizeByFactor
+ *
+ * @return int
+ */
+ public function getOptimizingFactor()
+ {
+ return $this->optimizingFactor;
+ }
+
+ /**
+ * Set serializer
+ *
+ * Used by:
+ * - Serializer
+ *
+ * @param string|SerializerAdapter $serializer
+ * @throws Exception\InvalidArgumentException
+ * @return self
+ */
+ public function setSerializer($serializer)
+ {
+ if (!is_string($serializer) && !$serializer instanceof SerializerAdapter) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects either a string serializer name or Zend\Serializer\Adapter\AdapterInterface instance; '
+ . 'received "%s"',
+ __METHOD__,
+ (is_object($serializer) ? get_class($serializer) : gettype($serializer))
+ ));
+ }
+ $this->serializer = $serializer;
+ return $this;
+ }
+
+ /**
+ * Get serializer
+ *
+ * Used by:
+ * - Serializer
+ *
+ * @return SerializerAdapter
+ */
+ public function getSerializer()
+ {
+ if (!$this->serializer instanceof SerializerAdapter) {
+ // use default serializer
+ if (!$this->serializer) {
+ $this->setSerializer(SerializerFactory::getDefaultAdapter());
+ // instantiate by class name + serializer_options
+ } else {
+ $options = $this->getSerializerOptions();
+ $this->setSerializer(SerializerFactory::factory($this->serializer, $options));
+ }
+ }
+ return $this->serializer;
+ }
+
+ /**
+ * Set configuration options for instantiating a serializer adapter
+ *
+ * Used by:
+ * - Serializer
+ *
+ * @param mixed $serializerOptions
+ * @return PluginOptions
+ */
+ public function setSerializerOptions($serializerOptions)
+ {
+ $this->serializerOptions = $serializerOptions;
+ return $this;
+ }
+
+ /**
+ * Get configuration options for instantiating a serializer adapter
+ *
+ * Used by:
+ * - Serializer
+ *
+ * @return array
+ */
+ public function getSerializerOptions()
+ {
+ return $this->serializerOptions;
+ }
+
+ /**
+ * Set flag indicating we should re-throw exceptions
+ *
+ * Used by:
+ * - ExceptionHandler
+ *
+ * @param bool $throwExceptions
+ * @return PluginOptions
+ */
+ public function setThrowExceptions($throwExceptions)
+ {
+ $this->throwExceptions = (bool) $throwExceptions;
+ return $this;
+ }
+
+ /**
+ * Should we re-throw exceptions?
+ *
+ * Used by:
+ * - ExceptionHandler
+ *
+ * @return bool
+ */
+ public function getThrowExceptions()
+ {
+ return $this->throwExceptions;
+ }
+
+ /**
+ * Normalize a factor
+ *
+ * Cast to int and ensure we have a value greater than zero.
+ *
+ * @param int $factor
+ * @return int
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function normalizeFactor($factor)
+ {
+ $factor = (int) $factor;
+ if ($factor < 0) {
+ throw new Exception\InvalidArgumentException(
+ "Invalid factor '{$factor}': must be greater or equal 0"
+ );
+ }
+ return $factor;
+ }
+}
diff --git a/library/Zend/Cache/Storage/Plugin/Serializer.php b/library/Zend/Cache/Storage/Plugin/Serializer.php
new file mode 100755
index 0000000000..c785e751eb
--- /dev/null
+++ b/library/Zend/Cache/Storage/Plugin/Serializer.php
@@ -0,0 +1,259 @@
+listeners[] = $events->attach('getItem.post', array($this, 'onReadItemPost'), $postPriority);
+ $this->listeners[] = $events->attach('getItems.post', array($this, 'onReadItemsPost'), $postPriority);
+
+ // write
+ $this->listeners[] = $events->attach('setItem.pre', array($this, 'onWriteItemPre'), $prePriority);
+ $this->listeners[] = $events->attach('setItems.pre', array($this, 'onWriteItemsPre'), $prePriority);
+
+ $this->listeners[] = $events->attach('addItem.pre', array($this, 'onWriteItemPre'), $prePriority);
+ $this->listeners[] = $events->attach('addItems.pre', array($this, 'onWriteItemsPre'), $prePriority);
+
+ $this->listeners[] = $events->attach('replaceItem.pre', array($this, 'onWriteItemPre'), $prePriority);
+ $this->listeners[] = $events->attach('replaceItems.pre', array($this, 'onWriteItemsPre'), $prePriority);
+
+ $this->listeners[] = $events->attach('checkAndSetItem.pre', array($this, 'onWriteItemPre'), $prePriority);
+
+ // increment / decrement item(s)
+ $this->listeners[] = $events->attach('incrementItem.pre', array($this, 'onIncrementItemPre'), $prePriority);
+ $this->listeners[] = $events->attach('incrementItems.pre', array($this, 'onIncrementItemsPre'), $prePriority);
+
+ $this->listeners[] = $events->attach('decrementItem.pre', array($this, 'onDecrementItemPre'), $prePriority);
+ $this->listeners[] = $events->attach('decrementItems.pre', array($this, 'onDecrementItemsPre'), $prePriority);
+
+ // overwrite capabilities
+ $this->listeners[] = $events->attach('getCapabilities.post', array($this, 'onGetCapabilitiesPost'), $postPriority);
+ }
+
+ /**
+ * On read item post
+ *
+ * @param PostEvent $event
+ * @return void
+ */
+ public function onReadItemPost(PostEvent $event)
+ {
+ $serializer = $this->getOptions()->getSerializer();
+ $result = $event->getResult();
+ $result = $serializer->unserialize($result);
+ $event->setResult($result);
+ }
+
+ /**
+ * On read items post
+ *
+ * @param PostEvent $event
+ * @return void
+ */
+ public function onReadItemsPost(PostEvent $event)
+ {
+ $serializer = $this->getOptions()->getSerializer();
+ $result = $event->getResult();
+ foreach ($result as &$value) {
+ $value = $serializer->unserialize($value);
+ }
+ $event->setResult($result);
+ }
+
+ /**
+ * On write item pre
+ *
+ * @param Event $event
+ * @return void
+ */
+ public function onWriteItemPre(Event $event)
+ {
+ $serializer = $this->getOptions()->getSerializer();
+ $params = $event->getParams();
+ $params['value'] = $serializer->serialize($params['value']);
+ }
+
+ /**
+ * On write items pre
+ *
+ * @param Event $event
+ * @return void
+ */
+ public function onWriteItemsPre(Event $event)
+ {
+ $serializer = $this->getOptions()->getSerializer();
+ $params = $event->getParams();
+ foreach ($params['keyValuePairs'] as &$value) {
+ $value = $serializer->serialize($value);
+ }
+ }
+
+ /**
+ * On increment item pre
+ *
+ * @param Event $event
+ * @return mixed
+ */
+ public function onIncrementItemPre(Event $event)
+ {
+ $storage = $event->getTarget();
+ $params = $event->getParams();
+ $casToken = null;
+ $success = null;
+ $oldValue = $storage->getItem($params['key'], $success, $casToken);
+ $newValue = $oldValue + $params['value'];
+
+ if ($success) {
+ $storage->checkAndSetItem($casToken, $params['key'], $oldValue + $params['value']);
+ $result = $newValue;
+ } else {
+ $result = false;
+ }
+
+ $event->stopPropagation(true);
+ return $result;
+ }
+
+ /**
+ * On increment items pre
+ *
+ * @param Event $event
+ * @return mixed
+ */
+ public function onIncrementItemsPre(Event $event)
+ {
+ $storage = $event->getTarget();
+ $params = $event->getParams();
+ $keyValuePairs = $storage->getItems(array_keys($params['keyValuePairs']));
+ foreach ($params['keyValuePairs'] as $key => & $value) {
+ if (isset($keyValuePairs[$key])) {
+ $keyValuePairs[$key]+= $value;
+ } else {
+ $keyValuePairs[$key] = $value;
+ }
+ }
+
+ $failedKeys = $storage->setItems($keyValuePairs);
+ foreach ($failedKeys as $failedKey) {
+ unset($keyValuePairs[$failedKey]);
+ }
+
+ $event->stopPropagation(true);
+ return $keyValuePairs;
+ }
+
+ /**
+ * On decrement item pre
+ *
+ * @param Event $event
+ * @return mixed
+ */
+ public function onDecrementItemPre(Event $event)
+ {
+ $storage = $event->getTarget();
+ $params = $event->getParams();
+ $success = null;
+ $casToken = null;
+ $oldValue = $storage->getItem($params['key'], $success, $casToken);
+ $newValue = $oldValue - $params['value'];
+
+ if ($success) {
+ $storage->checkAndSetItem($casToken, $params['key'], $oldValue + $params['value']);
+ $result = $newValue;
+ } else {
+ $result = false;
+ }
+
+ $event->stopPropagation(true);
+ return $result;
+ }
+
+ /**
+ * On decrement items pre
+ *
+ * @param Event $event
+ * @return mixed
+ */
+ public function onDecrementItemsPre(Event $event)
+ {
+ $storage = $event->getTarget();
+ $params = $event->getParams();
+ $keyValuePairs = $storage->getItems(array_keys($params['keyValuePairs']));
+ foreach ($params['keyValuePairs'] as $key => &$value) {
+ if (isset($keyValuePairs[$key])) {
+ $keyValuePairs[$key]-= $value;
+ } else {
+ $keyValuePairs[$key] = -$value;
+ }
+ }
+
+ $failedKeys = $storage->setItems($keyValuePairs);
+ foreach ($failedKeys as $failedKey) {
+ unset($keyValuePairs[$failedKey]);
+ }
+
+ $event->stopPropagation(true);
+ return $keyValuePairs;
+ }
+
+ /**
+ * On get capabilities
+ *
+ * @param PostEvent $event
+ * @return void
+ */
+ public function onGetCapabilitiesPost(PostEvent $event)
+ {
+ $baseCapabilities = $event->getResult();
+ $index = spl_object_hash($baseCapabilities);
+
+ if (!isset($this->capabilities[$index])) {
+ $this->capabilities[$index] = new Capabilities(
+ $baseCapabilities->getAdapter(),
+ new stdClass(), // marker
+ array('supportedDatatypes' => array(
+ 'NULL' => true,
+ 'boolean' => true,
+ 'integer' => true,
+ 'double' => true,
+ 'string' => true,
+ 'array' => true,
+ 'object' => 'object',
+ 'resource' => false,
+ )),
+ $baseCapabilities
+ );
+ }
+
+ $event->setResult($this->capabilities[$index]);
+ }
+}
diff --git a/library/Zend/Cache/Storage/PluginManager.php b/library/Zend/Cache/Storage/PluginManager.php
new file mode 100755
index 0000000000..a799a37d34
--- /dev/null
+++ b/library/Zend/Cache/Storage/PluginManager.php
@@ -0,0 +1,66 @@
+ 'Zend\Cache\Storage\Plugin\ClearExpiredByFactor',
+ 'exceptionhandler' => 'Zend\Cache\Storage\Plugin\ExceptionHandler',
+ 'ignoreuserabort' => 'Zend\Cache\Storage\Plugin\IgnoreUserAbort',
+ 'optimizebyfactor' => 'Zend\Cache\Storage\Plugin\OptimizeByFactor',
+ 'serializer' => 'Zend\Cache\Storage\Plugin\Serializer',
+ );
+
+ /**
+ * Do not share by default
+ *
+ * @var array
+ */
+ protected $shareByDefault = false;
+
+ /**
+ * Validate the plugin
+ *
+ * Checks that the plugin loaded is an instance of Plugin\PluginInterface.
+ *
+ * @param mixed $plugin
+ * @return void
+ * @throws Exception\RuntimeException if invalid
+ */
+ public function validatePlugin($plugin)
+ {
+ if ($plugin instanceof Plugin\PluginInterface) {
+ // we're okay
+ return;
+ }
+
+ throw new Exception\RuntimeException(sprintf(
+ 'Plugin of type %s is invalid; must implement %s\Plugin\PluginInterface',
+ (is_object($plugin) ? get_class($plugin) : gettype($plugin)),
+ __NAMESPACE__
+ ));
+ }
+}
diff --git a/library/Zend/Cache/Storage/PostEvent.php b/library/Zend/Cache/Storage/PostEvent.php
new file mode 100755
index 0000000000..9302b0814e
--- /dev/null
+++ b/library/Zend/Cache/Storage/PostEvent.php
@@ -0,0 +1,60 @@
+setResult($result);
+ }
+
+ /**
+ * Set the result/return value
+ *
+ * @param mixed $value
+ * @return PostEvent
+ */
+ public function setResult(& $value)
+ {
+ $this->result = & $value;
+ return $this;
+ }
+
+ /**
+ * Get the result/return value
+ *
+ * @return mixed
+ */
+ public function & getResult()
+ {
+ return $this->result;
+ }
+}
diff --git a/library/Zend/Cache/Storage/StorageInterface.php b/library/Zend/Cache/Storage/StorageInterface.php
new file mode 100755
index 0000000000..c89ca5bef2
--- /dev/null
+++ b/library/Zend/Cache/Storage/StorageInterface.php
@@ -0,0 +1,246 @@
+ $v) {
+ $pluginPrio = 1; // default priority
+
+ if (is_string($k)) {
+ if (!is_array($v)) {
+ throw new Exception\InvalidArgumentException(
+ "'plugins.{$k}' needs to be an array"
+ );
+ }
+ $pluginName = $k;
+ $pluginOptions = $v;
+ } elseif (is_array($v)) {
+ if (!isset($v['name'])) {
+ throw new Exception\InvalidArgumentException("Invalid plugins[{$k}] or missing plugins[{$k}].name");
+ }
+ $pluginName = (string) $v['name'];
+
+ if (isset($v['options'])) {
+ $pluginOptions = $v['options'];
+ } else {
+ $pluginOptions = array();
+ }
+
+ if (isset($v['priority'])) {
+ $pluginPrio = $v['priority'];
+ }
+ } else {
+ $pluginName = $v;
+ $pluginOptions = array();
+ }
+
+ $plugin = static::pluginFactory($pluginName, $pluginOptions);
+ $adapter->addPlugin($plugin, $pluginPrio);
+ }
+ }
+
+ return $adapter;
+ }
+
+ /**
+ * Instantiate a storage adapter
+ *
+ * @param string|Storage\StorageInterface $adapterName
+ * @param array|Traversable|Storage\Adapter\AdapterOptions $options
+ * @return Storage\StorageInterface
+ * @throws Exception\RuntimeException
+ */
+ public static function adapterFactory($adapterName, $options = array())
+ {
+ if ($adapterName instanceof Storage\StorageInterface) {
+ // $adapterName is already an adapter object
+ $adapter = $adapterName;
+ } else {
+ $adapter = static::getAdapterPluginManager()->get($adapterName);
+ }
+
+ if ($options) {
+ $adapter->setOptions($options);
+ }
+
+ return $adapter;
+ }
+
+ /**
+ * Get the adapter plugin manager
+ *
+ * @return Storage\AdapterPluginManager
+ */
+ public static function getAdapterPluginManager()
+ {
+ if (static::$adapters === null) {
+ static::$adapters = new Storage\AdapterPluginManager();
+ }
+ return static::$adapters;
+ }
+
+ /**
+ * Change the adapter plugin manager
+ *
+ * @param Storage\AdapterPluginManager $adapters
+ * @return void
+ */
+ public static function setAdapterPluginManager(Storage\AdapterPluginManager $adapters)
+ {
+ static::$adapters = $adapters;
+ }
+
+ /**
+ * Resets the internal adapter plugin manager
+ *
+ * @return void
+ */
+ public static function resetAdapterPluginManager()
+ {
+ static::$adapters = null;
+ }
+
+ /**
+ * Instantiate a storage plugin
+ *
+ * @param string|Storage\Plugin\PluginInterface $pluginName
+ * @param array|Traversable|Storage\Plugin\PluginOptions $options
+ * @return Storage\Plugin\PluginInterface
+ * @throws Exception\RuntimeException
+ */
+ public static function pluginFactory($pluginName, $options = array())
+ {
+ if ($pluginName instanceof Storage\Plugin\PluginInterface) {
+ // $pluginName is already a plugin object
+ $plugin = $pluginName;
+ } else {
+ $plugin = static::getPluginManager()->get($pluginName);
+ }
+
+ if (!$options instanceof Storage\Plugin\PluginOptions) {
+ $options = new Storage\Plugin\PluginOptions($options);
+ }
+
+ if ($options) {
+ $plugin->setOptions($options);
+ }
+
+ return $plugin;
+ }
+
+ /**
+ * Get the plugin manager
+ *
+ * @return Storage\PluginManager
+ */
+ public static function getPluginManager()
+ {
+ if (static::$plugins === null) {
+ static::$plugins = new Storage\PluginManager();
+ }
+ return static::$plugins;
+ }
+
+ /**
+ * Change the plugin manager
+ *
+ * @param Storage\PluginManager $plugins
+ * @return void
+ */
+ public static function setPluginManager(Storage\PluginManager $plugins)
+ {
+ static::$plugins = $plugins;
+ }
+
+ /**
+ * Resets the internal plugin manager
+ *
+ * @return void
+ */
+ public static function resetPluginManager()
+ {
+ static::$plugins = null;
+ }
+}
diff --git a/library/Zend/Cache/composer.json b/library/Zend/Cache/composer.json
new file mode 100755
index 0000000000..3d20dff9a3
--- /dev/null
+++ b/library/Zend/Cache/composer.json
@@ -0,0 +1,40 @@
+{
+ "name": "zendframework/zend-cache",
+ "description": "provides a generic way to cache any data",
+ "license": "BSD-3-Clause",
+ "keywords": [
+ "zf2",
+ "cache"
+ ],
+ "homepage": "https://github.com/zendframework/zf2",
+ "autoload": {
+ "psr-0": {
+ "Zend\\Cache\\": ""
+ }
+ },
+ "target-dir": "Zend/Cache",
+ "require": {
+ "php": ">=5.3.23",
+ "zendframework/zend-stdlib": "self.version",
+ "zendframework/zend-servicemanager": "self.version",
+ "zendframework/zend-serializer": "self.version",
+ "zendframework/zend-eventmanager": "self.version"
+ },
+ "require-dev": {
+ "zendframework/zend-session": "self.version"
+ },
+ "suggest": {
+ "zendframework/zend-serializer": "Zend\\Serializer component",
+ "zendframework/zend-session": "Zend\\Session component",
+ "ext-apc": "APC >= 3.1.6 to use the APC storage adapter",
+ "ext-dba": "DBA, to use the DBA storage adapter",
+ "ext-memcached": "Memcached >= 1.0.0 to use the Memcached storage adapter",
+ "ext-wincache": "WinCache, to use the WinCache storage adapter"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev",
+ "dev-develop": "2.4-dev"
+ }
+ }
+}
diff --git a/library/Zend/Captcha/AbstractAdapter.php b/library/Zend/Captcha/AbstractAdapter.php
new file mode 100755
index 0000000000..11784d69e7
--- /dev/null
+++ b/library/Zend/Captcha/AbstractAdapter.php
@@ -0,0 +1,135 @@
+name;
+ }
+
+ /**
+ * Set name
+ *
+ * @param string $name
+ * @return AbstractAdapter
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * Set single option for the object
+ *
+ * @param string $key
+ * @param string $value
+ * @return AbstractAdapter
+ */
+ public function setOption($key, $value)
+ {
+ if (in_array(strtolower($key), $this->skipOptions)) {
+ return $this;
+ }
+
+ $method = 'set' . ucfirst($key);
+ if (method_exists($this, $method)) {
+ // Setter exists; use it
+ $this->$method($value);
+ $this->options[$key] = $value;
+ } elseif (property_exists($this, $key)) {
+ // Assume it's metadata
+ $this->$key = $value;
+ $this->options[$key] = $value;
+ }
+ return $this;
+ }
+
+ /**
+ * Set object state from options array
+ *
+ * @param array|Traversable $options
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractAdapter
+ */
+ public function setOptions($options = array())
+ {
+ if (!is_array($options) && !$options instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(__METHOD__ . ' expects an array or Traversable');
+ }
+
+ foreach ($options as $key => $value) {
+ $this->setOption($key, $value);
+ }
+ return $this;
+ }
+
+ /**
+ * Retrieve options representing object state
+ *
+ * @return array
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * Get helper name used to render captcha
+ *
+ * By default, return empty string, indicating no helper needed.
+ *
+ * @return string
+ */
+ public function getHelperName()
+ {
+ return '';
+ }
+}
diff --git a/library/Zend/Captcha/AbstractWord.php b/library/Zend/Captcha/AbstractWord.php
new file mode 100755
index 0000000000..5ad8036611
--- /dev/null
+++ b/library/Zend/Captcha/AbstractWord.php
@@ -0,0 +1,407 @@
+ 'Empty captcha value',
+ self::MISSING_ID => 'Captcha ID field is missing',
+ self::BAD_CAPTCHA => 'Captcha value is wrong',
+ );
+
+ /**
+ * Length of the word to generate
+ *
+ * @var int
+ */
+ protected $wordlen = 8;
+
+ /**
+ * Retrieve session class to utilize
+ *
+ * @return string
+ */
+ public function getSessionClass()
+ {
+ return $this->sessionClass;
+ }
+
+ /**
+ * Set session class for persistence
+ *
+ * @param string $sessionClass
+ * @return AbstractWord
+ */
+ public function setSessionClass($sessionClass)
+ {
+ $this->sessionClass = $sessionClass;
+ return $this;
+ }
+
+ /**
+ * Retrieve word length to use when generating captcha
+ *
+ * @return int
+ */
+ public function getWordlen()
+ {
+ return $this->wordlen;
+ }
+
+ /**
+ * Set word length of captcha
+ *
+ * @param int $wordlen
+ * @return AbstractWord
+ */
+ public function setWordlen($wordlen)
+ {
+ $this->wordlen = $wordlen;
+ return $this;
+ }
+
+ /**
+ * Retrieve captcha ID
+ *
+ * @return string
+ */
+ public function getId()
+ {
+ if (null === $this->id) {
+ $this->setId($this->generateRandomId());
+ }
+ return $this->id;
+ }
+
+ /**
+ * Set captcha identifier
+ *
+ * @param string $id
+ * @return AbstractWord
+ */
+ protected function setId($id)
+ {
+ $this->id = $id;
+ return $this;
+ }
+
+ /**
+ * Set timeout for session token
+ *
+ * @param int $ttl
+ * @return AbstractWord
+ */
+ public function setTimeout($ttl)
+ {
+ $this->timeout = (int) $ttl;
+ return $this;
+ }
+
+ /**
+ * Get session token timeout
+ *
+ * @return int
+ */
+ public function getTimeout()
+ {
+ return $this->timeout;
+ }
+
+ /**
+ * Sets if session should be preserved on generate()
+ *
+ * @param bool $keepSession Should session be kept on generate()?
+ * @return AbstractWord
+ */
+ public function setKeepSession($keepSession)
+ {
+ $this->keepSession = $keepSession;
+ return $this;
+ }
+
+ /**
+ * Numbers should be included in the pattern?
+ *
+ * @return bool
+ */
+ public function getUseNumbers()
+ {
+ return $this->useNumbers;
+ }
+
+ /**
+ * Set if numbers should be included in the pattern
+ *
+ * @param bool $useNumbers numbers should be included in the pattern?
+ * @return AbstractWord
+ */
+ public function setUseNumbers($useNumbers)
+ {
+ $this->useNumbers = $useNumbers;
+ return $this;
+ }
+
+ /**
+ * Get session object
+ *
+ * @throws Exception\InvalidArgumentException
+ * @return Container
+ */
+ public function getSession()
+ {
+ if (!isset($this->session) || (null === $this->session)) {
+ $id = $this->getId();
+ if (!class_exists($this->sessionClass)) {
+ throw new Exception\InvalidArgumentException("Session class $this->sessionClass not found");
+ }
+ $this->session = new $this->sessionClass('Zend_Form_Captcha_' . $id);
+ $this->session->setExpirationHops(1, null);
+ $this->session->setExpirationSeconds($this->getTimeout());
+ }
+ return $this->session;
+ }
+
+ /**
+ * Set session namespace object
+ *
+ * @param Container $session
+ * @return AbstractWord
+ */
+ public function setSession(Container $session)
+ {
+ $this->session = $session;
+ if ($session) {
+ $this->keepSession = true;
+ }
+ return $this;
+ }
+
+ /**
+ * Get captcha word
+ *
+ * @return string
+ */
+ public function getWord()
+ {
+ if (empty($this->word)) {
+ $session = $this->getSession();
+ $this->word = $session->word;
+ }
+ return $this->word;
+ }
+
+ /**
+ * Set captcha word
+ *
+ * @param string $word
+ * @return AbstractWord
+ */
+ protected function setWord($word)
+ {
+ $session = $this->getSession();
+ $session->word = $word;
+ $this->word = $word;
+ return $this;
+ }
+
+ /**
+ * Generate new random word
+ *
+ * @return string
+ */
+ protected function generateWord()
+ {
+ $word = '';
+ $wordLen = $this->getWordLen();
+ $vowels = $this->useNumbers ? static::$VN : static::$V;
+ $consonants = $this->useNumbers ? static::$CN : static::$C;
+
+ for ($i=0; $i < $wordLen; $i = $i + 2) {
+ // generate word with mix of vowels and consonants
+ $consonant = $consonants[array_rand($consonants)];
+ $vowel = $vowels[array_rand($vowels)];
+ $word .= $consonant . $vowel;
+ }
+
+ if (strlen($word) > $wordLen) {
+ $word = substr($word, 0, $wordLen);
+ }
+
+ return $word;
+ }
+
+ /**
+ * Generate new session ID and new word
+ *
+ * @return string session ID
+ */
+ public function generate()
+ {
+ if (!$this->keepSession) {
+ $this->session = null;
+ }
+ $id = $this->generateRandomId();
+ $this->setId($id);
+ $word = $this->generateWord();
+ $this->setWord($word);
+ return $id;
+ }
+
+ /**
+ * Generate a random identifier
+ *
+ * @return string
+ */
+ protected function generateRandomId()
+ {
+ return md5(Rand::getBytes(32));
+ }
+
+ /**
+ * Validate the word
+ *
+ * @see Zend\Validator\ValidatorInterface::isValid()
+ * @param mixed $value
+ * @param mixed $context
+ * @return bool
+ */
+ public function isValid($value, $context = null)
+ {
+ if (!is_array($value)) {
+ if (!is_array($context)) {
+ $this->error(self::MISSING_VALUE);
+ return false;
+ }
+ $value = $context;
+ }
+
+ $name = $this->getName();
+
+ if (isset($value[$name])) {
+ $value = $value[$name];
+ }
+
+ if (!isset($value['input'])) {
+ $this->error(self::MISSING_VALUE);
+ return false;
+ }
+ $input = strtolower($value['input']);
+ $this->setValue($input);
+
+ if (!isset($value['id'])) {
+ $this->error(self::MISSING_ID);
+ return false;
+ }
+
+ $this->id = $value['id'];
+ if ($input !== $this->getWord()) {
+ $this->error(self::BAD_CAPTCHA);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get helper name used to render captcha
+ *
+ * @return string
+ */
+ public function getHelperName()
+ {
+ return 'captcha/word';
+ }
+}
diff --git a/library/Zend/Captcha/AdapterInterface.php b/library/Zend/Captcha/AdapterInterface.php
new file mode 100755
index 0000000000..227daa5c76
--- /dev/null
+++ b/library/Zend/Captcha/AdapterInterface.php
@@ -0,0 +1,49 @@
+label = $label;
+ }
+
+ /**
+ * Retrieve the label for the CAPTCHA
+ * @return string
+ */
+ public function getLabel()
+ {
+ return $this->label;
+ }
+
+ /**
+ * Retrieve optional view helper name to use when rendering this captcha
+ *
+ * @return string
+ */
+ public function getHelperName()
+ {
+ return 'captcha/dumb';
+ }
+}
diff --git a/library/Zend/Captcha/Exception/DomainException.php b/library/Zend/Captcha/Exception/DomainException.php
new file mode 100755
index 0000000000..ac900097b4
--- /dev/null
+++ b/library/Zend/Captcha/Exception/DomainException.php
@@ -0,0 +1,14 @@
+ 'Zend\Captcha\Dumb',
+ 'figlet' => 'Zend\Captcha\Figlet',
+ 'image' => 'Zend\Captcha\Image',
+ 'recaptcha' => 'Zend\Captcha\ReCaptcha',
+ );
+
+ /**
+ * Create a captcha adapter instance
+ *
+ * @param array|Traversable $options
+ * @return AdapterInterface
+ * @throws Exception\InvalidArgumentException for a non-array, non-Traversable $options
+ * @throws Exception\DomainException if class is missing or invalid
+ */
+ public static function factory($options)
+ {
+ if ($options instanceof Traversable) {
+ $options = ArrayUtils::iteratorToArray($options);
+ }
+
+ if (!is_array($options)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects an array or Traversable argument; received "%s"',
+ __METHOD__,
+ (is_object($options) ? get_class($options) : gettype($options))
+ ));
+ }
+
+ if (!isset($options['class'])) {
+ throw new Exception\DomainException(sprintf(
+ '%s expects a "class" attribute in the options; none provided',
+ __METHOD__
+ ));
+ }
+
+ $class = $options['class'];
+ if (isset(static::$classMap[strtolower($class)])) {
+ $class = static::$classMap[strtolower($class)];
+ }
+ if (!class_exists($class)) {
+ throw new Exception\DomainException(sprintf(
+ '%s expects the "class" attribute to resolve to an existing class; received "%s"',
+ __METHOD__,
+ $class
+ ));
+ }
+
+ unset($options['class']);
+
+ if (isset($options['options'])) {
+ $options = $options['options'];
+ }
+ $captcha = new $class($options);
+
+ if (!$captcha instanceof AdapterInterface) {
+ throw new Exception\DomainException(sprintf(
+ '%s expects the "class" attribute to resolve to a valid Zend\Captcha\AdapterInterface instance; received "%s"',
+ __METHOD__,
+ $class
+ ));
+ }
+
+ return $captcha;
+ }
+}
diff --git a/library/Zend/Captcha/Figlet.php b/library/Zend/Captcha/Figlet.php
new file mode 100755
index 0000000000..002f5f3072
--- /dev/null
+++ b/library/Zend/Captcha/Figlet.php
@@ -0,0 +1,69 @@
+figlet = new FigletManager($options);
+ }
+
+ /**
+ * Retrieve the composed figlet manager
+ *
+ * @return FigletManager
+ */
+ public function getFiglet()
+ {
+ return $this->figlet;
+ }
+
+ /**
+ * Generate new captcha
+ *
+ * @return string
+ */
+ public function generate()
+ {
+ $this->useNumbers = false;
+ return parent::generate();
+ }
+
+ /**
+ * Get helper name used to render captcha
+ *
+ * @return string
+ */
+ public function getHelperName()
+ {
+ return 'captcha/figlet';
+ }
+}
diff --git a/library/Zend/Captcha/Image.php b/library/Zend/Captcha/Image.php
new file mode 100755
index 0000000000..7f91b9128d
--- /dev/null
+++ b/library/Zend/Captcha/Image.php
@@ -0,0 +1,628 @@
+imgAlt;
+ }
+
+ /**
+ * @return string
+ */
+ public function getStartImage()
+ {
+ return $this->startImage;
+ }
+
+ /**
+ * @return int
+ */
+ public function getDotNoiseLevel()
+ {
+ return $this->dotNoiseLevel;
+ }
+
+ /**
+ * @return int
+ */
+ public function getLineNoiseLevel()
+ {
+ return $this->lineNoiseLevel;
+ }
+
+ /**
+ * Get captcha expiration
+ *
+ * @return int
+ */
+ public function getExpiration()
+ {
+ return $this->expiration;
+ }
+
+ /**
+ * Get garbage collection frequency
+ *
+ * @return int
+ */
+ public function getGcFreq()
+ {
+ return $this->gcFreq;
+ }
+
+ /**
+ * Get font to use when generating captcha
+ *
+ * @return string
+ */
+ public function getFont()
+ {
+ return $this->font;
+ }
+
+ /**
+ * Get font size
+ *
+ * @return int
+ */
+ public function getFontSize()
+ {
+ return $this->fsize;
+ }
+
+ /**
+ * Get captcha image height
+ *
+ * @return int
+ */
+ public function getHeight()
+ {
+ return $this->height;
+ }
+
+ /**
+ * Get captcha image directory
+ *
+ * @return string
+ */
+ public function getImgDir()
+ {
+ return $this->imgDir;
+ }
+
+ /**
+ * Get captcha image base URL
+ *
+ * @return string
+ */
+ public function getImgUrl()
+ {
+ return $this->imgUrl;
+ }
+
+ /**
+ * Get captcha image file suffix
+ *
+ * @return string
+ */
+ public function getSuffix()
+ {
+ return $this->suffix;
+ }
+
+ /**
+ * Get captcha image width
+ *
+ * @return int
+ */
+ public function getWidth()
+ {
+ return $this->width;
+ }
+
+ /**
+ * @param string $startImage
+ * @return Image
+ */
+ public function setStartImage($startImage)
+ {
+ $this->startImage = $startImage;
+ return $this;
+ }
+
+ /**
+ * @param int $dotNoiseLevel
+ * @return Image
+ */
+ public function setDotNoiseLevel($dotNoiseLevel)
+ {
+ $this->dotNoiseLevel = $dotNoiseLevel;
+ return $this;
+ }
+
+ /**
+ * @param int $lineNoiseLevel
+ * @return Image
+ */
+ public function setLineNoiseLevel($lineNoiseLevel)
+ {
+ $this->lineNoiseLevel = $lineNoiseLevel;
+ return $this;
+ }
+
+ /**
+ * Set captcha expiration
+ *
+ * @param int $expiration
+ * @return Image
+ */
+ public function setExpiration($expiration)
+ {
+ $this->expiration = $expiration;
+ return $this;
+ }
+
+ /**
+ * Set garbage collection frequency
+ *
+ * @param int $gcFreq
+ * @return Image
+ */
+ public function setGcFreq($gcFreq)
+ {
+ $this->gcFreq = $gcFreq;
+ return $this;
+ }
+
+ /**
+ * Set captcha font
+ *
+ * @param string $font
+ * @return Image
+ */
+ public function setFont($font)
+ {
+ $this->font = $font;
+ return $this;
+ }
+
+ /**
+ * Set captcha font size
+ *
+ * @param int $fsize
+ * @return Image
+ */
+ public function setFontSize($fsize)
+ {
+ $this->fsize = $fsize;
+ return $this;
+ }
+
+ /**
+ * Set captcha image height
+ *
+ * @param int $height
+ * @return Image
+ */
+ public function setHeight($height)
+ {
+ $this->height = $height;
+ return $this;
+ }
+
+ /**
+ * Set captcha image storage directory
+ *
+ * @param string $imgDir
+ * @return Image
+ */
+ public function setImgDir($imgDir)
+ {
+ $this->imgDir = rtrim($imgDir, "/\\") . '/';
+ return $this;
+ }
+
+ /**
+ * Set captcha image base URL
+ *
+ * @param string $imgUrl
+ * @return Image
+ */
+ public function setImgUrl($imgUrl)
+ {
+ $this->imgUrl = rtrim($imgUrl, "/\\") . '/';
+ return $this;
+ }
+
+ /**
+ * @param string $imgAlt
+ * @return Image
+ */
+ public function setImgAlt($imgAlt)
+ {
+ $this->imgAlt = $imgAlt;
+ return $this;
+ }
+
+ /**
+ * Set captcha image filename suffix
+ *
+ * @param string $suffix
+ * @return Image
+ */
+ public function setSuffix($suffix)
+ {
+ $this->suffix = $suffix;
+ return $this;
+ }
+
+ /**
+ * Set captcha image width
+ *
+ * @param int $width
+ * @return Image
+ */
+ public function setWidth($width)
+ {
+ $this->width = $width;
+ return $this;
+ }
+
+ /**
+ * Generate random frequency
+ *
+ * @return float
+ */
+ protected function randomFreq()
+ {
+ return mt_rand(700000, 1000000) / 15000000;
+ }
+
+ /**
+ * Generate random phase
+ *
+ * @return float
+ */
+ protected function randomPhase()
+ {
+ // random phase from 0 to pi
+ return mt_rand(0, 3141592) / 1000000;
+ }
+
+ /**
+ * Generate random character size
+ *
+ * @return int
+ */
+ protected function randomSize()
+ {
+ return mt_rand(300, 700) / 100;
+ }
+
+ /**
+ * Generate captcha
+ *
+ * @return string captcha ID
+ */
+ public function generate()
+ {
+ $id = parent::generate();
+ $tries = 5;
+
+ // If there's already such file, try creating a new ID
+ while ($tries-- && file_exists($this->getImgDir() . $id . $this->getSuffix())) {
+ $id = $this->generateRandomId();
+ $this->setId($id);
+ }
+ $this->generateImage($id, $this->getWord());
+
+ if (mt_rand(1, $this->getGcFreq()) == 1) {
+ $this->gc();
+ }
+
+ return $id;
+ }
+
+ /**
+ * Generate image captcha
+ *
+ * Override this function if you want different image generator
+ * Wave transform from http://www.captcha.ru/captchas/multiwave/
+ *
+ * @param string $id Captcha ID
+ * @param string $word Captcha word
+ * @throws Exception\NoFontProvidedException if no font was set
+ * @throws Exception\ImageNotLoadableException if start image cannot be loaded
+ */
+ protected function generateImage($id, $word)
+ {
+ $font = $this->getFont();
+
+ if (empty($font)) {
+ throw new Exception\NoFontProvidedException('Image CAPTCHA requires font');
+ }
+
+ $w = $this->getWidth();
+ $h = $this->getHeight();
+ $fsize = $this->getFontSize();
+
+ $imgFile = $this->getImgDir() . $id . $this->getSuffix();
+
+ if (empty($this->startImage)) {
+ $img = imagecreatetruecolor($w, $h);
+ } else {
+ // Potential error is change to exception
+ ErrorHandler::start();
+ $img = imagecreatefrompng($this->startImage);
+ $error = ErrorHandler::stop();
+ if (!$img || $error) {
+ throw new Exception\ImageNotLoadableException(
+ "Can not load start image '{$this->startImage}'",
+ 0,
+ $error
+ );
+ }
+ $w = imagesx($img);
+ $h = imagesy($img);
+ }
+
+ $textColor = imagecolorallocate($img, 0, 0, 0);
+ $bgColor = imagecolorallocate($img, 255, 255, 255);
+ imagefilledrectangle($img, 0, 0, $w-1, $h-1, $bgColor);
+ $textbox = imageftbbox($fsize, 0, $font, $word);
+ $x = ($w - ($textbox[2] - $textbox[0])) / 2;
+ $y = ($h - ($textbox[7] - $textbox[1])) / 2;
+ imagefttext($img, $fsize, 0, $x, $y, $textColor, $font, $word);
+
+ // generate noise
+ for ($i=0; $i < $this->dotNoiseLevel; $i++) {
+ imagefilledellipse($img, mt_rand(0, $w), mt_rand(0, $h), 2, 2, $textColor);
+ }
+ for ($i=0; $i < $this->lineNoiseLevel; $i++) {
+ imageline($img, mt_rand(0, $w), mt_rand(0, $h), mt_rand(0, $w), mt_rand(0, $h), $textColor);
+ }
+
+ // transformed image
+ $img2 = imagecreatetruecolor($w, $h);
+ $bgColor = imagecolorallocate($img2, 255, 255, 255);
+ imagefilledrectangle($img2, 0, 0, $w-1, $h-1, $bgColor);
+
+ // apply wave transforms
+ $freq1 = $this->randomFreq();
+ $freq2 = $this->randomFreq();
+ $freq3 = $this->randomFreq();
+ $freq4 = $this->randomFreq();
+
+ $ph1 = $this->randomPhase();
+ $ph2 = $this->randomPhase();
+ $ph3 = $this->randomPhase();
+ $ph4 = $this->randomPhase();
+
+ $szx = $this->randomSize();
+ $szy = $this->randomSize();
+
+ for ($x = 0; $x < $w; $x++) {
+ for ($y = 0; $y < $h; $y++) {
+ $sx = $x + (sin($x*$freq1 + $ph1) + sin($y*$freq3 + $ph3)) * $szx;
+ $sy = $y + (sin($x*$freq2 + $ph2) + sin($y*$freq4 + $ph4)) * $szy;
+
+ if ($sx < 0 || $sy < 0 || $sx >= $w - 1 || $sy >= $h - 1) {
+ continue;
+ } else {
+ $color = (imagecolorat($img, $sx, $sy) >> 16) & 0xFF;
+ $colorX = (imagecolorat($img, $sx + 1, $sy) >> 16) & 0xFF;
+ $colorY = (imagecolorat($img, $sx, $sy + 1) >> 16) & 0xFF;
+ $colorXY = (imagecolorat($img, $sx + 1, $sy + 1) >> 16) & 0xFF;
+ }
+
+ if ($color == 255 && $colorX == 255 && $colorY == 255 && $colorXY == 255) {
+ // ignore background
+ continue;
+ } elseif ($color == 0 && $colorX == 0 && $colorY == 0 && $colorXY == 0) {
+ // transfer inside of the image as-is
+ $newcolor = 0;
+ } else {
+ // do antialiasing for border items
+ $fracX = $sx - floor($sx);
+ $fracY = $sy - floor($sy);
+ $fracX1 = 1 - $fracX;
+ $fracY1 = 1 - $fracY;
+
+ $newcolor = $color * $fracX1 * $fracY1
+ + $colorX * $fracX * $fracY1
+ + $colorY * $fracX1 * $fracY
+ + $colorXY * $fracX * $fracY;
+ }
+
+ imagesetpixel($img2, $x, $y, imagecolorallocate($img2, $newcolor, $newcolor, $newcolor));
+ }
+ }
+
+ // generate noise
+ for ($i=0; $i<$this->dotNoiseLevel; $i++) {
+ imagefilledellipse($img2, mt_rand(0, $w), mt_rand(0, $h), 2, 2, $textColor);
+ }
+
+ for ($i=0; $i<$this->lineNoiseLevel; $i++) {
+ imageline($img2, mt_rand(0, $w), mt_rand(0, $h), mt_rand(0, $w), mt_rand(0, $h), $textColor);
+ }
+
+ imagepng($img2, $imgFile);
+ imagedestroy($img);
+ imagedestroy($img2);
+ }
+
+ /**
+ * Remove old files from image directory
+ *
+ */
+ protected function gc()
+ {
+ $expire = time() - $this->getExpiration();
+ $imgdir = $this->getImgDir();
+ if (!$imgdir || strlen($imgdir) < 2) {
+ // safety guard
+ return;
+ }
+
+ $suffixLength = strlen($this->suffix);
+ foreach (new DirectoryIterator($imgdir) as $file) {
+ if (!$file->isDot() && !$file->isDir()) {
+ if (file_exists($file->getPathname()) && $file->getMTime() < $expire) {
+ // only deletes files ending with $this->suffix
+ if (substr($file->getFilename(), -($suffixLength)) == $this->suffix) {
+ unlink($file->getPathname());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Get helper name used to render captcha
+ *
+ * @return string
+ */
+ public function getHelperName()
+ {
+ return 'captcha/image';
+ }
+}
diff --git a/library/Zend/Captcha/README.md b/library/Zend/Captcha/README.md
new file mode 100755
index 0000000000..1240158c59
--- /dev/null
+++ b/library/Zend/Captcha/README.md
@@ -0,0 +1,15 @@
+Captcha Component from ZF2
+==========================
+
+This is the Captcha component for ZF2.
+
+- File issues at https://github.com/zendframework/zf2/issues
+- Create pull requests against https://github.com/zendframework/zf2
+- Documentation is at http://framework.zend.com/docs
+
+LICENSE
+-------
+
+The files in this archive are released under the [Zend Framework
+license](http://framework.zend.com/license), which is a 3-clause BSD license.
+
diff --git a/library/Zend/Captcha/ReCaptcha.php b/library/Zend/Captcha/ReCaptcha.php
new file mode 100755
index 0000000000..7e5c019348
--- /dev/null
+++ b/library/Zend/Captcha/ReCaptcha.php
@@ -0,0 +1,247 @@
+ 'Missing captcha fields',
+ self::ERR_CAPTCHA => 'Failed to validate captcha',
+ self::BAD_CAPTCHA => 'Captcha value is wrong: %value%',
+ );
+
+ /**
+ * Retrieve ReCaptcha Private key
+ *
+ * @return string
+ */
+ public function getPrivkey()
+ {
+ return $this->getService()->getPrivateKey();
+ }
+
+ /**
+ * Retrieve ReCaptcha Public key
+ *
+ * @return string
+ */
+ public function getPubkey()
+ {
+ return $this->getService()->getPublicKey();
+ }
+
+ /**
+ * Set ReCaptcha Private key
+ *
+ * @param string $privkey
+ * @return ReCaptcha
+ */
+ public function setPrivkey($privkey)
+ {
+ $this->getService()->setPrivateKey($privkey);
+ return $this;
+ }
+
+ /**
+ * Set ReCaptcha public key
+ *
+ * @param string $pubkey
+ * @return ReCaptcha
+ */
+ public function setPubkey($pubkey)
+ {
+ $this->getService()->setPublicKey($pubkey);
+ return $this;
+ }
+
+ /**
+ * Constructor
+ *
+ * @param null|array|Traversable $options
+ */
+ public function __construct($options = null)
+ {
+ $this->setService(new ReCaptchaService());
+ $this->serviceParams = $this->getService()->getParams();
+ $this->serviceOptions = $this->getService()->getOptions();
+
+ parent::__construct($options);
+
+ if (!empty($options)) {
+ if (array_key_exists('private_key', $options)) {
+ $this->getService()->setPrivateKey($options['private_key']);
+ }
+ if (array_key_exists('public_key', $options)) {
+ $this->getService()->setPublicKey($options['public_key']);
+ }
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * Set service object
+ *
+ * @param ReCaptchaService $service
+ * @return ReCaptcha
+ */
+ public function setService(ReCaptchaService $service)
+ {
+ $this->service = $service;
+ return $this;
+ }
+
+ /**
+ * Retrieve ReCaptcha service object
+ *
+ * @return ReCaptchaService
+ */
+ public function getService()
+ {
+ return $this->service;
+ }
+
+ /**
+ * Set option
+ *
+ * If option is a service parameter, proxies to the service. The same
+ * goes for any service options (distinct from service params)
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return ReCaptcha
+ */
+ public function setOption($key, $value)
+ {
+ $service = $this->getService();
+ if (isset($this->serviceParams[$key])) {
+ $service->setParam($key, $value);
+ return $this;
+ }
+ if (isset($this->serviceOptions[$key])) {
+ $service->setOption($key, $value);
+ return $this;
+ }
+ return parent::setOption($key, $value);
+ }
+
+ /**
+ * Generate captcha
+ *
+ * @see AbstractAdapter::generate()
+ * @return string
+ */
+ public function generate()
+ {
+ return "";
+ }
+
+ /**
+ * Validate captcha
+ *
+ * @see \Zend\Validator\ValidatorInterface::isValid()
+ * @param mixed $value
+ * @param mixed $context
+ * @return bool
+ */
+ public function isValid($value, $context = null)
+ {
+ if (!is_array($value) && !is_array($context)) {
+ $this->error(self::MISSING_VALUE);
+ return false;
+ }
+
+ if (!is_array($value) && is_array($context)) {
+ $value = $context;
+ }
+
+ if (empty($value[$this->CHALLENGE]) || empty($value[$this->RESPONSE])) {
+ $this->error(self::MISSING_VALUE);
+ return false;
+ }
+
+ $service = $this->getService();
+
+ $res = $service->verify($value[$this->CHALLENGE], $value[$this->RESPONSE]);
+
+ if (!$res) {
+ $this->error(self::ERR_CAPTCHA);
+ return false;
+ }
+
+ if (!$res->isValid()) {
+ $this->error(self::BAD_CAPTCHA, $res->getErrorCode());
+ $service->setParam('error', $res->getErrorCode());
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get helper name used to render captcha
+ *
+ * @return string
+ */
+ public function getHelperName()
+ {
+ return "captcha/recaptcha";
+ }
+}
diff --git a/library/Zend/Captcha/composer.json b/library/Zend/Captcha/composer.json
new file mode 100755
index 0000000000..fec1684fe5
--- /dev/null
+++ b/library/Zend/Captcha/composer.json
@@ -0,0 +1,40 @@
+{
+ "name": "zendframework/zend-captcha",
+ "description": " ",
+ "license": "BSD-3-Clause",
+ "keywords": [
+ "zf2",
+ "captcha"
+ ],
+ "homepage": "https://github.com/zendframework/zf2",
+ "autoload": {
+ "psr-0": {
+ "Zend\\Captcha\\": ""
+ }
+ },
+ "target-dir": "Zend/Captcha",
+ "require": {
+ "php": ">=5.3.23",
+ "zendframework/zend-math": "self.version",
+ "zendframework/zend-stdlib": "self.version"
+ },
+ "require-dev": {
+ "zendframework/zend-session": "self.version",
+ "zendframework/zend-text": "self.version",
+ "zendframework/zend-validator": "self.version",
+ "zendframework/zendservice-recaptcha": "*"
+ },
+ "suggest": {
+ "zendframework/zend-resources": "Translations of captcha messages",
+ "zendframework/zend-session": "Zend\\Session component",
+ "zendframework/zend-text": "Zend\\Text component",
+ "zendframework/zend-validator": "Zend\\Validator component",
+ "zendframework/zendservice-recaptcha": "ZendService\\ReCaptcha component"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev",
+ "dev-develop": "2.4-dev"
+ }
+ }
+}
diff --git a/library/Zend/Code/Annotation/AnnotationCollection.php b/library/Zend/Code/Annotation/AnnotationCollection.php
new file mode 100755
index 0000000000..7b9aa51da6
--- /dev/null
+++ b/library/Zend/Code/Annotation/AnnotationCollection.php
@@ -0,0 +1,32 @@
+setIdentifiers(array(
+ __CLASS__,
+ get_class($this),
+ ));
+ $this->events = $events;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve event manager
+ *
+ * Lazy loads an instance if none registered.
+ *
+ * @return EventManagerInterface
+ */
+ public function getEventManager()
+ {
+ if (null === $this->events) {
+ $this->setEventManager(new EventManager());
+ }
+
+ return $this->events;
+ }
+
+ /**
+ * Attach a parser to listen to the createAnnotation event
+ *
+ * @param ParserInterface $parser
+ * @return AnnotationManager
+ */
+ public function attach(ParserInterface $parser)
+ {
+ $this->getEventManager()
+ ->attach(self::EVENT_CREATE_ANNOTATION, array($parser, 'onCreateAnnotation'));
+
+ return $this;
+ }
+
+ /**
+ * Create Annotation
+ *
+ * @param string[] $annotationData
+ * @return false|\stdClass
+ */
+ public function createAnnotation(array $annotationData)
+ {
+ $event = new Event();
+ $event->setName(self::EVENT_CREATE_ANNOTATION);
+ $event->setTarget($this);
+ $event->setParams(array(
+ 'class' => $annotationData[0],
+ 'content' => $annotationData[1],
+ 'raw' => $annotationData[2],
+ ));
+
+ $eventManager = $this->getEventManager();
+ $results = $eventManager->trigger($event, function ($r) {
+ return (is_object($r));
+ });
+
+ $annotation = $results->last();
+
+ return (is_object($annotation) ? $annotation : false);
+ }
+}
diff --git a/library/Zend/Code/Annotation/Parser/DoctrineAnnotationParser.php b/library/Zend/Code/Annotation/Parser/DoctrineAnnotationParser.php
new file mode 100755
index 0000000000..5168e3278c
--- /dev/null
+++ b/library/Zend/Code/Annotation/Parser/DoctrineAnnotationParser.php
@@ -0,0 +1,153 @@
+docParser = $docParser;
+ return $this;
+ }
+
+ /**
+ * Retrieve the DocParser instance
+ *
+ * If none is registered, lazy-loads a new instance.
+ *
+ * @return DocParser
+ */
+ public function getDocParser()
+ {
+ if (!$this->docParser instanceof DocParser) {
+ $this->setDocParser(new DocParser());
+ }
+
+ return $this->docParser;
+ }
+
+ /**
+ * Handle annotation creation
+ *
+ * @param EventInterface $e
+ * @return false|\stdClass
+ */
+ public function onCreateAnnotation(EventInterface $e)
+ {
+ $annotationClass = $e->getParam('class', false);
+ if (!$annotationClass) {
+ return false;
+ }
+
+ if (!isset($this->allowedAnnotations[$annotationClass])) {
+ return false;
+ }
+
+ $annotationString = $e->getParam('raw', false);
+ if (!$annotationString) {
+ return false;
+ }
+
+ // Annotation classes provided by the AnnotationScanner are already
+ // resolved to fully-qualified class names. Adding the global namespace
+ // prefix allows the Doctrine annotation parser to locate the annotation
+ // class correctly.
+ $annotationString = preg_replace('/^(@)/', '$1\\', $annotationString);
+
+ $parser = $this->getDocParser();
+ $annotations = $parser->parse($annotationString);
+ if (empty($annotations)) {
+ return false;
+ }
+
+ $annotation = array_shift($annotations);
+ if (!is_object($annotation)) {
+ return false;
+ }
+
+ return $annotation;
+ }
+
+ /**
+ * Specify an allowed annotation class
+ *
+ * @param string $annotation
+ * @return DoctrineAnnotationParser
+ */
+ public function registerAnnotation($annotation)
+ {
+ $this->allowedAnnotations[$annotation] = true;
+ return $this;
+ }
+
+ /**
+ * Set many allowed annotations at once
+ *
+ * @param array|Traversable $annotations Array or traversable object of
+ * annotation class names
+ * @throws Exception\InvalidArgumentException
+ * @return DoctrineAnnotationParser
+ */
+ public function registerAnnotations($annotations)
+ {
+ if (!is_array($annotations) && !$annotations instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s: expects an array or Traversable; received "%s"',
+ __METHOD__,
+ (is_object($annotations) ? get_class($annotations) : gettype($annotations))
+ ));
+ }
+
+ foreach ($annotations as $annotation) {
+ $this->allowedAnnotations[$annotation] = true;
+ }
+
+ return $this;
+ }
+}
diff --git a/library/Zend/Code/Annotation/Parser/GenericAnnotationParser.php b/library/Zend/Code/Annotation/Parser/GenericAnnotationParser.php
new file mode 100755
index 0000000000..b3f7566247
--- /dev/null
+++ b/library/Zend/Code/Annotation/Parser/GenericAnnotationParser.php
@@ -0,0 +1,224 @@
+getParam('class', false);
+ if (!$class || !$this->hasAnnotation($class)) {
+ return false;
+ }
+
+ $content = $e->getParam('content', '');
+ $content = trim($content, '()');
+
+ if ($this->hasAlias($class)) {
+ $class = $this->resolveAlias($class);
+ }
+
+ $index = array_search($class, $this->annotationNames);
+ $annotation = $this->annotations[$index];
+
+ $newAnnotation = clone $annotation;
+ if ($content) {
+ $newAnnotation->initialize($content);
+ }
+
+ return $newAnnotation;
+ }
+
+ /**
+ * Register annotations
+ *
+ * @param string|AnnotationInterface $annotation String class name of an
+ * AnnotationInterface implementation, or actual instance
+ * @return GenericAnnotationParser
+ * @throws Exception\InvalidArgumentException
+ */
+ public function registerAnnotation($annotation)
+ {
+ $class = false;
+ if (is_string($annotation) && class_exists($annotation)) {
+ $class = $annotation;
+ $annotation = new $annotation();
+ }
+
+ if (!$annotation instanceof AnnotationInterface) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s: expects an instance of %s\AnnotationInterface; received "%s"',
+ __METHOD__,
+ __NAMESPACE__,
+ (is_object($annotation) ? get_class($annotation) : gettype($annotation))
+ ));
+ }
+
+ $class = $class ?: get_class($annotation);
+
+ if (in_array($class, $this->annotationNames)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'An annotation for this class %s already exists',
+ $class
+ ));
+ }
+
+ $this->annotations[] = $annotation;
+ $this->annotationNames[] = $class;
+ }
+
+ /**
+ * Register many annotations at once
+ *
+ * @param array|Traversable $annotations
+ * @throws Exception\InvalidArgumentException
+ * @return GenericAnnotationParser
+ */
+ public function registerAnnotations($annotations)
+ {
+ if (!is_array($annotations) && !$annotations instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s: expects an array or Traversable; received "%s"',
+ __METHOD__,
+ (is_object($annotations) ? get_class($annotations) : gettype($annotations))
+ ));
+ }
+
+ foreach ($annotations as $annotation) {
+ $this->registerAnnotation($annotation);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Checks if the manager has annotations for a class
+ *
+ * @param string $class
+ * @return bool
+ */
+ public function hasAnnotation($class)
+ {
+ if (in_array($class, $this->annotationNames)) {
+ return true;
+ }
+
+ if ($this->hasAlias($class)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Alias an annotation name
+ *
+ * @param string $alias
+ * @param string $class May be either a registered annotation name or another alias
+ * @throws Exception\InvalidArgumentException
+ * @return GenericAnnotationParser
+ */
+ public function setAlias($alias, $class)
+ {
+ if (!in_array($class, $this->annotationNames) && !$this->hasAlias($class)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s: Cannot alias "%s" to "%s", as class "%s" is not currently a registered annotation or alias',
+ __METHOD__,
+ $alias,
+ $class,
+ $class
+ ));
+ }
+
+ $alias = $this->normalizeAlias($alias);
+ $this->aliases[$alias] = $class;
+
+ return $this;
+ }
+
+ /**
+ * Normalize an alias name
+ *
+ * @param string $alias
+ * @return string
+ */
+ protected function normalizeAlias($alias)
+ {
+ return strtolower(str_replace(array('-', '_', ' ', '\\', '/'), '', $alias));
+ }
+
+ /**
+ * Do we have an alias by the provided name?
+ *
+ * @param string $alias
+ * @return bool
+ */
+ protected function hasAlias($alias)
+ {
+ $alias = $this->normalizeAlias($alias);
+
+ return (isset($this->aliases[$alias]));
+ }
+
+ /**
+ * Resolve an alias to a class name
+ *
+ * @param string $alias
+ * @return string
+ */
+ protected function resolveAlias($alias)
+ {
+ do {
+ $normalized = $this->normalizeAlias($alias);
+ $class = $this->aliases[$normalized];
+ } while ($this->hasAlias($class));
+
+ return $class;
+ }
+}
diff --git a/library/Zend/Code/Annotation/Parser/ParserInterface.php b/library/Zend/Code/Annotation/Parser/ParserInterface.php
new file mode 100755
index 0000000000..bb6746e80c
--- /dev/null
+++ b/library/Zend/Code/Annotation/Parser/ParserInterface.php
@@ -0,0 +1,39 @@
+setOptions($options);
+ }
+ }
+
+ /**
+ * @param bool $isSourceDirty
+ * @return AbstractGenerator
+ */
+ public function setSourceDirty($isSourceDirty = true)
+ {
+ $this->isSourceDirty = (bool) $isSourceDirty;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isSourceDirty()
+ {
+ return $this->isSourceDirty;
+ }
+
+ /**
+ * @param string $indentation
+ * @return AbstractGenerator
+ */
+ public function setIndentation($indentation)
+ {
+ $this->indentation = (string) $indentation;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getIndentation()
+ {
+ return $this->indentation;
+ }
+
+ /**
+ * @param string $sourceContent
+ * @return AbstractGenerator
+ */
+ public function setSourceContent($sourceContent)
+ {
+ $this->sourceContent = (string) $sourceContent;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSourceContent()
+ {
+ return $this->sourceContent;
+ }
+
+ /**
+ * @param array|Traversable $options
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractGenerator
+ */
+ public function setOptions($options)
+ {
+ if (!is_array($options) && !$options instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects an array or Traversable object; received "%s"',
+ __METHOD__,
+ (is_object($options) ? get_class($options) : gettype($options))
+ ));
+ }
+
+ foreach ($options as $optionName => $optionValue) {
+ $methodName = 'set' . $optionName;
+ if (method_exists($this, $methodName)) {
+ $this->{$methodName}($optionValue);
+ }
+ }
+
+ return $this;
+ }
+}
diff --git a/library/Zend/Code/Generator/AbstractMemberGenerator.php b/library/Zend/Code/Generator/AbstractMemberGenerator.php
new file mode 100755
index 0000000000..dec0d8dc06
--- /dev/null
+++ b/library/Zend/Code/Generator/AbstractMemberGenerator.php
@@ -0,0 +1,224 @@
+flags = $flags;
+
+ return $this;
+ }
+
+ /**
+ * @param int $flag
+ * @return AbstractMemberGenerator
+ */
+ public function addFlag($flag)
+ {
+ $this->setFlags($this->flags | $flag);
+ return $this;
+ }
+
+ /**
+ * @param int $flag
+ * @return AbstractMemberGenerator
+ */
+ public function removeFlag($flag)
+ {
+ $this->setFlags($this->flags & ~$flag);
+ return $this;
+ }
+
+ /**
+ * @param bool $isAbstract
+ * @return AbstractMemberGenerator
+ */
+ public function setAbstract($isAbstract)
+ {
+ return (($isAbstract) ? $this->addFlag(self::FLAG_ABSTRACT) : $this->removeFlag(self::FLAG_ABSTRACT));
+ }
+
+ /**
+ * @return bool
+ */
+ public function isAbstract()
+ {
+ return (bool) ($this->flags & self::FLAG_ABSTRACT);
+ }
+
+ /**
+ * @param bool $isFinal
+ * @return AbstractMemberGenerator
+ */
+ public function setFinal($isFinal)
+ {
+ return (($isFinal) ? $this->addFlag(self::FLAG_FINAL) : $this->removeFlag(self::FLAG_FINAL));
+ }
+
+ /**
+ * @return bool
+ */
+ public function isFinal()
+ {
+ return (bool) ($this->flags & self::FLAG_FINAL);
+ }
+
+ /**
+ * @param bool $isStatic
+ * @return AbstractMemberGenerator
+ */
+ public function setStatic($isStatic)
+ {
+ return (($isStatic) ? $this->addFlag(self::FLAG_STATIC) : $this->removeFlag(self::FLAG_STATIC));
+ }
+
+ /**
+ * @return bool
+ */
+ public function isStatic()
+ {
+ return (bool) ($this->flags & self::FLAG_STATIC); // is FLAG_STATIC in flags
+ }
+
+ /**
+ * @param string $visibility
+ * @return AbstractMemberGenerator
+ */
+ public function setVisibility($visibility)
+ {
+ switch ($visibility) {
+ case self::VISIBILITY_PUBLIC:
+ $this->removeFlag(self::FLAG_PRIVATE | self::FLAG_PROTECTED); // remove both
+ $this->addFlag(self::FLAG_PUBLIC);
+ break;
+ case self::VISIBILITY_PROTECTED:
+ $this->removeFlag(self::FLAG_PUBLIC | self::FLAG_PRIVATE); // remove both
+ $this->addFlag(self::FLAG_PROTECTED);
+ break;
+ case self::VISIBILITY_PRIVATE:
+ $this->removeFlag(self::FLAG_PUBLIC | self::FLAG_PROTECTED); // remove both
+ $this->addFlag(self::FLAG_PRIVATE);
+ break;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getVisibility()
+ {
+ switch (true) {
+ case ($this->flags & self::FLAG_PROTECTED):
+ return self::VISIBILITY_PROTECTED;
+ case ($this->flags & self::FLAG_PRIVATE):
+ return self::VISIBILITY_PRIVATE;
+ default:
+ return self::VISIBILITY_PUBLIC;
+ }
+ }
+
+ /**
+ * @param string $name
+ * @return AbstractMemberGenerator
+ */
+ public function setName($name)
+ {
+ $this->name = (string) $name;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @param DocBlockGenerator|string $docBlock
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractMemberGenerator
+ */
+ public function setDocBlock($docBlock)
+ {
+ if (is_string($docBlock)) {
+ $docBlock = new DocBlockGenerator($docBlock);
+ } elseif (!$docBlock instanceof DocBlockGenerator) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s is expecting either a string, array or an instance of %s\DocBlockGenerator',
+ __METHOD__,
+ __NAMESPACE__
+ ));
+ }
+
+ $this->docBlock = $docBlock;
+
+ return $this;
+ }
+
+ /**
+ * @return DocBlockGenerator
+ */
+ public function getDocBlock()
+ {
+ return $this->docBlock;
+ }
+}
diff --git a/library/Zend/Code/Generator/BodyGenerator.php b/library/Zend/Code/Generator/BodyGenerator.php
new file mode 100755
index 0000000000..4851e5ca4d
--- /dev/null
+++ b/library/Zend/Code/Generator/BodyGenerator.php
@@ -0,0 +1,44 @@
+content = (string) $content;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * @return string
+ */
+ public function generate()
+ {
+ return $this->getContent();
+ }
+}
diff --git a/library/Zend/Code/Generator/ClassGenerator.php b/library/Zend/Code/Generator/ClassGenerator.php
new file mode 100755
index 0000000000..e845587466
--- /dev/null
+++ b/library/Zend/Code/Generator/ClassGenerator.php
@@ -0,0 +1,747 @@
+getName());
+
+ $cg->setSourceContent($cg->getSourceContent());
+ $cg->setSourceDirty(false);
+
+ if ($classReflection->getDocComment() != '') {
+ $cg->setDocBlock(DocBlockGenerator::fromReflection($classReflection->getDocBlock()));
+ }
+
+ $cg->setAbstract($classReflection->isAbstract());
+
+ // set the namespace
+ if ($classReflection->inNamespace()) {
+ $cg->setNamespaceName($classReflection->getNamespaceName());
+ }
+
+ /* @var \Zend\Code\Reflection\ClassReflection $parentClass */
+ $parentClass = $classReflection->getParentClass();
+ if ($parentClass) {
+ $cg->setExtendedClass($parentClass->getName());
+ $interfaces = array_diff($classReflection->getInterfaces(), $parentClass->getInterfaces());
+ } else {
+ $interfaces = $classReflection->getInterfaces();
+ }
+
+ $interfaceNames = array();
+ foreach ($interfaces as $interface) {
+ /* @var \Zend\Code\Reflection\ClassReflection $interface */
+ $interfaceNames[] = $interface->getName();
+ }
+
+ $cg->setImplementedInterfaces($interfaceNames);
+
+ $properties = array();
+ foreach ($classReflection->getProperties() as $reflectionProperty) {
+ if ($reflectionProperty->getDeclaringClass()->getName() == $classReflection->getName()) {
+ $properties[] = PropertyGenerator::fromReflection($reflectionProperty);
+ }
+ }
+ $cg->addProperties($properties);
+
+ $methods = array();
+ foreach ($classReflection->getMethods() as $reflectionMethod) {
+ $className = ($cg->getNamespaceName())? $cg->getNamespaceName() . "\\" . $cg->getName() : $cg->getName();
+ if ($reflectionMethod->getDeclaringClass()->getName() == $className) {
+ $methods[] = MethodGenerator::fromReflection($reflectionMethod);
+ }
+ }
+ $cg->addMethods($methods);
+
+ return $cg;
+ }
+
+ /**
+ * Generate from array
+ *
+ * @configkey name string [required] Class Name
+ * @configkey filegenerator FileGenerator File generator that holds this class
+ * @configkey namespacename string The namespace for this class
+ * @configkey docblock string The docblock information
+ * @configkey flags int Flags, one of ClassGenerator::FLAG_ABSTRACT ClassGenerator::FLAG_FINAL
+ * @configkey extendedclass string Class which this class is extending
+ * @configkey implementedinterfaces
+ * @configkey properties
+ * @configkey methods
+ *
+ * @throws Exception\InvalidArgumentException
+ * @param array $array
+ * @return ClassGenerator
+ */
+ public static function fromArray(array $array)
+ {
+ if (!isset($array['name'])) {
+ throw new Exception\InvalidArgumentException(
+ 'Class generator requires that a name is provided for this object'
+ );
+ }
+
+ $cg = new static($array['name']);
+ foreach ($array as $name => $value) {
+ // normalize key
+ switch (strtolower(str_replace(array('.', '-', '_'), '', $name))) {
+ case 'containingfile':
+ $cg->setContainingFileGenerator($value);
+ break;
+ case 'namespacename':
+ $cg->setNamespaceName($value);
+ break;
+ case 'docblock':
+ $docBlock = ($value instanceof DocBlockGenerator) ? $value : DocBlockGenerator::fromArray($value);
+ $cg->setDocBlock($docBlock);
+ break;
+ case 'flags':
+ $cg->setFlags($value);
+ break;
+ case 'extendedclass':
+ $cg->setExtendedClass($value);
+ break;
+ case 'implementedinterfaces':
+ $cg->setImplementedInterfaces($value);
+ break;
+ case 'properties':
+ $cg->addProperties($value);
+ break;
+ case 'methods':
+ $cg->addMethods($value);
+ break;
+ }
+ }
+
+ return $cg;
+ }
+
+ /**
+ * @param string $name
+ * @param string $namespaceName
+ * @param array|string $flags
+ * @param string $extends
+ * @param array $interfaces
+ * @param array $properties
+ * @param array $methods
+ * @param DocBlockGenerator $docBlock
+ */
+ public function __construct(
+ $name = null,
+ $namespaceName = null,
+ $flags = null,
+ $extends = null,
+ $interfaces = array(),
+ $properties = array(),
+ $methods = array(),
+ $docBlock = null
+ ) {
+ if ($name !== null) {
+ $this->setName($name);
+ }
+ if ($namespaceName !== null) {
+ $this->setNamespaceName($namespaceName);
+ }
+ if ($flags !== null) {
+ $this->setFlags($flags);
+ }
+ if ($properties !== array()) {
+ $this->addProperties($properties);
+ }
+ if ($extends !== null) {
+ $this->setExtendedClass($extends);
+ }
+ if (is_array($interfaces)) {
+ $this->setImplementedInterfaces($interfaces);
+ }
+ if ($methods !== array()) {
+ $this->addMethods($methods);
+ }
+ if ($docBlock !== null) {
+ $this->setDocBlock($docBlock);
+ }
+ }
+
+ /**
+ * @param string $name
+ * @return ClassGenerator
+ */
+ public function setName($name)
+ {
+ if (strstr($name, '\\')) {
+ $namespace = substr($name, 0, strrpos($name, '\\'));
+ $name = substr($name, strrpos($name, '\\') + 1);
+ $this->setNamespaceName($namespace);
+ }
+
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @param string $namespaceName
+ * @return ClassGenerator
+ */
+ public function setNamespaceName($namespaceName)
+ {
+ $this->namespaceName = $namespaceName;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getNamespaceName()
+ {
+ return $this->namespaceName;
+ }
+
+ /**
+ * @param FileGenerator $fileGenerator
+ * @return ClassGenerator
+ */
+ public function setContainingFileGenerator(FileGenerator $fileGenerator)
+ {
+ $this->containingFileGenerator = $fileGenerator;
+ return $this;
+ }
+
+ /**
+ * @return FileGenerator
+ */
+ public function getContainingFileGenerator()
+ {
+ return $this->containingFileGenerator;
+ }
+
+ /**
+ * @param DocBlockGenerator $docBlock
+ * @return ClassGenerator
+ */
+ public function setDocBlock(DocBlockGenerator $docBlock)
+ {
+ $this->docBlock = $docBlock;
+ return $this;
+ }
+
+ /**
+ * @return DocBlockGenerator
+ */
+ public function getDocBlock()
+ {
+ return $this->docBlock;
+ }
+
+ /**
+ * @param array|string $flags
+ * @return ClassGenerator
+ */
+ public function setFlags($flags)
+ {
+ if (is_array($flags)) {
+ $flagsArray = $flags;
+ $flags = 0x00;
+ foreach ($flagsArray as $flag) {
+ $flags |= $flag;
+ }
+ }
+ // check that visibility is one of three
+ $this->flags = $flags;
+
+ return $this;
+ }
+
+ /**
+ * @param string $flag
+ * @return ClassGenerator
+ */
+ public function addFlag($flag)
+ {
+ $this->setFlags($this->flags | $flag);
+ return $this;
+ }
+
+ /**
+ * @param string $flag
+ * @return ClassGenerator
+ */
+ public function removeFlag($flag)
+ {
+ $this->setFlags($this->flags & ~$flag);
+ return $this;
+ }
+
+ /**
+ * @param bool $isAbstract
+ * @return ClassGenerator
+ */
+ public function setAbstract($isAbstract)
+ {
+ return (($isAbstract) ? $this->addFlag(self::FLAG_ABSTRACT) : $this->removeFlag(self::FLAG_ABSTRACT));
+ }
+
+ /**
+ * @return bool
+ */
+ public function isAbstract()
+ {
+ return (bool) ($this->flags & self::FLAG_ABSTRACT);
+ }
+
+ /**
+ * @param bool $isFinal
+ * @return ClassGenerator
+ */
+ public function setFinal($isFinal)
+ {
+ return (($isFinal) ? $this->addFlag(self::FLAG_FINAL) : $this->removeFlag(self::FLAG_FINAL));
+ }
+
+ /**
+ * @return bool
+ */
+ public function isFinal()
+ {
+ return ($this->flags & self::FLAG_FINAL);
+ }
+
+ /**
+ * @param string $extendedClass
+ * @return ClassGenerator
+ */
+ public function setExtendedClass($extendedClass)
+ {
+ $this->extendedClass = $extendedClass;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getExtendedClass()
+ {
+ return $this->extendedClass;
+ }
+
+ /**
+ * @param array $implementedInterfaces
+ * @return ClassGenerator
+ */
+ public function setImplementedInterfaces(array $implementedInterfaces)
+ {
+ $this->implementedInterfaces = $implementedInterfaces;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getImplementedInterfaces()
+ {
+ return $this->implementedInterfaces;
+ }
+
+ /**
+ * @param array $properties
+ * @return ClassGenerator
+ */
+ public function addProperties(array $properties)
+ {
+ foreach ($properties as $property) {
+ if ($property instanceof PropertyGenerator) {
+ $this->addPropertyFromGenerator($property);
+ } else {
+ if (is_string($property)) {
+ $this->addProperty($property);
+ } elseif (is_array($property)) {
+ call_user_func_array(array($this, 'addProperty'), $property);
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add Property from scalars
+ *
+ * @param string $name
+ * @param string|array $defaultValue
+ * @param int $flags
+ * @throws Exception\InvalidArgumentException
+ * @return ClassGenerator
+ */
+ public function addProperty($name, $defaultValue = null, $flags = PropertyGenerator::FLAG_PUBLIC)
+ {
+ if (!is_string($name)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects string for name',
+ __METHOD__
+ ));
+ }
+
+ return $this->addPropertyFromGenerator(new PropertyGenerator($name, $defaultValue, $flags));
+ }
+
+ /**
+ * Add property from PropertyGenerator
+ *
+ * @param PropertyGenerator $property
+ * @throws Exception\InvalidArgumentException
+ * @return ClassGenerator
+ */
+ public function addPropertyFromGenerator(PropertyGenerator $property)
+ {
+ $propertyName = $property->getName();
+
+ if (isset($this->properties[$propertyName])) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'A property by name %s already exists in this class.',
+ $propertyName
+ ));
+ }
+
+ $this->properties[$propertyName] = $property;
+ return $this;
+ }
+
+ /**
+ * Add a class to "use" classes
+ *
+ * @param string $use
+ * @param string|null $useAlias
+ * @return ClassGenerator
+ */
+ public function addUse($use, $useAlias = null)
+ {
+ if (!empty($useAlias)) {
+ $use .= ' as ' . $useAlias;
+ }
+
+ $this->uses[$use] = $use;
+ return $this;
+ }
+
+ /**
+ * @return PropertyGenerator[]
+ */
+ public function getProperties()
+ {
+ return $this->properties;
+ }
+
+ /**
+ * @param string $propertyName
+ * @return PropertyGenerator|false
+ */
+ public function getProperty($propertyName)
+ {
+ foreach ($this->getProperties() as $property) {
+ if ($property->getName() == $propertyName) {
+ return $property;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the "use" classes
+ *
+ * @return array
+ */
+ public function getUses()
+ {
+ return array_values($this->uses);
+ }
+
+ /**
+ * @param string $propertyName
+ * @return bool
+ */
+ public function hasProperty($propertyName)
+ {
+ return isset($this->properties[$propertyName]);
+ }
+
+ /**
+ * @param array $methods
+ * @return ClassGenerator
+ */
+ public function addMethods(array $methods)
+ {
+ foreach ($methods as $method) {
+ if ($method instanceof MethodGenerator) {
+ $this->addMethodFromGenerator($method);
+ } else {
+ if (is_string($method)) {
+ $this->addMethod($method);
+ } elseif (is_array($method)) {
+ call_user_func_array(array($this, 'addMethod'), $method);
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add Method from scalars
+ *
+ * @param string $name
+ * @param array $parameters
+ * @param int $flags
+ * @param string $body
+ * @param string $docBlock
+ * @throws Exception\InvalidArgumentException
+ * @return ClassGenerator
+ */
+ public function addMethod(
+ $name = null,
+ array $parameters = array(),
+ $flags = MethodGenerator::FLAG_PUBLIC,
+ $body = null,
+ $docBlock = null
+ ) {
+ if (!is_string($name)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects string for name',
+ __METHOD__
+ ));
+ }
+
+ return $this->addMethodFromGenerator(new MethodGenerator($name, $parameters, $flags, $body, $docBlock));
+ }
+
+ /**
+ * Add Method from MethodGenerator
+ *
+ * @param MethodGenerator $method
+ * @throws Exception\InvalidArgumentException
+ * @return ClassGenerator
+ */
+ public function addMethodFromGenerator(MethodGenerator $method)
+ {
+ $methodName = $method->getName();
+
+ if ($this->hasMethod($methodName)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'A method by name %s already exists in this class.',
+ $methodName
+ ));
+ }
+
+ $this->methods[strtolower($methodName)] = $method;
+ return $this;
+ }
+
+ /**
+ * @return MethodGenerator[]
+ */
+ public function getMethods()
+ {
+ return $this->methods;
+ }
+
+ /**
+ * @param string $methodName
+ * @return MethodGenerator|false
+ */
+ public function getMethod($methodName)
+ {
+ return $this->hasMethod($methodName) ? $this->methods[strtolower($methodName)] : false;
+ }
+
+ /**
+ * @param string $methodName
+ * @return ClassGenerator
+ */
+ public function removeMethod($methodName)
+ {
+ if ($this->hasMethod($methodName)) {
+ unset($this->methods[strtolower($methodName)]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $methodName
+ * @return bool
+ */
+ public function hasMethod($methodName)
+ {
+ return isset($this->methods[strtolower($methodName)]);
+ }
+
+ /**
+ * @return bool
+ */
+ public function isSourceDirty()
+ {
+ if (($docBlock = $this->getDocBlock()) && $docBlock->isSourceDirty()) {
+ return true;
+ }
+
+ foreach ($this->getProperties() as $property) {
+ if ($property->isSourceDirty()) {
+ return true;
+ }
+ }
+
+ foreach ($this->getMethods() as $method) {
+ if ($method->isSourceDirty()) {
+ return true;
+ }
+ }
+
+ return parent::isSourceDirty();
+ }
+
+ /**
+ * @return string
+ */
+ public function generate()
+ {
+ if (!$this->isSourceDirty()) {
+ $output = $this->getSourceContent();
+ if (!empty($output)) {
+ return $output;
+ }
+ }
+
+ $output = '';
+
+ if (null !== ($namespace = $this->getNamespaceName())) {
+ $output .= 'namespace ' . $namespace . ';' . self::LINE_FEED . self::LINE_FEED;
+ }
+
+ $uses = $this->getUses();
+ if (!empty($uses)) {
+ foreach ($uses as $use) {
+ $output .= 'use ' . $use . ';' . self::LINE_FEED;
+ }
+ $output .= self::LINE_FEED;
+ }
+
+ if (null !== ($docBlock = $this->getDocBlock())) {
+ $docBlock->setIndentation('');
+ $output .= $docBlock->generate();
+ }
+
+ if ($this->isAbstract()) {
+ $output .= 'abstract ';
+ }
+
+ $output .= 'class ' . $this->getName();
+
+ if (!empty($this->extendedClass)) {
+ $output .= ' extends ' . $this->extendedClass;
+ }
+
+ $implemented = $this->getImplementedInterfaces();
+ if (!empty($implemented)) {
+ $output .= ' implements ' . implode(', ', $implemented);
+ }
+
+ $output .= self::LINE_FEED . '{' . self::LINE_FEED . self::LINE_FEED;
+
+ $properties = $this->getProperties();
+ if (!empty($properties)) {
+ foreach ($properties as $property) {
+ $output .= $property->generate() . self::LINE_FEED . self::LINE_FEED;
+ }
+ }
+
+ $methods = $this->getMethods();
+ if (!empty($methods)) {
+ foreach ($methods as $method) {
+ $output .= $method->generate() . self::LINE_FEED;
+ }
+ }
+
+ $output .= self::LINE_FEED . '}' . self::LINE_FEED;
+
+ return $output;
+ }
+}
diff --git a/library/Zend/Code/Generator/DocBlock/Tag.php b/library/Zend/Code/Generator/DocBlock/Tag.php
new file mode 100755
index 0000000000..58bd045e0d
--- /dev/null
+++ b/library/Zend/Code/Generator/DocBlock/Tag.php
@@ -0,0 +1,50 @@
+initializeDefaultTags();
+ return $tagManager->createTagFromReflection($reflectionTag);
+ }
+
+ /**
+ * @param string $description
+ * @return Tag
+ * @deprecated Deprecated in 2.3. Use GenericTag::setContent() instead
+ */
+ public function setDescription($description)
+ {
+ return $this->setContent($description);
+ }
+
+ /**
+ * @return string
+ * @deprecated Deprecated in 2.3. Use GenericTag::getContent() instead
+ */
+ public function getDescription()
+ {
+ return $this->getContent();
+ }
+}
diff --git a/library/Zend/Code/Generator/DocBlock/Tag/AbstractTypeableTag.php b/library/Zend/Code/Generator/DocBlock/Tag/AbstractTypeableTag.php
new file mode 100755
index 0000000000..1ecf5dc077
--- /dev/null
+++ b/library/Zend/Code/Generator/DocBlock/Tag/AbstractTypeableTag.php
@@ -0,0 +1,96 @@
+setTypes($types);
+ }
+
+ if (!empty($description)) {
+ $this->setDescription($description);
+ }
+ }
+
+ /**
+ * @param string $description
+ * @return ReturnTag
+ */
+ public function setDescription($description)
+ {
+ $this->description = $description;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ /**
+ * Array of types or string with types delimited by pipe (|)
+ * e.g. array('int', 'null') or "int|null"
+ *
+ * @param array|string $types
+ * @return ReturnTag
+ */
+ public function setTypes($types)
+ {
+ if (is_string($types)) {
+ $types = explode('|', $types);
+ }
+ $this->types = $types;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getTypes()
+ {
+ return $this->types;
+ }
+
+ /**
+ * @param string $delimiter
+ * @return string
+ */
+ public function getTypesAsString($delimiter = '|')
+ {
+ return implode($delimiter, $this->types);
+ }
+}
diff --git a/library/Zend/Code/Generator/DocBlock/Tag/AuthorTag.php b/library/Zend/Code/Generator/DocBlock/Tag/AuthorTag.php
new file mode 100755
index 0000000000..333912dcad
--- /dev/null
+++ b/library/Zend/Code/Generator/DocBlock/Tag/AuthorTag.php
@@ -0,0 +1,110 @@
+setAuthorName($authorName);
+ }
+
+ if (!empty($authorEmail)) {
+ $this->setAuthorEmail($authorEmail);
+ }
+ }
+
+ /**
+ * @param ReflectionTagInterface $reflectionTag
+ * @return ReturnTag
+ * @deprecated Deprecated in 2.3. Use TagManager::createTagFromReflection() instead
+ */
+ public static function fromReflection(ReflectionTagInterface $reflectionTag)
+ {
+ $tagManager = new TagManager();
+ $tagManager->initializeDefaultTags();
+ return $tagManager->createTagFromReflection($reflectionTag);
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return 'author';
+ }
+
+ /**
+ * @param string $authorEmail
+ * @return AuthorTag
+ */
+ public function setAuthorEmail($authorEmail)
+ {
+ $this->authorEmail = $authorEmail;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getAuthorEmail()
+ {
+ return $this->authorEmail;
+ }
+
+ /**
+ * @param string $authorName
+ * @return AuthorTag
+ */
+ public function setAuthorName($authorName)
+ {
+ $this->authorName = $authorName;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getAuthorName()
+ {
+ return $this->authorName;
+ }
+
+ /**
+ * @return string
+ */
+ public function generate()
+ {
+ $output = '@author'
+ . ((!empty($this->authorName)) ? ' ' . $this->authorName : '')
+ . ((!empty($this->authorEmail)) ? ' <' . $this->authorEmail . '>' : '');
+
+ return $output;
+ }
+}
diff --git a/library/Zend/Code/Generator/DocBlock/Tag/GenericTag.php b/library/Zend/Code/Generator/DocBlock/Tag/GenericTag.php
new file mode 100755
index 0000000000..4ff98d9f78
--- /dev/null
+++ b/library/Zend/Code/Generator/DocBlock/Tag/GenericTag.php
@@ -0,0 +1,88 @@
+setName($name);
+ }
+
+ if (!empty($content)) {
+ $this->setContent($content);
+ }
+ }
+
+ /**
+ * @param string $name
+ * @return GenericTag
+ */
+ public function setName($name)
+ {
+ $this->name = ltrim($name, '@');
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @param string $content
+ * @return GenericTag
+ */
+ public function setContent($content)
+ {
+ $this->content = $content;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * @return string
+ */
+ public function generate()
+ {
+ $output = '@' . $this->name
+ . ((!empty($this->content)) ? ' ' . $this->content : '');
+
+ return $output;
+ }
+}
diff --git a/library/Zend/Code/Generator/DocBlock/Tag/LicenseTag.php b/library/Zend/Code/Generator/DocBlock/Tag/LicenseTag.php
new file mode 100755
index 0000000000..91a974152f
--- /dev/null
+++ b/library/Zend/Code/Generator/DocBlock/Tag/LicenseTag.php
@@ -0,0 +1,110 @@
+setUrl($url);
+ }
+
+ if (!empty($licenseName)) {
+ $this->setLicenseName($licenseName);
+ }
+ }
+
+ /**
+ * @param ReflectionTagInterface $reflectionTag
+ * @return ReturnTag
+ * @deprecated Deprecated in 2.3. Use TagManager::createTagFromReflection() instead
+ */
+ public static function fromReflection(ReflectionTagInterface $reflectionTag)
+ {
+ $tagManager = new TagManager();
+ $tagManager->initializeDefaultTags();
+ return $tagManager->createTagFromReflection($reflectionTag);
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return 'license';
+ }
+
+ /**
+ * @param string $url
+ * @return LicenseTag
+ */
+ public function setUrl($url)
+ {
+ $this->url = $url;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUrl()
+ {
+ return $this->url;
+ }
+
+ /**
+ * @param string $name
+ * @return LicenseTag
+ */
+ public function setLicenseName($name)
+ {
+ $this->licenseName = $name;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getLicenseName()
+ {
+ return $this->licenseName;
+ }
+
+ /**
+ * @return string
+ */
+ public function generate()
+ {
+ $output = '@license'
+ . ((!empty($this->url)) ? ' ' . $this->url : '')
+ . ((!empty($this->licenseName)) ? ' ' . $this->licenseName : '');
+
+ return $output;
+ }
+}
diff --git a/library/Zend/Code/Generator/DocBlock/Tag/MethodTag.php b/library/Zend/Code/Generator/DocBlock/Tag/MethodTag.php
new file mode 100755
index 0000000000..e3c84c4ca6
--- /dev/null
+++ b/library/Zend/Code/Generator/DocBlock/Tag/MethodTag.php
@@ -0,0 +1,98 @@
+setMethodName($methodName);
+ }
+
+ $this->setIsStatic((bool) $isStatic);
+
+ parent::__construct($types, $description);
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return 'method';
+ }
+
+ /**
+ * @param boolean $isStatic
+ * @return MethodTag
+ */
+ public function setIsStatic($isStatic)
+ {
+ $this->isStatic = $isStatic;
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isStatic()
+ {
+ return $this->isStatic;
+ }
+
+ /**
+ * @param string $methodName
+ * @return MethodTag
+ */
+ public function setMethodName($methodName)
+ {
+ $this->methodName = rtrim($methodName, ')(');
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getMethodName()
+ {
+ return $this->methodName;
+ }
+
+ /**
+ * @return string
+ */
+ public function generate()
+ {
+ $output = '@method'
+ . (($this->isStatic) ? ' static' : '')
+ . ((!empty($this->types)) ? ' ' . $this->getTypesAsString() : '')
+ . ((!empty($this->methodName)) ? ' ' . $this->methodName . '()' : '')
+ . ((!empty($this->description)) ? ' ' . $this->description : '');
+
+ return $output;
+ }
+}
diff --git a/library/Zend/Code/Generator/DocBlock/Tag/ParamTag.php b/library/Zend/Code/Generator/DocBlock/Tag/ParamTag.php
new file mode 100755
index 0000000000..75d8f86cb4
--- /dev/null
+++ b/library/Zend/Code/Generator/DocBlock/Tag/ParamTag.php
@@ -0,0 +1,124 @@
+setVariableName($variableName);
+ }
+
+ parent::__construct($types, $description);
+ }
+
+ /**
+ * @param ReflectionTagInterface $reflectionTag
+ * @return ReturnTag
+ * @deprecated Deprecated in 2.3. Use TagManager::createTagFromReflection() instead
+ */
+ public static function fromReflection(ReflectionTagInterface $reflectionTag)
+ {
+ $tagManager = new TagManager();
+ $tagManager->initializeDefaultTags();
+ return $tagManager->createTagFromReflection($reflectionTag);
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return 'param';
+ }
+
+ /**
+ * @param string $variableName
+ * @return ParamTag
+ */
+ public function setVariableName($variableName)
+ {
+ $this->variableName = ltrim($variableName, '$');
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getVariableName()
+ {
+ return $this->variableName;
+ }
+
+ /**
+ * @param string $datatype
+ * @return ReturnTag
+ * @deprecated Deprecated in 2.3. Use setTypes() instead
+ */
+ public function setDatatype($datatype)
+ {
+ return $this->setTypes($datatype);
+ }
+
+ /**
+ * @return string
+ * @deprecated Deprecated in 2.3. Use getTypes() or getTypesAsString() instead
+ */
+ public function getDatatype()
+ {
+ return $this->getTypesAsString();
+ }
+
+ /**
+ * @param string $paramName
+ * @return ParamTag
+ * @deprecated Deprecated in 2.3. Use setVariableName() instead
+ */
+ public function setParamName($paramName)
+ {
+ return $this->setVariableName($paramName);
+ }
+
+ /**
+ * @return string
+ * @deprecated Deprecated in 2.3. Use getVariableName() instead
+ */
+ public function getParamName()
+ {
+ return $this->getVariableName();
+ }
+
+ /**
+ * @return string
+ */
+ public function generate()
+ {
+ $output = '@param'
+ . ((!empty($this->types)) ? ' ' . $this->getTypesAsString() : '')
+ . ((!empty($this->variableName)) ? ' $' . $this->variableName : '')
+ . ((!empty($this->description)) ? ' ' . $this->description : '');
+
+ return $output;
+ }
+}
diff --git a/library/Zend/Code/Generator/DocBlock/Tag/PropertyTag.php b/library/Zend/Code/Generator/DocBlock/Tag/PropertyTag.php
new file mode 100755
index 0000000000..26699a8970
--- /dev/null
+++ b/library/Zend/Code/Generator/DocBlock/Tag/PropertyTag.php
@@ -0,0 +1,71 @@
+setPropertyName($propertyName);
+ }
+
+ parent::__construct($types, $description);
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return 'property';
+ }
+
+ /**
+ * @param string $propertyName
+ * @return self
+ */
+ public function setPropertyName($propertyName)
+ {
+ $this->propertyName = ltrim($propertyName, '$');
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPropertyName()
+ {
+ return $this->propertyName;
+ }
+
+ /**
+ * @return string
+ */
+ public function generate()
+ {
+ $output = '@property'
+ . ((!empty($this->types)) ? ' ' . $this->getTypesAsString() : '')
+ . ((!empty($this->propertyName)) ? ' $' . $this->propertyName : '')
+ . ((!empty($this->description)) ? ' ' . $this->description : '');
+
+ return $output;
+ }
+}
diff --git a/library/Zend/Code/Generator/DocBlock/Tag/ReturnTag.php b/library/Zend/Code/Generator/DocBlock/Tag/ReturnTag.php
new file mode 100755
index 0000000000..f3b356ebb0
--- /dev/null
+++ b/library/Zend/Code/Generator/DocBlock/Tag/ReturnTag.php
@@ -0,0 +1,67 @@
+initializeDefaultTags();
+ return $tagManager->createTagFromReflection($reflectionTag);
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return 'return';
+ }
+
+ /**
+ * @param string $datatype
+ * @return ReturnTag
+ * @deprecated Deprecated in 2.3. Use setTypes() instead
+ */
+ public function setDatatype($datatype)
+ {
+ return $this->setTypes($datatype);
+ }
+
+ /**
+ * @return string
+ * @deprecated Deprecated in 2.3. Use getTypes() or getTypesAsString() instead
+ */
+ public function getDatatype()
+ {
+ return $this->getTypesAsString();
+ }
+
+ /**
+ * @return string
+ */
+ public function generate()
+ {
+ $output = '@return '
+ . $this->getTypesAsString()
+ . ((!empty($this->description)) ? ' ' . $this->description : '');
+
+ return $output;
+ }
+}
diff --git a/library/Zend/Code/Generator/DocBlock/Tag/TagInterface.php b/library/Zend/Code/Generator/DocBlock/Tag/TagInterface.php
new file mode 100755
index 0000000000..4d4ef3fcd3
--- /dev/null
+++ b/library/Zend/Code/Generator/DocBlock/Tag/TagInterface.php
@@ -0,0 +1,16 @@
+types)) ? ' ' . $this->getTypesAsString() : '')
+ . ((!empty($this->description)) ? ' ' . $this->description : '');
+
+ return $output;
+ }
+}
diff --git a/library/Zend/Code/Generator/DocBlock/TagManager.php b/library/Zend/Code/Generator/DocBlock/TagManager.php
new file mode 100755
index 0000000000..4ff3a2bc66
--- /dev/null
+++ b/library/Zend/Code/Generator/DocBlock/TagManager.php
@@ -0,0 +1,69 @@
+addPrototype(new Tag\ParamTag());
+ $this->addPrototype(new Tag\ReturnTag());
+ $this->addPrototype(new Tag\MethodTag());
+ $this->addPrototype(new Tag\PropertyTag());
+ $this->addPrototype(new Tag\AuthorTag());
+ $this->addPrototype(new Tag\LicenseTag());
+ $this->addPrototype(new Tag\ThrowsTag());
+ $this->setGenericPrototype(new Tag\GenericTag());
+ }
+
+ /**
+ * @param ReflectionTagInterface $reflectionTag
+ * @return TagInterface
+ */
+ public function createTagFromReflection(ReflectionTagInterface $reflectionTag)
+ {
+ $tagName = $reflectionTag->getName();
+
+ /* @var TagInterface $newTag */
+ $newTag = $this->getClonedPrototype($tagName);
+
+ // transport any properties via accessors and mutators from reflection to codegen object
+ $reflectionClass = new \ReflectionClass($reflectionTag);
+ foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
+ if (substr($method->getName(), 0, 3) == 'get') {
+ $propertyName = substr($method->getName(), 3);
+ if (method_exists($newTag, 'set' . $propertyName)) {
+ $newTag->{'set' . $propertyName}($reflectionTag->{'get' . $propertyName}());
+ }
+ } elseif (substr($method->getName(), 0, 2) == 'is') {
+ $propertyName = ucfirst($method->getName());
+ if (method_exists($newTag, 'set' . $propertyName)) {
+ $newTag->{'set' . $propertyName}($reflectionTag->{$method->getName()}());
+ }
+ }
+ }
+ return $newTag;
+ }
+}
diff --git a/library/Zend/Code/Generator/DocBlockGenerator.php b/library/Zend/Code/Generator/DocBlockGenerator.php
new file mode 100755
index 0000000000..e983fa6b1a
--- /dev/null
+++ b/library/Zend/Code/Generator/DocBlockGenerator.php
@@ -0,0 +1,274 @@
+setSourceContent($reflectionDocBlock->getContents());
+ $docBlock->setSourceDirty(false);
+
+ $docBlock->setShortDescription($reflectionDocBlock->getShortDescription());
+ $docBlock->setLongDescription($reflectionDocBlock->getLongDescription());
+
+ foreach ($reflectionDocBlock->getTags() as $tag) {
+ $docBlock->setTag(self::getTagManager()->createTagFromReflection($tag));
+ }
+
+ return $docBlock;
+ }
+
+ /**
+ * Generate from array
+ *
+ * @configkey shortdescription string The short description for this doc block
+ * @configkey longdescription string The long description for this doc block
+ * @configkey tags array
+ *
+ * @throws Exception\InvalidArgumentException
+ * @param array $array
+ * @return DocBlockGenerator
+ */
+ public static function fromArray(array $array)
+ {
+ $docBlock = new static();
+
+ foreach ($array as $name => $value) {
+ // normalize key
+ switch (strtolower(str_replace(array('.', '-', '_'), '', $name))) {
+ case 'shortdescription':
+ $docBlock->setShortDescription($value);
+ break;
+ case 'longdescription':
+ $docBlock->setLongDescription($value);
+ break;
+ case 'tags':
+ $docBlock->setTags($value);
+ break;
+ }
+ }
+
+ return $docBlock;
+ }
+
+ protected static function getTagManager()
+ {
+ if (!isset(static::$tagManager)) {
+ static::$tagManager = new TagManager();
+ static::$tagManager->initializeDefaultTags();
+ }
+ return static::$tagManager;
+ }
+
+ /**
+ * @param string $shortDescription
+ * @param string $longDescription
+ * @param array $tags
+ */
+ public function __construct($shortDescription = null, $longDescription = null, array $tags = array())
+ {
+ if ($shortDescription) {
+ $this->setShortDescription($shortDescription);
+ }
+ if ($longDescription) {
+ $this->setLongDescription($longDescription);
+ }
+ if (is_array($tags) && $tags) {
+ $this->setTags($tags);
+ }
+ }
+
+ /**
+ * @param string $shortDescription
+ * @return DocBlockGenerator
+ */
+ public function setShortDescription($shortDescription)
+ {
+ $this->shortDescription = $shortDescription;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getShortDescription()
+ {
+ return $this->shortDescription;
+ }
+
+ /**
+ * @param string $longDescription
+ * @return DocBlockGenerator
+ */
+ public function setLongDescription($longDescription)
+ {
+ $this->longDescription = $longDescription;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getLongDescription()
+ {
+ return $this->longDescription;
+ }
+
+ /**
+ * @param array $tags
+ * @return DocBlockGenerator
+ */
+ public function setTags(array $tags)
+ {
+ foreach ($tags as $tag) {
+ $this->setTag($tag);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param array|TagInterface $tag
+ * @throws Exception\InvalidArgumentException
+ * @return DocBlockGenerator
+ */
+ public function setTag($tag)
+ {
+ if (is_array($tag)) {
+ // use deprecated Tag class for backward compatiblity to old array-keys
+ $genericTag = new Tag();
+ $genericTag->setOptions($tag);
+ $tag = $genericTag;
+ } elseif (!$tag instanceof TagInterface) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects either an array of method options or an instance of %s\DocBlock\Tag\TagInterface',
+ __METHOD__,
+ __NAMESPACE__
+ ));
+ }
+
+ $this->tags[] = $tag;
+ return $this;
+ }
+
+ /**
+ * @return TagInterface[]
+ */
+ public function getTags()
+ {
+ return $this->tags;
+ }
+
+ /**
+ * @param bool $value
+ * @return DocBlockGenerator
+ */
+ public function setWordWrap($value)
+ {
+ $this->wordwrap = (bool) $value;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getWordWrap()
+ {
+ return $this->wordwrap;
+ }
+
+ /**
+ * @return string
+ */
+ public function generate()
+ {
+ if (!$this->isSourceDirty()) {
+ return $this->docCommentize(trim($this->getSourceContent()));
+ }
+
+ $output = '';
+ if (null !== ($sd = $this->getShortDescription())) {
+ $output .= $sd . self::LINE_FEED . self::LINE_FEED;
+ }
+ if (null !== ($ld = $this->getLongDescription())) {
+ $output .= $ld . self::LINE_FEED . self::LINE_FEED;
+ }
+
+ /* @var $tag GeneratorInterface */
+ foreach ($this->getTags() as $tag) {
+ $output .= $tag->generate() . self::LINE_FEED;
+ }
+
+ return $this->docCommentize(trim($output));
+ }
+
+ /**
+ * @param string $content
+ * @return string
+ */
+ protected function docCommentize($content)
+ {
+ $indent = $this->getIndentation();
+ $output = $indent . '/**' . self::LINE_FEED;
+ $content = $this->getWordWrap() == true ? wordwrap($content, 80, self::LINE_FEED) : $content;
+ $lines = explode(self::LINE_FEED, $content);
+ foreach ($lines as $line) {
+ $output .= $indent . ' *';
+ if ($line) {
+ $output .= " $line";
+ }
+ $output .= self::LINE_FEED;
+ }
+ $output .= $indent . ' */' . self::LINE_FEED;
+
+ return $output;
+ }
+}
diff --git a/library/Zend/Code/Generator/Exception/ExceptionInterface.php b/library/Zend/Code/Generator/Exception/ExceptionInterface.php
new file mode 100755
index 0000000000..d6abfd426e
--- /dev/null
+++ b/library/Zend/Code/Generator/Exception/ExceptionInterface.php
@@ -0,0 +1,16 @@
+setOptions($options);
+ }
+ }
+
+ /**
+ * Use this if you intend on generating code generation objects based on the same file.
+ * This will keep previous changes to the file in tact during the same PHP process
+ *
+ * @param string $filePath
+ * @param bool $includeIfNotAlreadyIncluded
+ * @throws ReflectionException\InvalidArgumentException If file does not exists
+ * @throws ReflectionException\RuntimeException If file exists but is not included or required
+ * @return FileGenerator
+ */
+ public static function fromReflectedFileName($filePath, $includeIfNotAlreadyIncluded = true)
+ {
+ $fileReflector = new FileReflection($filePath, $includeIfNotAlreadyIncluded);
+ $codeGenerator = static::fromReflection($fileReflector);
+
+ return $codeGenerator;
+ }
+
+ /**
+ * @param FileReflection $fileReflection
+ * @return FileGenerator
+ */
+ public static function fromReflection(FileReflection $fileReflection)
+ {
+ $file = new static();
+
+ $file->setSourceContent($fileReflection->getContents());
+ $file->setSourceDirty(false);
+
+ $body = $fileReflection->getContents();
+
+ $uses = $fileReflection->getUses();
+
+ foreach ($fileReflection->getClasses() as $class) {
+ $phpClass = ClassGenerator::fromReflection($class);
+ $phpClass->setContainingFileGenerator($file);
+
+ foreach ($uses as $fileUse) {
+ $phpClass->addUse($fileUse['use'], $fileUse['as']);
+ }
+
+ $file->setClass($phpClass);
+ }
+
+ $namespace = $fileReflection->getNamespace();
+
+ if ($namespace != '') {
+ $file->setNamespace($namespace);
+ }
+
+ if ($uses) {
+ $file->setUses($uses);
+ }
+
+ if (($fileReflection->getDocComment() != '')) {
+ $docBlock = $fileReflection->getDocBlock();
+ $file->setDocBlock(DocBlockGenerator::fromReflection($docBlock));
+ }
+
+ return $file;
+ }
+
+ /**
+ * @param array $values
+ * @return FileGenerator
+ */
+ public static function fromArray(array $values)
+ {
+ $fileGenerator = new static;
+ foreach ($values as $name => $value) {
+ switch (strtolower(str_replace(array('.', '-', '_'), '', $name))) {
+ case 'filename':
+ $fileGenerator->setFilename($value);
+ continue;
+ case 'class':
+ $fileGenerator->setClass(($value instanceof ClassGenerator) ? $value : ClassGenerator::fromArray($value));
+ continue;
+ case 'requiredfiles':
+ $fileGenerator->setRequiredFiles($value);
+ continue;
+ default:
+ if (property_exists($fileGenerator, $name)) {
+ $fileGenerator->{$name} = $value;
+ } elseif (method_exists($fileGenerator, 'set' . $name)) {
+ $fileGenerator->{'set' . $name}($value);
+ }
+ }
+ }
+
+ return $fileGenerator;
+ }
+
+ /**
+ * @param DocBlockGenerator|string $docBlock
+ * @throws Exception\InvalidArgumentException
+ * @return FileGenerator
+ */
+ public function setDocBlock($docBlock)
+ {
+ if (is_string($docBlock)) {
+ $docBlock = array('shortDescription' => $docBlock);
+ }
+
+ if (is_array($docBlock)) {
+ $docBlock = new DocBlockGenerator($docBlock);
+ } elseif (!$docBlock instanceof DocBlockGenerator) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s is expecting either a string, array or an instance of %s\DocBlockGenerator',
+ __METHOD__,
+ __NAMESPACE__
+ ));
+ }
+
+ $this->docBlock = $docBlock;
+ return $this;
+ }
+
+ /**
+ * @return DocBlockGenerator
+ */
+ public function getDocBlock()
+ {
+ return $this->docBlock;
+ }
+
+ /**
+ * @param array $requiredFiles
+ * @return FileGenerator
+ */
+ public function setRequiredFiles(array $requiredFiles)
+ {
+ $this->requiredFiles = $requiredFiles;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getRequiredFiles()
+ {
+ return $this->requiredFiles;
+ }
+
+ /**
+ * @return string
+ */
+ public function getNamespace()
+ {
+ return $this->namespace;
+ }
+
+ /**
+ * @param string $namespace
+ * @return FileGenerator
+ */
+ public function setNamespace($namespace)
+ {
+ $this->namespace = (string) $namespace;
+ return $this;
+ }
+
+ /**
+ * Returns an array with the first element the use statement, second is the as part.
+ * If $withResolvedAs is set to true, there will be a third element that is the
+ * "resolved" as statement, as the second part is not required in use statements
+ *
+ * @param bool $withResolvedAs
+ * @return array
+ */
+ public function getUses($withResolvedAs = false)
+ {
+ $uses = $this->uses;
+ if ($withResolvedAs) {
+ for ($useIndex = 0, $count = count($uses); $useIndex < $count; $useIndex++) {
+ if ($uses[$useIndex][1] == '') {
+ if (($lastSeparator = strrpos($uses[$useIndex][0], '\\')) !== false) {
+ $uses[$useIndex][2] = substr($uses[$useIndex][0], $lastSeparator + 1);
+ } else {
+ $uses[$useIndex][2] = $uses[$useIndex][0];
+ }
+ } else {
+ $uses[$useIndex][2] = $uses[$useIndex][1];
+ }
+ }
+ }
+
+ return $uses;
+ }
+
+ /**
+ * @param array $uses
+ * @return FileGenerator
+ */
+ public function setUses(array $uses)
+ {
+ foreach ($uses as $use) {
+ $use = (array) $use;
+ if (array_key_exists('use', $use) && array_key_exists('as', $use)) {
+ $import = $use['use'];
+ $alias = $use['as'];
+ } elseif (count($use) == 2) {
+ list($import, $alias) = $use;
+ } else {
+ $import = current($use);
+ $alias = null;
+ }
+ $this->setUse($import, $alias);
+ }
+ return $this;
+ }
+
+ /**
+ * @param string $use
+ * @param null|string $as
+ * @return FileGenerator
+ */
+ public function setUse($use, $as = null)
+ {
+ if (!in_array(array($use, $as), $this->uses)) {
+ $this->uses[] = array($use, $as);
+ }
+ return $this;
+ }
+
+ /**
+ * @param array $classes
+ * @return FileGenerator
+ */
+ public function setClasses(array $classes)
+ {
+ foreach ($classes as $class) {
+ $this->setClass($class);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $name
+ * @return ClassGenerator
+ */
+ public function getClass($name = null)
+ {
+ if ($name == null) {
+ reset($this->classes);
+
+ return current($this->classes);
+ }
+
+ return $this->classes[(string) $name];
+ }
+
+ /**
+ * @param array|string|ClassGenerator $class
+ * @throws Exception\InvalidArgumentException
+ * @return FileGenerator
+ */
+ public function setClass($class)
+ {
+ if (is_array($class)) {
+ $class = ClassGenerator::fromArray($class);
+ } elseif (is_string($class)) {
+ $class = new ClassGenerator($class);
+ } elseif (!$class instanceof ClassGenerator) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s is expecting either a string, array or an instance of %s\ClassGenerator',
+ __METHOD__,
+ __NAMESPACE__
+ ));
+ }
+
+ // @todo check for dup here
+ $className = $class->getName();
+ $this->classes[$className] = $class;
+
+ return $this;
+ }
+
+ /**
+ * @param string $filename
+ * @return FileGenerator
+ */
+ public function setFilename($filename)
+ {
+ $this->filename = (string) $filename;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getFilename()
+ {
+ return $this->filename;
+ }
+
+ /**
+ * @return ClassGenerator[]
+ */
+ public function getClasses()
+ {
+ return $this->classes;
+ }
+
+ /**
+ * @param string $body
+ * @return FileGenerator
+ */
+ public function setBody($body)
+ {
+ $this->body = (string) $body;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getBody()
+ {
+ return $this->body;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isSourceDirty()
+ {
+ $docBlock = $this->getDocBlock();
+ if ($docBlock && $docBlock->isSourceDirty()) {
+ return true;
+ }
+
+ foreach ($this->classes as $class) {
+ if ($class->isSourceDirty()) {
+ return true;
+ }
+ }
+
+ return parent::isSourceDirty();
+ }
+
+ /**
+ * @return string
+ */
+ public function generate()
+ {
+ if ($this->isSourceDirty() === false) {
+ return $this->sourceContent;
+ }
+
+ $output = '';
+
+ // @note body gets populated when FileGenerator created
+ // from a file. @see fromReflection and may also be set
+ // via FileGenerator::setBody
+ $body = $this->getBody();
+
+ // start with the body (if there), or open tag
+ if (preg_match('#(?:\s*)<\?php#', $body) == false) {
+ $output = 'getDocBlock())) {
+ $docBlock->setIndentation('');
+
+ if (preg_match('#/\* Zend_Code_Generator_FileGenerator-DocBlockMarker \*/#m', $output)) {
+ $output = preg_replace('#/\* Zend_Code_Generator_FileGenerator-DocBlockMarker \*/#m', $docBlock->generate(), $output, 1);
+ } else {
+ $output .= $docBlock->generate() . self::LINE_FEED;
+ }
+ }
+
+ // newline
+ $output .= self::LINE_FEED;
+
+ // namespace, if any
+ $namespace = $this->getNamespace();
+ if ($namespace) {
+ $namespace = sprintf('namespace %s;%s', $namespace, str_repeat(self::LINE_FEED, 2));
+ if (preg_match('#/\* Zend_Code_Generator_FileGenerator-NamespaceMarker \*/#m', $output)) {
+ $output = preg_replace('#/\* Zend_Code_Generator_FileGenerator-NamespaceMarker \*/#m', $namespace,
+ $output, 1);
+ } else {
+ $output .= $namespace;
+ }
+ }
+
+ // process required files
+ // @todo marker replacement for required files
+ $requiredFiles = $this->getRequiredFiles();
+ if (!empty($requiredFiles)) {
+ foreach ($requiredFiles as $requiredFile) {
+ $output .= 'require_once \'' . $requiredFile . '\';' . self::LINE_FEED;
+ }
+
+ $output .= self::LINE_FEED;
+ }
+
+ $classes = $this->getClasses();
+ $classUses = array();
+ //build uses array
+ foreach ($classes as $class) {
+ //check for duplicate use statements
+ $uses = $class->getUses();
+ if (!empty($uses) && is_array($uses)) {
+ $classUses = array_merge($classUses, $uses);
+ }
+ }
+
+ // process import statements
+ $uses = $this->getUses();
+ if (!empty($uses)) {
+ $useOutput = '';
+
+ foreach ($uses as $use) {
+ list($import, $alias) = $use;
+ if (null === $alias) {
+ $tempOutput = sprintf('%s', $import);
+ } else {
+ $tempOutput = sprintf('%s as %s', $import, $alias);
+ }
+
+ //don't duplicate use statements
+ if (!in_array($tempOutput, $classUses)) {
+ $useOutput .= "use ". $tempOutput .";";
+ $useOutput .= self::LINE_FEED;
+ }
+ }
+ $useOutput .= self::LINE_FEED;
+
+ if (preg_match('#/\* Zend_Code_Generator_FileGenerator-UseMarker \*/#m', $output)) {
+ $output = preg_replace('#/\* Zend_Code_Generator_FileGenerator-UseMarker \*/#m', $useOutput,
+ $output, 1);
+ } else {
+ $output .= $useOutput;
+ }
+ }
+
+ // process classes
+ if (!empty($classes)) {
+ foreach ($classes as $class) {
+ $regex = str_replace('&', $class->getName(), '/\* Zend_Code_Generator_Php_File-ClassMarker: \{[A-Za-z0-9\\\]+?&\} \*/');
+ if (preg_match('#' . $regex . '#m', $output)) {
+ $output = preg_replace('#' . $regex . '#', $class->generate(), $output, 1);
+ } else {
+ if ($namespace) {
+ $class->setNamespaceName(null);
+ }
+ $output .= $class->generate() . self::LINE_FEED;
+ }
+ }
+ }
+
+ if (!empty($body)) {
+ // add an extra space between classes and
+ if (!empty($classes)) {
+ $output .= self::LINE_FEED;
+ }
+
+ $output .= $body;
+ }
+
+ return $output;
+ }
+
+ /**
+ * @return FileGenerator
+ * @throws Exception\RuntimeException
+ */
+ public function write()
+ {
+ if ($this->filename == '' || !is_writable(dirname($this->filename))) {
+ throw new Exception\RuntimeException('This code generator object is not writable.');
+ }
+ file_put_contents($this->filename, $this->generate());
+
+ return $this;
+ }
+}
diff --git a/library/Zend/Code/Generator/FileGeneratorRegistry.php b/library/Zend/Code/Generator/FileGeneratorRegistry.php
new file mode 100755
index 0000000000..164cc52543
--- /dev/null
+++ b/library/Zend/Code/Generator/FileGeneratorRegistry.php
@@ -0,0 +1,44 @@
+getFilename();
+ }
+
+ if ($fileName == '') {
+ throw new RuntimeException('FileName does not exist.');
+ }
+
+ // cannot use realpath since the file might not exist, but we do need to have the index
+ // in the same DIRECTORY_SEPARATOR that realpath would use:
+ $fileName = str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $fileName);
+
+ static::$fileCodeGenerators[$fileName] = $fileCodeGenerator;
+ }
+}
diff --git a/library/Zend/Code/Generator/GeneratorInterface.php b/library/Zend/Code/Generator/GeneratorInterface.php
new file mode 100755
index 0000000000..77e1eb8c80
--- /dev/null
+++ b/library/Zend/Code/Generator/GeneratorInterface.php
@@ -0,0 +1,15 @@
+setSourceContent($reflectionMethod->getContents(false));
+ $method->setSourceDirty(false);
+
+ if ($reflectionMethod->getDocComment() != '') {
+ $method->setDocBlock(DocBlockGenerator::fromReflection($reflectionMethod->getDocBlock()));
+ }
+
+ $method->setFinal($reflectionMethod->isFinal());
+
+ if ($reflectionMethod->isPrivate()) {
+ $method->setVisibility(self::VISIBILITY_PRIVATE);
+ } elseif ($reflectionMethod->isProtected()) {
+ $method->setVisibility(self::VISIBILITY_PROTECTED);
+ } else {
+ $method->setVisibility(self::VISIBILITY_PUBLIC);
+ }
+
+ $method->setStatic($reflectionMethod->isStatic());
+
+ $method->setName($reflectionMethod->getName());
+
+ foreach ($reflectionMethod->getParameters() as $reflectionParameter) {
+ $method->setParameter(ParameterGenerator::fromReflection($reflectionParameter));
+ }
+
+ $method->setBody($reflectionMethod->getBody());
+
+ return $method;
+ }
+
+ /**
+ * Generate from array
+ *
+ * @configkey name string [required] Class Name
+ * @configkey docblock string The docblock information
+ * @configkey flags int Flags, one of MethodGenerator::FLAG_ABSTRACT MethodGenerator::FLAG_FINAL
+ * @configkey parameters string Class which this class is extending
+ * @configkey body string
+ * @configkey abstract bool
+ * @configkey final bool
+ * @configkey static bool
+ * @configkey visibility string
+ *
+ * @throws Exception\InvalidArgumentException
+ * @param array $array
+ * @return MethodGenerator
+ */
+ public static function fromArray(array $array)
+ {
+ if (!isset($array['name'])) {
+ throw new Exception\InvalidArgumentException(
+ 'Method generator requires that a name is provided for this object'
+ );
+ }
+
+ $method = new static($array['name']);
+ foreach ($array as $name => $value) {
+ // normalize key
+ switch (strtolower(str_replace(array('.', '-', '_'), '', $name))) {
+ case 'docblock':
+ $docBlock = ($value instanceof DocBlockGenerator) ? $value : DocBlockGenerator::fromArray($value);
+ $method->setDocBlock($docBlock);
+ break;
+ case 'flags':
+ $method->setFlags($value);
+ break;
+ case 'parameters':
+ $method->setParameters($value);
+ break;
+ case 'body':
+ $method->setBody($value);
+ break;
+ case 'abstract':
+ $method->setAbstract($value);
+ break;
+ case 'final':
+ $method->setFinal($value);
+ break;
+ case 'static':
+ $method->setStatic($value);
+ break;
+ case 'visibility':
+ $method->setVisibility($value);
+ break;
+ }
+ }
+
+ return $method;
+ }
+
+ /**
+ * @param string $name
+ * @param array $parameters
+ * @param int $flags
+ * @param string $body
+ * @param DocBlockGenerator|string $docBlock
+ */
+ public function __construct(
+ $name = null,
+ array $parameters = array(),
+ $flags = self::FLAG_PUBLIC,
+ $body = null,
+ $docBlock = null
+ ) {
+ if ($name) {
+ $this->setName($name);
+ }
+ if ($parameters) {
+ $this->setParameters($parameters);
+ }
+ if ($flags !== self::FLAG_PUBLIC) {
+ $this->setFlags($flags);
+ }
+ if ($body) {
+ $this->setBody($body);
+ }
+ if ($docBlock) {
+ $this->setDocBlock($docBlock);
+ }
+ }
+
+ /**
+ * @param array $parameters
+ * @return MethodGenerator
+ */
+ public function setParameters(array $parameters)
+ {
+ foreach ($parameters as $parameter) {
+ $this->setParameter($parameter);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param ParameterGenerator|string $parameter
+ * @throws Exception\InvalidArgumentException
+ * @return MethodGenerator
+ */
+ public function setParameter($parameter)
+ {
+ if (is_string($parameter)) {
+ $parameter = new ParameterGenerator($parameter);
+ } elseif (!$parameter instanceof ParameterGenerator) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s is expecting either a string, array or an instance of %s\ParameterGenerator',
+ __METHOD__,
+ __NAMESPACE__
+ ));
+ }
+
+ $parameterName = $parameter->getName();
+
+ $this->parameters[$parameterName] = $parameter;
+
+ return $this;
+ }
+
+ /**
+ * @return ParameterGenerator[]
+ */
+ public function getParameters()
+ {
+ return $this->parameters;
+ }
+
+ /**
+ * @param string $body
+ * @return MethodGenerator
+ */
+ public function setBody($body)
+ {
+ $this->body = $body;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getBody()
+ {
+ return $this->body;
+ }
+
+ /**
+ * @return string
+ */
+ public function generate()
+ {
+ $output = '';
+
+ $indent = $this->getIndentation();
+
+ if (($docBlock = $this->getDocBlock()) !== null) {
+ $docBlock->setIndentation($indent);
+ $output .= $docBlock->generate();
+ }
+
+ $output .= $indent;
+
+ if ($this->isAbstract()) {
+ $output .= 'abstract ';
+ } else {
+ $output .= (($this->isFinal()) ? 'final ' : '');
+ }
+
+ $output .= $this->getVisibility()
+ . (($this->isStatic()) ? ' static' : '')
+ . ' function ' . $this->getName() . '(';
+
+ $parameters = $this->getParameters();
+ if (!empty($parameters)) {
+ foreach ($parameters as $parameter) {
+ $parameterOutput[] = $parameter->generate();
+ }
+
+ $output .= implode(', ', $parameterOutput);
+ }
+
+ $output .= ')';
+
+ if ($this->isAbstract()) {
+ return $output . ';';
+ }
+
+ $output .= self::LINE_FEED . $indent . '{' . self::LINE_FEED;
+
+ if ($this->body) {
+ $output .= preg_replace('#^((?![a-zA-Z0-9_-]+;).+?)$#m', $indent . $indent . '$1', trim($this->body))
+ . self::LINE_FEED;
+ }
+
+ $output .= $indent . '}' . self::LINE_FEED;
+
+ return $output;
+ }
+
+ public function __toString()
+ {
+ return $this->generate();
+ }
+}
diff --git a/library/Zend/Code/Generator/ParameterGenerator.php b/library/Zend/Code/Generator/ParameterGenerator.php
new file mode 100755
index 0000000000..30fad22c09
--- /dev/null
+++ b/library/Zend/Code/Generator/ParameterGenerator.php
@@ -0,0 +1,300 @@
+setName($reflectionParameter->getName());
+
+ if ($reflectionParameter->isArray()) {
+ $param->setType('array');
+ } elseif (method_exists($reflectionParameter, 'isCallable') && $reflectionParameter->isCallable()) {
+ $param->setType('callable');
+ } else {
+ $typeClass = $reflectionParameter->getClass();
+ if ($typeClass) {
+ $parameterType = $typeClass->getName();
+ $currentNamespace = $reflectionParameter->getDeclaringClass()->getNamespaceName();
+
+ if (!empty($currentNamespace) && substr($parameterType, 0, strlen($currentNamespace)) == $currentNamespace) {
+ $parameterType = substr($parameterType, strlen($currentNamespace) + 1);
+ } else {
+ $parameterType = '\\' . trim($parameterType, '\\');
+ }
+
+ $param->setType($parameterType);
+ }
+ }
+
+ $param->setPosition($reflectionParameter->getPosition());
+
+ if ($reflectionParameter->isOptional()) {
+ $param->setDefaultValue($reflectionParameter->getDefaultValue());
+ }
+ $param->setPassedByReference($reflectionParameter->isPassedByReference());
+
+ return $param;
+ }
+
+ /**
+ * Generate from array
+ *
+ * @configkey name string [required] Class Name
+ * @configkey type string
+ * @configkey defaultvalue null|bool|string|int|float|array|ValueGenerator
+ * @configkey passedbyreference bool
+ * @configkey position int
+ * @configkey sourcedirty bool
+ * @configkey indentation string
+ * @configkey sourcecontent string
+ *
+ * @throws Exception\InvalidArgumentException
+ * @param array $array
+ * @return ParameterGenerator
+ */
+ public static function fromArray(array $array)
+ {
+ if (!isset($array['name'])) {
+ throw new Exception\InvalidArgumentException(
+ 'Paramerer generator requires that a name is provided for this object'
+ );
+ }
+
+ $param = new static($array['name']);
+ foreach ($array as $name => $value) {
+ // normalize key
+ switch (strtolower(str_replace(array('.', '-', '_'), '', $name))) {
+ case 'type':
+ $param->setType($value);
+ break;
+ case 'defaultvalue':
+ $param->setDefaultValue($value);
+ break;
+ case 'passedbyreference':
+ $param->setPassedByReference($value);
+ break;
+ case 'position':
+ $param->setPosition($value);
+ break;
+ case 'sourcedirty':
+ $param->setSourceDirty($value);
+ break;
+ case 'indentation':
+ $param->setIndentation($value);
+ break;
+ case 'sourcecontent':
+ $param->setSourceContent($value);
+ break;
+ }
+ }
+
+ return $param;
+ }
+
+ /**
+ * @param string $name
+ * @param string $type
+ * @param mixed $defaultValue
+ * @param int $position
+ * @param bool $passByReference
+ */
+ public function __construct(
+ $name = null,
+ $type = null,
+ $defaultValue = null,
+ $position = null,
+ $passByReference = false
+ ) {
+ if (null !== $name) {
+ $this->setName($name);
+ }
+ if (null !== $type) {
+ $this->setType($type);
+ }
+ if (null !== $defaultValue) {
+ $this->setDefaultValue($defaultValue);
+ }
+ if (null !== $position) {
+ $this->setPosition($position);
+ }
+ if (false !== $passByReference) {
+ $this->setPassedByReference(true);
+ }
+ }
+
+ /**
+ * @param string $type
+ * @return ParameterGenerator
+ */
+ public function setType($type)
+ {
+ $this->type = (string) $type;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * @param string $name
+ * @return ParameterGenerator
+ */
+ public function setName($name)
+ {
+ $this->name = (string) $name;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Set the default value of the parameter.
+ *
+ * Certain variables are difficult to express
+ *
+ * @param null|bool|string|int|float|array|ValueGenerator $defaultValue
+ * @return ParameterGenerator
+ */
+ public function setDefaultValue($defaultValue)
+ {
+ if (!($defaultValue instanceof ValueGenerator)) {
+ $defaultValue = new ValueGenerator($defaultValue);
+ }
+ $this->defaultValue = $defaultValue;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDefaultValue()
+ {
+ return $this->defaultValue;
+ }
+
+ /**
+ * @param int $position
+ * @return ParameterGenerator
+ */
+ public function setPosition($position)
+ {
+ $this->position = (int) $position;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getPosition()
+ {
+ return $this->position;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getPassedByReference()
+ {
+ return $this->passedByReference;
+ }
+
+ /**
+ * @param bool $passedByReference
+ * @return ParameterGenerator
+ */
+ public function setPassedByReference($passedByReference)
+ {
+ $this->passedByReference = (bool) $passedByReference;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function generate()
+ {
+ $output = '';
+
+ if ($this->type && !in_array($this->type, static::$simple)) {
+ $output .= $this->type . ' ';
+ }
+
+ if (true === $this->passedByReference) {
+ $output .= '&';
+ }
+
+ $output .= '$' . $this->name;
+
+ if ($this->defaultValue !== null) {
+ $output .= ' = ';
+ if (is_string($this->defaultValue)) {
+ $output .= ValueGenerator::escape($this->defaultValue);
+ } elseif ($this->defaultValue instanceof ValueGenerator) {
+ $this->defaultValue->setOutputMode(ValueGenerator::OUTPUT_SINGLE_LINE);
+ $output .= $this->defaultValue;
+ } else {
+ $output .= $this->defaultValue;
+ }
+ }
+
+ return $output;
+ }
+}
diff --git a/library/Zend/Code/Generator/PropertyGenerator.php b/library/Zend/Code/Generator/PropertyGenerator.php
new file mode 100755
index 0000000000..36735ad37f
--- /dev/null
+++ b/library/Zend/Code/Generator/PropertyGenerator.php
@@ -0,0 +1,226 @@
+setName($reflectionProperty->getName());
+
+ $allDefaultProperties = $reflectionProperty->getDeclaringClass()->getDefaultProperties();
+
+ $property->setDefaultValue($allDefaultProperties[$reflectionProperty->getName()]);
+
+ if ($reflectionProperty->getDocComment() != '') {
+ $property->setDocBlock(DocBlockGenerator::fromReflection($reflectionProperty->getDocBlock()));
+ }
+
+ if ($reflectionProperty->isStatic()) {
+ $property->setStatic(true);
+ }
+
+ if ($reflectionProperty->isPrivate()) {
+ $property->setVisibility(self::VISIBILITY_PRIVATE);
+ } elseif ($reflectionProperty->isProtected()) {
+ $property->setVisibility(self::VISIBILITY_PROTECTED);
+ } else {
+ $property->setVisibility(self::VISIBILITY_PUBLIC);
+ }
+
+ $property->setSourceDirty(false);
+
+ return $property;
+ }
+
+ /**
+ * Generate from array
+ *
+ * @configkey name string [required] Class Name
+ * @configkey const bool
+ * @configkey defaultvalue null|bool|string|int|float|array|ValueGenerator
+ * @configkey flags int
+ * @configkey abstract bool
+ * @configkey final bool
+ * @configkey static bool
+ * @configkey visibility string
+ *
+ * @throws Exception\InvalidArgumentException
+ * @param array $array
+ * @return PropertyGenerator
+ */
+ public static function fromArray(array $array)
+ {
+ if (!isset($array['name'])) {
+ throw new Exception\InvalidArgumentException(
+ 'Property generator requires that a name is provided for this object'
+ );
+ }
+
+ $property = new static($array['name']);
+ foreach ($array as $name => $value) {
+ // normalize key
+ switch (strtolower(str_replace(array('.', '-', '_'), '', $name))) {
+ case 'const':
+ $property->setConst($value);
+ break;
+ case 'defaultvalue':
+ $property->setDefaultValue($value);
+ break;
+ case 'docblock':
+ $docBlock = ($value instanceof DocBlockGenerator) ? $value : DocBlockGenerator::fromArray($value);
+ $property->setDocBlock($docBlock);
+ break;
+ case 'flags':
+ $property->setFlags($value);
+ break;
+ case 'abstract':
+ $property->setAbstract($value);
+ break;
+ case 'final':
+ $property->setFinal($value);
+ break;
+ case 'static':
+ $property->setStatic($value);
+ break;
+ case 'visibility':
+ $property->setVisibility($value);
+ break;
+ }
+ }
+
+ return $property;
+ }
+
+ /**
+ * @param string $name
+ * @param PropertyValueGenerator|string|array $defaultValue
+ * @param int $flags
+ */
+ public function __construct($name = null, $defaultValue = null, $flags = self::FLAG_PUBLIC)
+ {
+ if (null !== $name) {
+ $this->setName($name);
+ }
+ if (null !== $defaultValue) {
+ $this->setDefaultValue($defaultValue);
+ }
+ if ($flags !== self::FLAG_PUBLIC) {
+ $this->setFlags($flags);
+ }
+ }
+
+ /**
+ * @param bool $const
+ * @return PropertyGenerator
+ */
+ public function setConst($const)
+ {
+ if ($const) {
+ $this->removeFlag(self::FLAG_PUBLIC | self::FLAG_PRIVATE | self::FLAG_PROTECTED);
+ $this->setFlags(self::FLAG_CONSTANT);
+ } else {
+ $this->removeFlag(self::FLAG_CONSTANT);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isConst()
+ {
+ return (bool) ($this->flags & self::FLAG_CONSTANT);
+ }
+
+ /**
+ * @param PropertyValueGenerator|mixed $defaultValue
+ * @param string $defaultValueType
+ * @param string $defaultValueOutputMode
+ *
+ * @return PropertyGenerator
+ */
+ public function setDefaultValue($defaultValue, $defaultValueType = PropertyValueGenerator::TYPE_AUTO, $defaultValueOutputMode = PropertyValueGenerator::OUTPUT_MULTIPLE_LINE)
+ {
+ if (!($defaultValue instanceof PropertyValueGenerator)) {
+ $defaultValue = new PropertyValueGenerator($defaultValue, $defaultValueType, $defaultValueOutputMode);
+ }
+
+ $this->defaultValue = $defaultValue;
+
+ return $this;
+ }
+
+ /**
+ * @return PropertyValueGenerator
+ */
+ public function getDefaultValue()
+ {
+ return $this->defaultValue;
+ }
+
+ /**
+ * @throws Exception\RuntimeException
+ * @return string
+ */
+ public function generate()
+ {
+ $name = $this->getName();
+ $defaultValue = $this->getDefaultValue();
+
+ $output = '';
+
+ if (($docBlock = $this->getDocBlock()) !== null) {
+ $docBlock->setIndentation(' ');
+ $output .= $docBlock->generate();
+ }
+
+ if ($this->isConst()) {
+ if ($defaultValue != null && !$defaultValue->isValidConstantType()) {
+ throw new Exception\RuntimeException(sprintf(
+ 'The property %s is said to be '
+ . 'constant but does not have a valid constant value.',
+ $this->name
+ ));
+ }
+ $output .= $this->indentation . 'const ' . $name . ' = '
+ . (($defaultValue !== null) ? $defaultValue->generate() : 'null;');
+ } else {
+ $output .= $this->indentation
+ . $this->getVisibility()
+ . (($this->isStatic()) ? ' static' : '')
+ . ' $' . $name . ' = '
+ . (($defaultValue !== null) ? $defaultValue->generate() : 'null;');
+ }
+
+ return $output;
+ }
+}
diff --git a/library/Zend/Code/Generator/PropertyValueGenerator.php b/library/Zend/Code/Generator/PropertyValueGenerator.php
new file mode 100755
index 0000000000..f36fc8c1fc
--- /dev/null
+++ b/library/Zend/Code/Generator/PropertyValueGenerator.php
@@ -0,0 +1,23 @@
+setValue($value);
+ }
+ if ($type !== self::TYPE_AUTO) {
+ $this->setType($type);
+ }
+ if ($outputMode !== self::OUTPUT_MULTIPLE_LINE) {
+ $this->setOutputMode($outputMode);
+ }
+ if ($constants !== null) {
+ $this->constants = $constants;
+ } else {
+ $this->constants = new ArrayObject();
+ }
+ }
+
+ /**
+ * Init constant list by defined and magic constants
+ */
+ public function initEnvironmentConstants()
+ {
+ $constants = array(
+ '__DIR__',
+ '__FILE__',
+ '__LINE__',
+ '__CLASS__',
+ '__TRAIT__',
+ '__METHOD__',
+ '__FUNCTION__',
+ '__NAMESPACE__',
+ '::'
+ );
+ $constants = array_merge($constants, array_keys(get_defined_constants()), $this->constants->getArrayCopy());
+ $this->constants->exchangeArray($constants);
+ }
+
+ /**
+ * Add constant to list
+ *
+ * @param string $constant
+ *
+ * @return $this
+ */
+ public function addConstant($constant)
+ {
+ $this->constants->append($constant);
+
+ return $this;
+ }
+
+ /**
+ * Delete constant from constant list
+ *
+ * @param string $constant
+ *
+ * @return bool
+ */
+ public function deleteConstant($constant)
+ {
+ if (($index = array_search($constant, $this->constants->getArrayCopy())) !== false) {
+ $this->constants->offsetUnset($index);
+ }
+
+ return $index !== false;
+ }
+
+ /**
+ * Return constant list
+ *
+ * @return ArrayObject
+ */
+ public function getConstants()
+ {
+ return $this->constants;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isValidConstantType()
+ {
+ if ($this->type == self::TYPE_AUTO) {
+ $type = $this->getAutoDeterminedType($this->value);
+ } else {
+ $type = $this->type;
+ }
+
+ // valid types for constants
+ $scalarTypes = array(
+ self::TYPE_BOOLEAN,
+ self::TYPE_BOOL,
+ self::TYPE_NUMBER,
+ self::TYPE_INTEGER,
+ self::TYPE_INT,
+ self::TYPE_FLOAT,
+ self::TYPE_DOUBLE,
+ self::TYPE_STRING,
+ self::TYPE_CONSTANT,
+ self::TYPE_NULL
+ );
+
+ return in_array($type, $scalarTypes);
+ }
+
+ /**
+ * @param mixed $value
+ * @return ValueGenerator
+ */
+ public function setValue($value)
+ {
+ $this->value = $value;
+ return $this;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * @param string $type
+ * @return ValueGenerator
+ */
+ public function setType($type)
+ {
+ $this->type = (string) $type;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * @param int $arrayDepth
+ * @return ValueGenerator
+ */
+ public function setArrayDepth($arrayDepth)
+ {
+ $this->arrayDepth = (int) $arrayDepth;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getArrayDepth()
+ {
+ return $this->arrayDepth;
+ }
+
+ /**
+ * @param string $type
+ * @return string
+ */
+ protected function getValidatedType($type)
+ {
+ $types = array(
+ self::TYPE_AUTO,
+ self::TYPE_BOOLEAN,
+ self::TYPE_BOOL,
+ self::TYPE_NUMBER,
+ self::TYPE_INTEGER,
+ self::TYPE_INT,
+ self::TYPE_FLOAT,
+ self::TYPE_DOUBLE,
+ self::TYPE_STRING,
+ self::TYPE_ARRAY,
+ self::TYPE_CONSTANT,
+ self::TYPE_NULL,
+ self::TYPE_OBJECT,
+ self::TYPE_OTHER
+ );
+
+ if (in_array($type, $types)) {
+ return $type;
+ }
+
+ return self::TYPE_AUTO;
+ }
+
+ /**
+ * @param mixed $value
+ * @return string
+ */
+ public function getAutoDeterminedType($value)
+ {
+ switch (gettype($value)) {
+ case 'boolean':
+ return self::TYPE_BOOLEAN;
+ case 'string':
+ foreach ($this->constants as $constant) {
+ if (strpos($value, $constant) !== false) {
+ return self::TYPE_CONSTANT;
+ }
+ }
+ return self::TYPE_STRING;
+ case 'double':
+ case 'float':
+ case 'integer':
+ return self::TYPE_NUMBER;
+ case 'array':
+ return self::TYPE_ARRAY;
+ case 'NULL':
+ return self::TYPE_NULL;
+ case 'object':
+ case 'resource':
+ case 'unknown type':
+ default:
+ return self::TYPE_OTHER;
+ }
+ }
+
+ /**
+ * @throws Exception\RuntimeException
+ * @return string
+ */
+ public function generate()
+ {
+ $type = $this->type;
+
+ if ($type != self::TYPE_AUTO) {
+ $type = $this->getValidatedType($type);
+ }
+
+ $value = $this->value;
+
+ if ($type == self::TYPE_AUTO) {
+ $type = $this->getAutoDeterminedType($value);
+ }
+
+ if ($type == self::TYPE_ARRAY) {
+ foreach ($value as &$curValue) {
+ if ($curValue instanceof self) {
+ continue;
+ }
+ $curValue = new self($curValue, self::TYPE_AUTO, self::OUTPUT_MULTIPLE_LINE, $this->getConstants());
+ }
+ }
+
+ $output = '';
+
+ switch ($type) {
+ case self::TYPE_BOOLEAN:
+ case self::TYPE_BOOL:
+ $output .= ($value ? 'true' : 'false');
+ break;
+ case self::TYPE_STRING:
+ $output .= self::escape($value);
+ break;
+ case self::TYPE_NULL:
+ $output .= 'null';
+ break;
+ case self::TYPE_NUMBER:
+ case self::TYPE_INTEGER:
+ case self::TYPE_INT:
+ case self::TYPE_FLOAT:
+ case self::TYPE_DOUBLE:
+ case self::TYPE_CONSTANT:
+ $output .= $value;
+ break;
+ case self::TYPE_ARRAY:
+ $output .= 'array(';
+ if ($this->outputMode == self::OUTPUT_MULTIPLE_LINE) {
+ $output .= self::LINE_FEED . str_repeat($this->indentation, $this->arrayDepth + 1);
+ }
+ $outputParts = array();
+ $noKeyIndex = 0;
+ foreach ($value as $n => $v) {
+ /* @var $v ValueGenerator */
+ $v->setArrayDepth($this->arrayDepth + 1);
+ $partV = $v->generate();
+ $short = false;
+ if (is_int($n)) {
+ if ($n === $noKeyIndex) {
+ $short = true;
+ $noKeyIndex++;
+ } else {
+ $noKeyIndex = max($n + 1, $noKeyIndex);
+ }
+ }
+
+ if ($short) {
+ $outputParts[] = $partV;
+ } else {
+ $outputParts[] = (is_int($n) ? $n : self::escape($n)) . ' => ' . $partV;
+ }
+ }
+ $padding = ($this->outputMode == self::OUTPUT_MULTIPLE_LINE)
+ ? self::LINE_FEED . str_repeat($this->indentation, $this->arrayDepth + 1)
+ : ' ';
+ $output .= implode(',' . $padding, $outputParts);
+ if ($this->outputMode == self::OUTPUT_MULTIPLE_LINE) {
+ $output .= self::LINE_FEED . str_repeat($this->indentation, $this->arrayDepth);
+ }
+ $output .= ')';
+ break;
+ case self::TYPE_OTHER:
+ default:
+ throw new Exception\RuntimeException(
+ sprintf('Type "%s" is unknown or cannot be used as property default value.', get_class($value))
+ );
+ }
+
+ return $output;
+ }
+
+ /**
+ * Quotes value for PHP code.
+ *
+ * @param string $input Raw string.
+ * @param bool $quote Whether add surrounding quotes or not.
+ * @return string PHP-ready code.
+ */
+ public static function escape($input, $quote = true)
+ {
+ $output = addcslashes($input, "\\'");
+
+ // adds quoting strings
+ if ($quote) {
+ $output = "'" . $output . "'";
+ }
+
+ return $output;
+ }
+
+ /**
+ * @param string $outputMode
+ * @return ValueGenerator
+ */
+ public function setOutputMode($outputMode)
+ {
+ $this->outputMode = (string) $outputMode;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getOutputMode()
+ {
+ return $this->outputMode;
+ }
+
+ public function __toString()
+ {
+ return $this->generate();
+ }
+}
diff --git a/library/Zend/Code/Generic/Prototype/PrototypeClassFactory.php b/library/Zend/Code/Generic/Prototype/PrototypeClassFactory.php
new file mode 100755
index 0000000000..7c3a9bf1ba
--- /dev/null
+++ b/library/Zend/Code/Generic/Prototype/PrototypeClassFactory.php
@@ -0,0 +1,121 @@
+addPrototype($prototype);
+ }
+
+ if ($genericPrototype) {
+ $this->setGenericPrototype($genericPrototype);
+ }
+ }
+
+ /**
+ * @param PrototypeInterface $prototype
+ * @throws Exception\InvalidArgumentException
+ */
+ public function addPrototype(PrototypeInterface $prototype)
+ {
+ $prototypeName = $this->normalizeName($prototype->getName());
+
+ if (isset($this->prototypes[$prototypeName])) {
+ throw new Exception\InvalidArgumentException('A prototype with this name already exists in this manager');
+ }
+
+ $this->prototypes[$prototypeName] = $prototype;
+ }
+
+ /**
+ * @param PrototypeGenericInterface $prototype
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setGenericPrototype(PrototypeGenericInterface $prototype)
+ {
+ if (isset($this->genericPrototype)) {
+ throw new Exception\InvalidArgumentException('A default prototype is already set');
+ }
+
+ $this->genericPrototype = $prototype;
+ }
+
+ /**
+ * @param string $name
+ * @return string
+ */
+ protected function normalizeName($name)
+ {
+ return str_replace(array('-', '_'), '', $name);
+ }
+
+ /**
+ * @param string $name
+ * @return bool
+ */
+ public function hasPrototype($name)
+ {
+ $name = $this->normalizeName($name);
+ return isset($this->prototypes[$name]);
+ }
+
+ /**
+ * @param string $prototypeName
+ * @return PrototypeInterface
+ * @throws Exception\RuntimeException
+ */
+ public function getClonedPrototype($prototypeName)
+ {
+ $prototypeName = $this->normalizeName($prototypeName);
+
+ if (!$this->hasPrototype($prototypeName) && !isset($this->genericPrototype)) {
+ throw new Exception\RuntimeException('This tag name is not supported by this tag manager');
+ }
+
+ if (!$this->hasPrototype($prototypeName)) {
+ $newPrototype = clone $this->genericPrototype;
+ $newPrototype->setName($prototypeName);
+ } else {
+ $newPrototype = clone $this->prototypes[$prototypeName];
+ }
+
+ return $newPrototype;
+ }
+}
diff --git a/library/Zend/Code/Generic/Prototype/PrototypeGenericInterface.php b/library/Zend/Code/Generic/Prototype/PrototypeGenericInterface.php
new file mode 100755
index 0000000000..3a5e44a883
--- /dev/null
+++ b/library/Zend/Code/Generic/Prototype/PrototypeGenericInterface.php
@@ -0,0 +1,18 @@
+setNamespace($namespace);
+ }
+ if ($uses) {
+ $this->setUses($uses);
+ }
+ }
+
+ /**
+ * @param string $namespace
+ * @return NameInformation
+ */
+ public function setNamespace($namespace)
+ {
+ $this->namespace = (string) $namespace;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getNamespace()
+ {
+ return $this->namespace;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasNamespace()
+ {
+ return ($this->namespace != null);
+ }
+
+ /**
+ * @param array $uses
+ * @return NameInformation
+ */
+ public function setUses(array $uses)
+ {
+ $this->uses = array();
+ $this->addUses($uses);
+
+ return $this;
+ }
+
+ /**
+ * @param array $uses
+ * @return NameInformation
+ */
+ public function addUses(array $uses)
+ {
+ foreach ($uses as $use => $as) {
+ if (is_int($use)) {
+ $this->addUse($as);
+ } elseif (is_string($use)) {
+ $this->addUse($use, $as);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param array|string $use
+ * @param string $as
+ */
+ public function addUse($use, $as = null)
+ {
+ if (is_array($use) && array_key_exists('use', $use) && array_key_exists('as', $use)) {
+ $uses = $use;
+ $use = $uses['use'];
+ $as = $uses['as'];
+ }
+
+ $use = trim($use, '\\');
+ if ($as === null) {
+ $as = trim($use, '\\');
+ $nsSeparatorPosition = strrpos($as, '\\');
+ if ($nsSeparatorPosition !== false && $nsSeparatorPosition !== 0 && $nsSeparatorPosition != strlen($as)) {
+ $as = substr($as, $nsSeparatorPosition + 1);
+ }
+ }
+
+ $this->uses[$use] = $as;
+ }
+
+ /**
+ * @return array
+ */
+ public function getUses()
+ {
+ return $this->uses;
+ }
+
+ /**
+ * @param string $name
+ * @return string
+ */
+ public function resolveName($name)
+ {
+ if ($this->namespace && !$this->uses && strlen($name) > 0 && $name{0} != '\\') {
+ return $this->namespace . '\\' . $name;
+ }
+
+ if (!$this->uses || strlen($name) <= 0 || $name{0} == '\\') {
+ return ltrim($name, '\\');
+ }
+
+ if ($this->namespace || $this->uses) {
+ $firstPart = $name;
+ if (($firstPartEnd = strpos($firstPart, '\\')) !== false) {
+ $firstPart = substr($firstPart, 0, $firstPartEnd);
+ } else {
+ $firstPartEnd = strlen($firstPart);
+ }
+ if (($fqns = array_search($firstPart, $this->uses)) !== false) {
+ return substr_replace($name, $fqns, 0, $firstPartEnd);
+ }
+ if ($this->namespace) {
+ return $this->namespace . '\\' . $name;
+ }
+ }
+
+ return $name;
+ }
+}
diff --git a/library/Zend/Code/README.md b/library/Zend/Code/README.md
new file mode 100755
index 0000000000..640084b572
--- /dev/null
+++ b/library/Zend/Code/README.md
@@ -0,0 +1,15 @@
+Code Component from ZF2
+=======================
+
+This is the Code component for ZF2.
+
+- File issues at https://github.com/zendframework/zf2/issues
+- Create pull requests against https://github.com/zendframework/zf2
+- Documentation is at http://framework.zend.com/docs
+
+LICENSE
+-------
+
+The files in this archive are released under the [Zend Framework
+license](http://framework.zend.com/license), which is a 3-clause BSD license.
+
diff --git a/library/Zend/Code/Reflection/ClassReflection.php b/library/Zend/Code/Reflection/ClassReflection.php
new file mode 100755
index 0000000000..86d1f14cbd
--- /dev/null
+++ b/library/Zend/Code/Reflection/ClassReflection.php
@@ -0,0 +1,257 @@
+getFileName());
+
+ return $instance;
+ }
+
+ /**
+ * Return the classes DocBlock reflection object
+ *
+ * @return DocBlockReflection
+ * @throws Exception\ExceptionInterface for missing DocBock or invalid reflection class
+ */
+ public function getDocBlock()
+ {
+ if (isset($this->docBlock)) {
+ return $this->docBlock;
+ }
+
+ if ('' == $this->getDocComment()) {
+ return false;
+ }
+
+ $this->docBlock = new DocBlockReflection($this);
+
+ return $this->docBlock;
+ }
+
+ /**
+ * @param AnnotationManager $annotationManager
+ * @return AnnotationCollection
+ */
+ public function getAnnotations(AnnotationManager $annotationManager)
+ {
+ $docComment = $this->getDocComment();
+
+ if ($docComment == '') {
+ return false;
+ }
+
+ if ($this->annotations) {
+ return $this->annotations;
+ }
+
+ $fileScanner = $this->createFileScanner($this->getFileName());
+ $nameInformation = $fileScanner->getClassNameInformation($this->getName());
+
+ if (!$nameInformation) {
+ return false;
+ }
+
+ $this->annotations = new AnnotationScanner($annotationManager, $docComment, $nameInformation);
+
+ return $this->annotations;
+ }
+
+ /**
+ * Return the start line of the class
+ *
+ * @param bool $includeDocComment
+ * @return int
+ */
+ public function getStartLine($includeDocComment = false)
+ {
+ if ($includeDocComment && $this->getDocComment() != '') {
+ return $this->getDocBlock()->getStartLine();
+ }
+
+ return parent::getStartLine();
+ }
+
+ /**
+ * Return the contents of the class
+ *
+ * @param bool $includeDocBlock
+ * @return string
+ */
+ public function getContents($includeDocBlock = true)
+ {
+ $fileName = $this->getFileName();
+
+ if (false === $fileName || ! file_exists($fileName)) {
+ return '';
+ }
+
+ $filelines = file($fileName);
+ $startnum = $this->getStartLine($includeDocBlock);
+ $endnum = $this->getEndLine() - $this->getStartLine();
+
+ // Ensure we get between the open and close braces
+ $lines = array_slice($filelines, $startnum, $endnum);
+ array_unshift($lines, $filelines[$startnum-1]);
+
+ return strstr(implode('', $lines), '{');
+ }
+
+ /**
+ * Get all reflection objects of implemented interfaces
+ *
+ * @return ClassReflection[]
+ */
+ public function getInterfaces()
+ {
+ $phpReflections = parent::getInterfaces();
+ $zendReflections = array();
+ while ($phpReflections && ($phpReflection = array_shift($phpReflections))) {
+ $instance = new ClassReflection($phpReflection->getName());
+ $zendReflections[] = $instance;
+ unset($phpReflection);
+ }
+ unset($phpReflections);
+
+ return $zendReflections;
+ }
+
+ /**
+ * Return method reflection by name
+ *
+ * @param string $name
+ * @return MethodReflection
+ */
+ public function getMethod($name)
+ {
+ $method = new MethodReflection($this->getName(), parent::getMethod($name)->getName());
+
+ return $method;
+ }
+
+ /**
+ * Get reflection objects of all methods
+ *
+ * @param int $filter
+ * @return MethodReflection[]
+ */
+ public function getMethods($filter = -1)
+ {
+ $methods = array();
+ foreach (parent::getMethods($filter) as $method) {
+ $instance = new MethodReflection($this->getName(), $method->getName());
+ $methods[] = $instance;
+ }
+
+ return $methods;
+ }
+
+ /**
+ * Get parent reflection class of reflected class
+ *
+ * @return ClassReflection|bool
+ */
+ public function getParentClass()
+ {
+ $phpReflection = parent::getParentClass();
+ if ($phpReflection) {
+ $zendReflection = new ClassReflection($phpReflection->getName());
+ unset($phpReflection);
+
+ return $zendReflection;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return reflection property of this class by name
+ *
+ * @param string $name
+ * @return PropertyReflection
+ */
+ public function getProperty($name)
+ {
+ $phpReflection = parent::getProperty($name);
+ $zendReflection = new PropertyReflection($this->getName(), $phpReflection->getName());
+ unset($phpReflection);
+
+ return $zendReflection;
+ }
+
+ /**
+ * Return reflection properties of this class
+ *
+ * @param int $filter
+ * @return PropertyReflection[]
+ */
+ public function getProperties($filter = -1)
+ {
+ $phpReflections = parent::getProperties($filter);
+ $zendReflections = array();
+ while ($phpReflections && ($phpReflection = array_shift($phpReflections))) {
+ $instance = new PropertyReflection($this->getName(), $phpReflection->getName());
+ $zendReflections[] = $instance;
+ unset($phpReflection);
+ }
+ unset($phpReflections);
+
+ return $zendReflections;
+ }
+
+ public function toString()
+ {
+ return parent::__toString();
+ }
+
+ public function __toString()
+ {
+ return parent::__toString();
+ }
+
+ /**
+ * Creates a new FileScanner instance.
+ *
+ * By having this as a seperate method it allows the method to be overridden
+ * if a different FileScanner is needed.
+ *
+ * @param string $filename
+ *
+ * @return FileScanner
+ */
+ protected function createFileScanner($filename)
+ {
+ return new FileScanner($filename);
+ }
+}
diff --git a/library/Zend/Code/Reflection/DocBlock/Tag/AuthorTag.php b/library/Zend/Code/Reflection/DocBlock/Tag/AuthorTag.php
new file mode 100755
index 0000000000..9afdee0878
--- /dev/null
+++ b/library/Zend/Code/Reflection/DocBlock/Tag/AuthorTag.php
@@ -0,0 +1,74 @@
+]*)\>)?(.*)$/u', $tagDocblockLine, $match)) {
+ return;
+ }
+
+ if ($match[1] !== '') {
+ $this->authorName = rtrim($match[1]);
+ }
+
+ if (isset($match[3]) && $match[3] !== '') {
+ $this->authorEmail = $match[3];
+ }
+ }
+
+ /**
+ * @return null|string
+ */
+ public function getAuthorName()
+ {
+ return $this->authorName;
+ }
+
+ /**
+ * @return null|string
+ */
+ public function getAuthorEmail()
+ {
+ return $this->authorEmail;
+ }
+
+ public function __toString()
+ {
+ return 'DocBlock Tag [ * @' . $this->getName() . ' ]' . PHP_EOL;
+ }
+}
diff --git a/library/Zend/Code/Reflection/DocBlock/Tag/GenericTag.php b/library/Zend/Code/Reflection/DocBlock/Tag/GenericTag.php
new file mode 100755
index 0000000000..9f34810852
--- /dev/null
+++ b/library/Zend/Code/Reflection/DocBlock/Tag/GenericTag.php
@@ -0,0 +1,109 @@
+contentSplitCharacter = $contentSplitCharacter;
+ }
+
+ /**
+ * @param string $tagDocBlockLine
+ * @return void
+ */
+ public function initialize($tagDocBlockLine)
+ {
+ $this->parse($tagDocBlockLine);
+ }
+
+ /**
+ * Get annotation tag name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @param string $name
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * @return string
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * @param int $position
+ * @return string
+ */
+ public function returnValue($position)
+ {
+ return $this->values[$position];
+ }
+
+ /**
+ * Serialize to string
+ *
+ * Required by Reflector
+ *
+ * @todo What should this do?
+ * @return string
+ */
+ public function __toString()
+ {
+ return 'DocBlock Tag [ * @' . $this->name . ' ]' . PHP_EOL;
+ }
+
+ /**
+ * @param string $docBlockLine
+ */
+ protected function parse($docBlockLine)
+ {
+ $this->content = trim($docBlockLine);
+ $this->values = explode($this->contentSplitCharacter, $docBlockLine);
+ }
+}
diff --git a/library/Zend/Code/Reflection/DocBlock/Tag/LicenseTag.php b/library/Zend/Code/Reflection/DocBlock/Tag/LicenseTag.php
new file mode 100755
index 0000000000..d1148ef621
--- /dev/null
+++ b/library/Zend/Code/Reflection/DocBlock/Tag/LicenseTag.php
@@ -0,0 +1,74 @@
+url = trim($match[1]);
+ }
+
+ if (isset($match[2]) && $match[2] !== '') {
+ $this->licenseName = $match[2];
+ }
+ }
+
+ /**
+ * @return null|string
+ */
+ public function getUrl()
+ {
+ return $this->url;
+ }
+
+ /**
+ * @return null|string
+ */
+ public function getLicenseName()
+ {
+ return $this->licenseName;
+ }
+
+ public function __toString()
+ {
+ return 'DocBlock Tag [ * @' . $this->getName() . ' ]' . PHP_EOL;
+ }
+}
diff --git a/library/Zend/Code/Reflection/DocBlock/Tag/MethodTag.php b/library/Zend/Code/Reflection/DocBlock/Tag/MethodTag.php
new file mode 100755
index 0000000000..50738bfea1
--- /dev/null
+++ b/library/Zend/Code/Reflection/DocBlock/Tag/MethodTag.php
@@ -0,0 +1,122 @@
+isStatic = true;
+ }
+
+ if ($match[2] !== '') {
+ $this->types = explode('|', rtrim($match[2]));
+ }
+
+ $this->methodName = $match[3];
+
+ if ($match[4] !== '') {
+ $this->description = $match[4];
+ }
+ }
+
+ /**
+ * Get return value type
+ *
+ * @return null|string
+ * @deprecated 2.0.4 use getTypes instead
+ */
+ public function getReturnType()
+ {
+ if (empty($this->types)) {
+ return null;
+ }
+
+ return $this->types[0];
+ }
+
+ public function getTypes()
+ {
+ return $this->types;
+ }
+
+ /**
+ * @return string
+ */
+ public function getMethodName()
+ {
+ return $this->methodName;
+ }
+
+ /**
+ * @return null|string
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isStatic()
+ {
+ return $this->isStatic;
+ }
+
+ public function __toString()
+ {
+ return 'DocBlock Tag [ * @' . $this->getName() . ' ]' . PHP_EOL;
+ }
+}
diff --git a/library/Zend/Code/Reflection/DocBlock/Tag/ParamTag.php b/library/Zend/Code/Reflection/DocBlock/Tag/ParamTag.php
new file mode 100755
index 0000000000..b11a16e4ef
--- /dev/null
+++ b/library/Zend/Code/Reflection/DocBlock/Tag/ParamTag.php
@@ -0,0 +1,98 @@
+types = explode('|', $matches[1]);
+
+ if (isset($matches[2])) {
+ $this->variableName = $matches[2];
+ }
+
+ if (isset($matches[3])) {
+ $this->description = trim(preg_replace('#\s+#', ' ', $matches[3]));
+ }
+ }
+
+ /**
+ * Get parameter variable type
+ *
+ * @return string
+ * @deprecated 2.0.4 use getTypes instead
+ */
+ public function getType()
+ {
+ if (empty($this->types)) {
+ return '';
+ }
+
+ return $this->types[0];
+ }
+
+ public function getTypes()
+ {
+ return $this->types;
+ }
+
+ /**
+ * Get parameter name
+ *
+ * @return string
+ */
+ public function getVariableName()
+ {
+ return $this->variableName;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+}
diff --git a/library/Zend/Code/Reflection/DocBlock/Tag/PhpDocTypedTagInterface.php b/library/Zend/Code/Reflection/DocBlock/Tag/PhpDocTypedTagInterface.php
new file mode 100755
index 0000000000..01bea4b9d4
--- /dev/null
+++ b/library/Zend/Code/Reflection/DocBlock/Tag/PhpDocTypedTagInterface.php
@@ -0,0 +1,20 @@
+types = explode('|', rtrim($match[1]));
+ }
+
+ if ($match[2] !== '') {
+ $this->propertyName = $match[2];
+ }
+
+ if ($match[3] !== '') {
+ $this->description = $match[3];
+ }
+ }
+
+ /**
+ * @return null|string
+ * @deprecated 2.0.4 use getTypes instead
+ */
+ public function getType()
+ {
+ if (empty($this->types)) {
+ return null;
+ }
+
+ return $this->types[0];
+ }
+
+ public function getTypes()
+ {
+ return $this->types;
+ }
+
+ /**
+ * @return null|string
+ */
+ public function getPropertyName()
+ {
+ return $this->propertyName;
+ }
+
+ /**
+ * @return null|string
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ public function __toString()
+ {
+ return 'DocBlock Tag [ * @' . $this->getName() . ' ]' . PHP_EOL;
+ }
+}
diff --git a/library/Zend/Code/Reflection/DocBlock/Tag/ReturnTag.php b/library/Zend/Code/Reflection/DocBlock/Tag/ReturnTag.php
new file mode 100755
index 0000000000..f43d7e2e3f
--- /dev/null
+++ b/library/Zend/Code/Reflection/DocBlock/Tag/ReturnTag.php
@@ -0,0 +1,75 @@
+types = explode('|', $matches[1]);
+
+ if (isset($matches[2])) {
+ $this->description = trim(preg_replace('#\s+#', ' ', $matches[2]));
+ }
+ }
+
+ /**
+ * @return string
+ * @deprecated 2.0.4 use getTypes instead
+ */
+ public function getType()
+ {
+ if (empty($this->types)) {
+ return '';
+ }
+
+ return $this->types[0];
+ }
+
+ public function getTypes()
+ {
+ return $this->types;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+}
diff --git a/library/Zend/Code/Reflection/DocBlock/Tag/TagInterface.php b/library/Zend/Code/Reflection/DocBlock/Tag/TagInterface.php
new file mode 100755
index 0000000000..f34e154f64
--- /dev/null
+++ b/library/Zend/Code/Reflection/DocBlock/Tag/TagInterface.php
@@ -0,0 +1,21 @@
+types = explode('|', $matches[1]);
+
+ if (isset($matches[2])) {
+ $this->description = $matches[2];
+ }
+ }
+
+ /**
+ * Get return variable type
+ *
+ * @return string
+ * @deprecated 2.0.4 use getTypes instead
+ */
+ public function getType()
+ {
+ return implode('|', $this->getTypes());
+ }
+
+ /**
+ * @return array
+ */
+ public function getTypes()
+ {
+ return $this->types;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+}
diff --git a/library/Zend/Code/Reflection/DocBlock/TagManager.php b/library/Zend/Code/Reflection/DocBlock/TagManager.php
new file mode 100755
index 0000000000..4d8f0da85c
--- /dev/null
+++ b/library/Zend/Code/Reflection/DocBlock/TagManager.php
@@ -0,0 +1,48 @@
+addPrototype(new Tag\ParamTag());
+ $this->addPrototype(new Tag\ReturnTag());
+ $this->addPrototype(new Tag\MethodTag());
+ $this->addPrototype(new Tag\PropertyTag());
+ $this->addPrototype(new Tag\AuthorTag());
+ $this->addPrototype(new Tag\LicenseTag());
+ $this->addPrototype(new Tag\ThrowsTag());
+ $this->setGenericPrototype(new Tag\GenericTag());
+ }
+
+ /**
+ * @param string $tagName
+ * @param string $content
+ * @return TagInterface
+ */
+ public function createTag($tagName, $content = null)
+ {
+ /* @var TagInterface $newTag */
+ $newTag = $this->getClonedPrototype($tagName);
+
+ if ($content) {
+ $newTag->initialize($content);
+ }
+
+ return $newTag;
+ }
+}
diff --git a/library/Zend/Code/Reflection/DocBlockReflection.php b/library/Zend/Code/Reflection/DocBlockReflection.php
new file mode 100755
index 0000000000..a49021364b
--- /dev/null
+++ b/library/Zend/Code/Reflection/DocBlockReflection.php
@@ -0,0 +1,293 @@
+initializeDefaultTags();
+ }
+ $this->tagManager = $tagManager;
+
+ if ($commentOrReflector instanceof Reflector) {
+ $this->reflector = $commentOrReflector;
+ if (!method_exists($commentOrReflector, 'getDocComment')) {
+ throw new Exception\InvalidArgumentException('Reflector must contain method "getDocComment"');
+ }
+ /* @var MethodReflection $commentOrReflector */
+ $this->docComment = $commentOrReflector->getDocComment();
+
+ // determine line numbers
+ $lineCount = substr_count($this->docComment, "\n");
+ $this->startLine = $this->reflector->getStartLine() - $lineCount - 1;
+ $this->endLine = $this->reflector->getStartLine() - 1;
+ } elseif (is_string($commentOrReflector)) {
+ $this->docComment = $commentOrReflector;
+ } else {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s must have a (string) DocComment or a Reflector in the constructor',
+ get_class($this)
+ ));
+ }
+
+ if ($this->docComment == '') {
+ throw new Exception\InvalidArgumentException('DocComment cannot be empty');
+ }
+
+ $this->reflect();
+ }
+
+ /**
+ * Retrieve contents of DocBlock
+ *
+ * @return string
+ */
+ public function getContents()
+ {
+ $this->reflect();
+
+ return $this->cleanDocComment;
+ }
+
+ /**
+ * Get start line (position) of DocBlock
+ *
+ * @return int
+ */
+ public function getStartLine()
+ {
+ $this->reflect();
+
+ return $this->startLine;
+ }
+
+ /**
+ * Get last line (position) of DocBlock
+ *
+ * @return int
+ */
+ public function getEndLine()
+ {
+ $this->reflect();
+
+ return $this->endLine;
+ }
+
+ /**
+ * Get DocBlock short description
+ *
+ * @return string
+ */
+ public function getShortDescription()
+ {
+ $this->reflect();
+
+ return $this->shortDescription;
+ }
+
+ /**
+ * Get DocBlock long description
+ *
+ * @return string
+ */
+ public function getLongDescription()
+ {
+ $this->reflect();
+
+ return $this->longDescription;
+ }
+
+ /**
+ * Does the DocBlock contain the given annotation tag?
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function hasTag($name)
+ {
+ $this->reflect();
+ foreach ($this->tags as $tag) {
+ if ($tag->getName() == $name) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Retrieve the given DocBlock tag
+ *
+ * @param string $name
+ * @return DocBlockTagInterface|false
+ */
+ public function getTag($name)
+ {
+ $this->reflect();
+ foreach ($this->tags as $tag) {
+ if ($tag->getName() == $name) {
+ return $tag;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get all DocBlock annotation tags
+ *
+ * @param string $filter
+ * @return DocBlockTagInterface[]
+ */
+ public function getTags($filter = null)
+ {
+ $this->reflect();
+ if ($filter === null || !is_string($filter)) {
+ return $this->tags;
+ }
+
+ $returnTags = array();
+ foreach ($this->tags as $tag) {
+ if ($tag->getName() == $filter) {
+ $returnTags[] = $tag;
+ }
+ }
+
+ return $returnTags;
+ }
+
+ /**
+ * Parse the DocBlock
+ *
+ * @return void
+ */
+ protected function reflect()
+ {
+ if ($this->isReflected) {
+ return;
+ }
+
+ $docComment = $this->docComment; // localize variable
+
+ // create a clean docComment
+ $this->cleanDocComment = preg_replace("#[ \t]*(?:/\*\*|\*/|\*)[ ]{0,1}(.*)?#", '$1', $docComment);
+ $this->cleanDocComment = ltrim($this->cleanDocComment, "\r\n"); // @todo should be changed to remove first and last empty line
+
+ $scanner = new DocBlockScanner($docComment);
+ $this->shortDescription = ltrim($scanner->getShortDescription());
+ $this->longDescription = ltrim($scanner->getLongDescription());
+
+ foreach ($scanner->getTags() as $tag) {
+ $this->tags[] = $this->tagManager->createTag(ltrim($tag['name'], '@'), ltrim($tag['value']));
+ }
+
+ $this->isReflected = true;
+ }
+
+ public function toString()
+ {
+ $str = "DocBlock [ /* DocBlock */ ] {" . PHP_EOL . PHP_EOL;
+ $str .= " - Tags [" . count($this->tags) . "] {" . PHP_EOL;
+
+ foreach ($this->tags as $tag) {
+ $str .= " " . $tag;
+ }
+
+ $str .= " }" . PHP_EOL;
+ $str .= "}" . PHP_EOL;
+
+ return $str;
+ }
+
+ /**
+ * Serialize to string
+ *
+ * Required by the Reflector interface
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+}
diff --git a/library/Zend/Code/Reflection/Exception/BadMethodCallException.php b/library/Zend/Code/Reflection/Exception/BadMethodCallException.php
new file mode 100755
index 0000000000..6eeb226fb5
--- /dev/null
+++ b/library/Zend/Code/Reflection/Exception/BadMethodCallException.php
@@ -0,0 +1,17 @@
+filePath = $fileRealPath;
+ $this->reflect();
+ }
+
+ /**
+ * Required by the Reflector interface.
+ *
+ * @todo What should this do?
+ * @return null
+ */
+ public static function export()
+ {
+ return null;
+ }
+
+ /**
+ * Return the file name of the reflected file
+ *
+ * @return string
+ */
+ public function getFileName()
+ {
+ // @todo get file name from path
+ return $this->filePath;
+ }
+
+ /**
+ * Get the start line - Always 1, staying consistent with the Reflection API
+ *
+ * @return int
+ */
+ public function getStartLine()
+ {
+ return $this->startLine;
+ }
+
+ /**
+ * Get the end line / number of lines
+ *
+ * @return int
+ */
+ public function getEndLine()
+ {
+ return $this->endLine;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDocComment()
+ {
+ return $this->docComment;
+ }
+
+ /**
+ * @return DocBlockReflection
+ */
+ public function getDocBlock()
+ {
+ if (!($docComment = $this->getDocComment())) {
+ return false;
+ }
+
+ $instance = new DocBlockReflection($docComment);
+
+ return $instance;
+ }
+
+ /**
+ * @return array
+ */
+ public function getNamespaces()
+ {
+ return $this->namespaces;
+ }
+
+ /**
+ * @return string
+ */
+ public function getNamespace()
+ {
+ if (count($this->namespaces) == 0) {
+ return null;
+ }
+
+ return $this->namespaces[0];
+ }
+
+ /**
+ * @return array
+ */
+ public function getUses()
+ {
+ return $this->uses;
+ }
+
+ /**
+ * Return the reflection classes of the classes found inside this file
+ *
+ * @return ClassReflection[]
+ */
+ public function getClasses()
+ {
+ $classes = array();
+ foreach ($this->classes as $class) {
+ $classes[] = new ClassReflection($class);
+ }
+
+ return $classes;
+ }
+
+ /**
+ * Return the reflection functions of the functions found inside this file
+ *
+ * @return FunctionReflection[]
+ */
+ public function getFunctions()
+ {
+ $functions = array();
+ foreach ($this->functions as $function) {
+ $functions[] = new FunctionReflection($function);
+ }
+
+ return $functions;
+ }
+
+ /**
+ * Retrieve the reflection class of a given class found in this file
+ *
+ * @param null|string $name
+ * @return ClassReflection
+ * @throws Exception\InvalidArgumentException for invalid class name or invalid reflection class
+ */
+ public function getClass($name = null)
+ {
+ if (null === $name) {
+ reset($this->classes);
+ $selected = current($this->classes);
+
+ return new ClassReflection($selected);
+ }
+
+ if (in_array($name, $this->classes)) {
+ return new ClassReflection($name);
+ }
+
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Class by name %s not found.',
+ $name
+ ));
+ }
+
+ /**
+ * Return the full contents of file
+ *
+ * @return string
+ */
+ public function getContents()
+ {
+ return file_get_contents($this->filePath);
+ }
+
+ public function toString()
+ {
+ return ''; // @todo
+ }
+
+ /**
+ * Serialize to string
+ *
+ * Required by the Reflector interface
+ *
+ * @todo What should this serialization look like?
+ * @return string
+ */
+ public function __toString()
+ {
+ return '';
+ }
+
+ /**
+ * This method does the work of "reflecting" the file
+ *
+ * Uses Zend\Code\Scanner\FileScanner to gather file information
+ *
+ * @return void
+ */
+ protected function reflect()
+ {
+ $scanner = new CachingFileScanner($this->filePath);
+ $this->docComment = $scanner->getDocComment();
+ $this->requiredFiles = $scanner->getIncludes();
+ $this->classes = $scanner->getClassNames();
+ $this->namespaces = $scanner->getNamespaces();
+ $this->uses = $scanner->getUses();
+ }
+
+ /**
+ * Validate / check a file level DocBlock
+ *
+ * @param array $tokens Array of tokenizer tokens
+ * @return void
+ */
+ protected function checkFileDocBlock($tokens)
+ {
+ foreach ($tokens as $token) {
+ $type = $token[0];
+ $value = $token[1];
+ $lineNum = $token[2];
+ if (($type == T_OPEN_TAG) || ($type == T_WHITESPACE)) {
+ continue;
+ } elseif ($type == T_DOC_COMMENT) {
+ $this->docComment = $value;
+ $this->startLine = $lineNum + substr_count($value, "\n") + 1;
+
+ return;
+ } else {
+ // Only whitespace is allowed before file DocBlocks
+ return;
+ }
+ }
+ }
+}
diff --git a/library/Zend/Code/Reflection/FunctionReflection.php b/library/Zend/Code/Reflection/FunctionReflection.php
new file mode 100755
index 0000000000..c752f1ecac
--- /dev/null
+++ b/library/Zend/Code/Reflection/FunctionReflection.php
@@ -0,0 +1,266 @@
+getDocComment())) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s does not have a DocBlock',
+ $this->getName()
+ ));
+ }
+
+ $instance = new DocBlockReflection($comment);
+
+ return $instance;
+ }
+
+ /**
+ * Get start line (position) of function
+ *
+ * @param bool $includeDocComment
+ * @return int
+ */
+ public function getStartLine($includeDocComment = false)
+ {
+ if ($includeDocComment) {
+ if ($this->getDocComment() != '') {
+ return $this->getDocBlock()->getStartLine();
+ }
+ }
+
+ return parent::getStartLine();
+ }
+
+ /**
+ * Get contents of function
+ *
+ * @param bool $includeDocBlock
+ * @return string
+ */
+ public function getContents($includeDocBlock = true)
+ {
+ $fileName = $this->getFileName();
+ if (false === $fileName) {
+ return '';
+ }
+
+ $startLine = $this->getStartLine();
+ $endLine = $this->getEndLine();
+
+ // eval'd protect
+ if (preg_match('#\((\d+)\) : eval\(\)\'d code$#', $fileName, $matches)) {
+ $fileName = preg_replace('#\(\d+\) : eval\(\)\'d code$#', '', $fileName);
+ $startLine = $endLine = $matches[1];
+ }
+
+ $lines = array_slice(
+ file($fileName, FILE_IGNORE_NEW_LINES),
+ $startLine - 1,
+ ($endLine - ($startLine - 1)),
+ true
+ );
+
+ $functionLine = implode("\n", $lines);
+
+ $content = '';
+ if ($this->isClosure()) {
+ preg_match('#function\s*\([^\)]*\)\s*(use\s*\([^\)]+\))?\s*\{(.*\;)?\s*\}#s', $functionLine, $matches);
+ if (isset($matches[0])) {
+ $content = $matches[0];
+ }
+ } else {
+ $name = substr($this->getName(), strrpos($this->getName(), '\\')+1);
+ preg_match('#function\s+' . preg_quote($name) . '\s*\([^\)]*\)\s*{([^{}]+({[^}]+})*[^}]+)?}#', $functionLine, $matches);
+ if (isset($matches[0])) {
+ $content = $matches[0];
+ }
+ }
+
+ $docComment = $this->getDocComment();
+
+ return $includeDocBlock && $docComment ? $docComment . "\n" . $content : $content;
+ }
+
+ /**
+ * Get method prototype
+ *
+ * @return array
+ */
+ public function getPrototype($format = FunctionReflection::PROTOTYPE_AS_ARRAY)
+ {
+ $returnType = 'mixed';
+ $docBlock = $this->getDocBlock();
+ if ($docBlock) {
+ $return = $docBlock->getTag('return');
+ $returnTypes = $return->getTypes();
+ $returnType = count($returnTypes) > 1 ? implode('|', $returnTypes) : $returnTypes[0];
+ }
+
+ $prototype = array(
+ 'namespace' => $this->getNamespaceName(),
+ 'name' => substr($this->getName(), strlen($this->getNamespaceName()) + 1),
+ 'return' => $returnType,
+ 'arguments' => array(),
+ );
+
+ $parameters = $this->getParameters();
+ foreach ($parameters as $parameter) {
+ $prototype['arguments'][$parameter->getName()] = array(
+ 'type' => $parameter->getType(),
+ 'required' => !$parameter->isOptional(),
+ 'by_ref' => $parameter->isPassedByReference(),
+ 'default' => $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null,
+ );
+ }
+
+ if ($format == FunctionReflection::PROTOTYPE_AS_STRING) {
+ $line = $prototype['return'] . ' ' . $prototype['name'] . '(';
+ $args = array();
+ foreach ($prototype['arguments'] as $name => $argument) {
+ $argsLine = ($argument['type'] ? $argument['type'] . ' ' : '') . ($argument['by_ref'] ? '&' : '') . '$' . $name;
+ if (!$argument['required']) {
+ $argsLine .= ' = ' . var_export($argument['default'], true);
+ }
+ $args[] = $argsLine;
+ }
+ $line .= implode(', ', $args);
+ $line .= ')';
+
+ return $line;
+ }
+
+ return $prototype;
+ }
+
+ /**
+ * Get function parameters
+ *
+ * @return ParameterReflection[]
+ */
+ public function getParameters()
+ {
+ $phpReflections = parent::getParameters();
+ $zendReflections = array();
+ while ($phpReflections && ($phpReflection = array_shift($phpReflections))) {
+ $instance = new ParameterReflection($this->getName(), $phpReflection->getName());
+ $zendReflections[] = $instance;
+ unset($phpReflection);
+ }
+ unset($phpReflections);
+
+ return $zendReflections;
+ }
+
+ /**
+ * Get return type tag
+ *
+ * @throws Exception\InvalidArgumentException
+ * @return DocBlockReflection
+ */
+ public function getReturn()
+ {
+ $docBlock = $this->getDocBlock();
+ if (!$docBlock->hasTag('return')) {
+ throw new Exception\InvalidArgumentException(
+ 'Function does not specify an @return annotation tag; cannot determine return type'
+ );
+ }
+
+ $tag = $docBlock->getTag('return');
+
+ return new DocBlockReflection('@return ' . $tag->getDescription());
+ }
+
+ /**
+ * Get method body
+ *
+ * @return string|bool
+ */
+ public function getBody()
+ {
+ $fileName = $this->getFileName();
+ if (false === $fileName) {
+ throw new Exception\InvalidArgumentException(
+ 'Cannot determine internals functions body'
+ );
+ }
+
+ $startLine = $this->getStartLine();
+ $endLine = $this->getEndLine();
+
+ // eval'd protect
+ if (preg_match('#\((\d+)\) : eval\(\)\'d code$#', $fileName, $matches)) {
+ $fileName = preg_replace('#\(\d+\) : eval\(\)\'d code$#', '', $fileName);
+ $startLine = $endLine = $matches[1];
+ }
+
+ $lines = array_slice(
+ file($fileName, FILE_IGNORE_NEW_LINES),
+ $startLine - 1,
+ ($endLine - ($startLine - 1)),
+ true
+ );
+
+ $functionLine = implode("\n", $lines);
+
+ $body = false;
+ if ($this->isClosure()) {
+ preg_match('#function\s*\([^\)]*\)\s*(use\s*\([^\)]+\))?\s*\{(.*\;)\s*\}#s', $functionLine, $matches);
+ if (isset($matches[2])) {
+ $body = $matches[2];
+ }
+ } else {
+ $name = substr($this->getName(), strrpos($this->getName(), '\\')+1);
+ preg_match('#function\s+' . $name . '\s*\([^\)]*\)\s*{([^{}]+({[^}]+})*[^}]+)}#', $functionLine, $matches);
+ if (isset($matches[1])) {
+ $body = $matches[1];
+ }
+ }
+
+ return $body;
+ }
+
+ public function toString()
+ {
+ return $this->__toString();
+ }
+
+ /**
+ * Required due to bug in php
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return parent::__toString();
+ }
+}
diff --git a/library/Zend/Code/Reflection/MethodReflection.php b/library/Zend/Code/Reflection/MethodReflection.php
new file mode 100755
index 0000000000..10a9b8ab4c
--- /dev/null
+++ b/library/Zend/Code/Reflection/MethodReflection.php
@@ -0,0 +1,487 @@
+getDocComment()) {
+ return false;
+ }
+
+ $instance = new DocBlockReflection($this);
+
+ return $instance;
+ }
+
+ /**
+ * @param AnnotationManager $annotationManager
+ * @return AnnotationScanner
+ */
+ public function getAnnotations(AnnotationManager $annotationManager)
+ {
+ if (($docComment = $this->getDocComment()) == '') {
+ return false;
+ }
+
+ if ($this->annotations) {
+ return $this->annotations;
+ }
+
+ $cachingFileScanner = $this->createFileScanner($this->getFileName());
+ $nameInformation = $cachingFileScanner->getClassNameInformation($this->getDeclaringClass()->getName());
+
+ if (!$nameInformation) {
+ return false;
+ }
+
+ $this->annotations = new AnnotationScanner($annotationManager, $docComment, $nameInformation);
+
+ return $this->annotations;
+ }
+
+ /**
+ * Get start line (position) of method
+ *
+ * @param bool $includeDocComment
+ * @return int
+ */
+ public function getStartLine($includeDocComment = false)
+ {
+ if ($includeDocComment) {
+ if ($this->getDocComment() != '') {
+ return $this->getDocBlock()->getStartLine();
+ }
+ }
+
+ return parent::getStartLine();
+ }
+
+ /**
+ * Get reflection of declaring class
+ *
+ * @return ClassReflection
+ */
+ public function getDeclaringClass()
+ {
+ $phpReflection = parent::getDeclaringClass();
+ $zendReflection = new ClassReflection($phpReflection->getName());
+ unset($phpReflection);
+
+ return $zendReflection;
+ }
+
+ /**
+ * Get method prototype
+ *
+ * @return array
+ */
+ public function getPrototype($format = MethodReflection::PROTOTYPE_AS_ARRAY)
+ {
+ $returnType = 'mixed';
+ $docBlock = $this->getDocBlock();
+ if ($docBlock) {
+ $return = $docBlock->getTag('return');
+ $returnTypes = $return->getTypes();
+ $returnType = count($returnTypes) > 1 ? implode('|', $returnTypes) : $returnTypes[0];
+ }
+
+ $declaringClass = $this->getDeclaringClass();
+ $prototype = array(
+ 'namespace' => $declaringClass->getNamespaceName(),
+ 'class' => substr($declaringClass->getName(), strlen($declaringClass->getNamespaceName()) + 1),
+ 'name' => $this->getName(),
+ 'visibility' => ($this->isPublic() ? 'public' : ($this->isPrivate() ? 'private' : 'protected')),
+ 'return' => $returnType,
+ 'arguments' => array(),
+ );
+
+ $parameters = $this->getParameters();
+ foreach ($parameters as $parameter) {
+ $prototype['arguments'][$parameter->getName()] = array(
+ 'type' => $parameter->getType(),
+ 'required' => !$parameter->isOptional(),
+ 'by_ref' => $parameter->isPassedByReference(),
+ 'default' => $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null,
+ );
+ }
+
+ if ($format == MethodReflection::PROTOTYPE_AS_STRING) {
+ $line = $prototype['visibility'] . ' ' . $prototype['return'] . ' ' . $prototype['name'] . '(';
+ $args = array();
+ foreach ($prototype['arguments'] as $name => $argument) {
+ $argsLine = ($argument['type'] ? $argument['type'] . ' ' : '') . ($argument['by_ref'] ? '&' : '') . '$' . $name;
+ if (!$argument['required']) {
+ $argsLine .= ' = ' . var_export($argument['default'], true);
+ }
+ $args[] = $argsLine;
+ }
+ $line .= implode(', ', $args);
+ $line .= ')';
+
+ return $line;
+ }
+
+ return $prototype;
+ }
+
+ /**
+ * Get all method parameter reflection objects
+ *
+ * @return ParameterReflection[]
+ */
+ public function getParameters()
+ {
+ $phpReflections = parent::getParameters();
+ $zendReflections = array();
+ while ($phpReflections && ($phpReflection = array_shift($phpReflections))) {
+ $instance = new ParameterReflection(
+ array($this->getDeclaringClass()->getName(), $this->getName()),
+ $phpReflection->getName()
+ );
+ $zendReflections[] = $instance;
+ unset($phpReflection);
+ }
+ unset($phpReflections);
+
+ return $zendReflections;
+ }
+
+ /**
+ * Get method contents
+ *
+ * @param bool $includeDocBlock
+ * @return string|bool
+ */
+ public function getContents($includeDocBlock = true)
+ {
+ $docComment = $this->getDocComment();
+ $content = ($includeDocBlock && !empty($docComment)) ? $docComment . "\n" : '';
+ $content .= $this->extractMethodContents();
+
+ return $content;
+ }
+
+ /**
+ * Get method body
+ *
+ * @return string|bool
+ */
+ public function getBody()
+ {
+ return $this->extractMethodContents(true);
+ }
+
+ /**
+ * Tokenize method string and return concatenated body
+ *
+ * @param bool $bodyOnly
+ * @return string
+ */
+ protected function extractMethodContents($bodyOnly=false)
+ {
+ $fileName = $this->getDeclaringClass()->getFileName();
+
+ if ((class_exists($this->class) && false === $fileName) || ! file_exists($fileName)) {
+ return '';
+ }
+
+ $lines = array_slice(
+ file($fileName, FILE_IGNORE_NEW_LINES),
+ $this->getStartLine() - 1,
+ ($this->getEndLine() - ($this->getStartLine() - 1)),
+ true
+ );
+
+ $functionLine = implode("\n", $lines);
+ $tokens = token_get_all(" $token) {
+ $tokenType = (is_array($token)) ? token_name($token[0]) : $token;
+ $tokenValue = (is_array($token)) ? $token[1] : $token;
+
+ switch ($tokenType) {
+ case "T_FINAL":
+ case "T_ABSTRACT":
+ case "T_PUBLIC":
+ case "T_PROTECTED":
+ case "T_PRIVATE":
+ case "T_STATIC":
+ case "T_FUNCTION":
+ // check to see if we have a valid function
+ // then check if we are inside function and have a closure
+ if ($this->isValidFunction($tokens, $key, $this->getName())) {
+ if ($bodyOnly === false) {
+ //if first instance of tokenType grab prefixed whitespace
+ //and append to body
+ if ($capture === false) {
+ $body .= $this->extractPrefixedWhitespace($tokens, $key);
+ }
+ $body .= $tokenValue;
+ }
+
+ $capture = true;
+ } else {
+ //closure test
+ if ($firstBrace && $tokenType == "T_FUNCTION") {
+ $body .= $tokenValue;
+ continue;
+ }
+ $capture = false;
+ continue;
+ }
+ break;
+
+ case "{":
+ if ($capture === false) {
+ continue;
+ }
+
+ if ($firstBrace === false) {
+ $firstBrace = true;
+ if ($bodyOnly === true) {
+ continue;
+ }
+ }
+
+ $body .= $tokenValue;
+ break;
+
+ case "}":
+ if ($capture === false) {
+ continue;
+ }
+
+ //check to see if this is the last brace
+ if ($this->isEndingBrace($tokens, $key)) {
+ //capture the end brace if not bodyOnly
+ if ($bodyOnly === false) {
+ $body .= $tokenValue;
+ }
+
+ break 2;
+ }
+
+ $body .= $tokenValue;
+ break;
+
+ default:
+ if ($capture === false) {
+ continue;
+ }
+
+ // if returning body only wait for first brace before capturing
+ if ($bodyOnly === true && $firstBrace !== true) {
+ continue;
+ }
+
+ $body .= $tokenValue;
+ break;
+ }
+ }
+
+ //remove ending whitespace and return
+ return rtrim($body);
+ }
+
+ /**
+ * Take current position and find any whitespace
+ *
+ * @param $haystack
+ * @param $position
+ * @return string
+ */
+ protected function extractPrefixedWhitespace($haystack, $position)
+ {
+ $content = '';
+ $count = count($haystack);
+ if ($position+1 == $count) {
+ return $content;
+ }
+
+ for ($i = $position-1;$i >= 0;$i--) {
+ $tokenType = (is_array($haystack[$i])) ? token_name($haystack[$i][0]) : $haystack[$i];
+ $tokenValue = (is_array($haystack[$i])) ? $haystack[$i][1] : $haystack[$i];
+
+ //search only for whitespace
+ if ($tokenType == "T_WHITESPACE") {
+ $content .= $tokenValue;
+ } else {
+ break;
+ }
+ }
+
+ return $content;
+ }
+
+ /**
+ * Test for ending brace
+ *
+ * @param $haystack
+ * @param $position
+ * @return bool
+ */
+ protected function isEndingBrace($haystack, $position)
+ {
+ $count = count($haystack);
+
+ //advance one position
+ $position = $position+1;
+
+ if ($position == $count) {
+ return true;
+ }
+
+ for ($i = $position;$i < $count; $i++) {
+ $tokenType = (is_array($haystack[$i])) ? token_name($haystack[$i][0]) : $haystack[$i];
+ switch ($tokenType) {
+ case "T_FINAL":
+ case "T_ABSTRACT":
+ case "T_PUBLIC":
+ case "T_PROTECTED":
+ case "T_PRIVATE":
+ case "T_STATIC":
+ return true;
+
+ case "T_FUNCTION":
+ // If a function is encountered and that function is not a closure
+ // then return true. otherwise the function is a closure, return false
+ if ($this->isValidFunction($haystack, $i)) {
+ return true;
+ }
+ return false;
+
+ case "}":
+ case ";";
+ case "T_BREAK":
+ case "T_CATCH":
+ case "T_DO":
+ case "T_ECHO":
+ case "T_ELSE":
+ case "T_ELSEIF":
+ case "T_EVAL":
+ case "T_EXIT":
+ case "T_FINALLY":
+ case "T_FOR":
+ case "T_FOREACH":
+ case "T_GOTO":
+ case "T_IF":
+ case "T_INCLUDE":
+ case "T_INCLUDE_ONCE":
+ case "T_PRINT":
+ case "T_STRING":
+ case "T_STRING_VARNAME":
+ case "T_THROW":
+ case "T_USE":
+ case "T_VARIABLE":
+ case "T_WHILE":
+ case "T_YIELD":
+
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Test to see if current position is valid function or
+ * closure. Returns true if it's a function and NOT a closure
+ *
+ * @param $haystack
+ * @param $position
+ * @return bool
+ */
+ protected function isValidFunction($haystack, $position, $functionName = null)
+ {
+ $isValid = false;
+ $count = count($haystack);
+ for ($i = $position+1;$i < $count; $i++) {
+ $tokenType = (is_array($haystack[$i])) ? token_name($haystack[$i][0]) : $haystack[$i];
+ $tokenValue = (is_array($haystack[$i])) ? $haystack[$i][1] : $haystack[$i];
+
+ //check for occurance of ( or
+ if ($tokenType == "T_STRING") {
+ //check to see if function name is passed, if so validate against that
+ if ($functionName !== null && $tokenValue != $functionName) {
+ $isValid = false;
+ break;
+ }
+
+ $isValid = true;
+ break;
+ } elseif ($tokenValue == "(") {
+ break;
+ }
+ }
+
+ return $isValid;
+ }
+
+ public function toString()
+ {
+ return parent::__toString();
+ }
+
+ public function __toString()
+ {
+ return parent::__toString();
+ }
+
+ /**
+ * Creates a new FileScanner instance.
+ *
+ * By having this as a seperate method it allows the method to be overridden
+ * if a different FileScanner is needed.
+ *
+ * @param string $filename
+ *
+ * @return CachingFileScanner
+ */
+ protected function createFileScanner($filename)
+ {
+ return new CachingFileScanner($filename);
+ }
+}
diff --git a/library/Zend/Code/Reflection/ParameterReflection.php b/library/Zend/Code/Reflection/ParameterReflection.php
new file mode 100755
index 0000000000..e6239abce7
--- /dev/null
+++ b/library/Zend/Code/Reflection/ParameterReflection.php
@@ -0,0 +1,110 @@
+getName());
+ unset($phpReflection);
+
+ return $zendReflection;
+ }
+
+ /**
+ * Get class reflection object
+ *
+ * @return ClassReflection
+ */
+ public function getClass()
+ {
+ $phpReflection = parent::getClass();
+ if ($phpReflection == null) {
+ return null;
+ }
+
+ $zendReflection = new ClassReflection($phpReflection->getName());
+ unset($phpReflection);
+
+ return $zendReflection;
+ }
+
+ /**
+ * Get declaring function reflection object
+ *
+ * @return FunctionReflection|MethodReflection
+ */
+ public function getDeclaringFunction()
+ {
+ $phpReflection = parent::getDeclaringFunction();
+ if ($phpReflection instanceof \ReflectionMethod) {
+ $zendReflection = new MethodReflection($this->getDeclaringClass()->getName(), $phpReflection->getName());
+ } else {
+ $zendReflection = new FunctionReflection($phpReflection->getName());
+ }
+ unset($phpReflection);
+
+ return $zendReflection;
+ }
+
+ /**
+ * Get parameter type
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ if ($this->isArray()) {
+ return 'array';
+ } elseif (method_exists($this, 'isCallable') && $this->isCallable()) {
+ return 'callable';
+ }
+
+ if (($class = $this->getClass()) instanceof \ReflectionClass) {
+ return $class->getName();
+ }
+
+ $docBlock = $this->getDeclaringFunction()->getDocBlock();
+ if (!$docBlock instanceof DocBlockReflection) {
+ return null;
+ }
+
+ $params = $docBlock->getTags('param');
+ if (isset($params[$this->getPosition()])) {
+ return $params[$this->getPosition()]->getType();
+ }
+
+ return null;
+ }
+
+ public function toString()
+ {
+ return parent::__toString();
+ }
+
+ public function __toString()
+ {
+ return parent::__toString();
+ }
+}
diff --git a/library/Zend/Code/Reflection/PropertyReflection.php b/library/Zend/Code/Reflection/PropertyReflection.php
new file mode 100755
index 0000000000..c9b5a8d255
--- /dev/null
+++ b/library/Zend/Code/Reflection/PropertyReflection.php
@@ -0,0 +1,111 @@
+getName());
+ unset($phpReflection);
+
+ return $zendReflection;
+ }
+
+ /**
+ * Get DocBlock comment
+ *
+ * @return string|false False if no DocBlock defined
+ */
+ public function getDocComment()
+ {
+ return parent::getDocComment();
+ }
+
+ /**
+ * @return false|DocBlockReflection
+ */
+ public function getDocBlock()
+ {
+ if (!($docComment = $this->getDocComment())) {
+ return false;
+ }
+
+ $docBlockReflection = new DocBlockReflection($docComment);
+
+ return $docBlockReflection;
+ }
+
+ /**
+ * @param AnnotationManager $annotationManager
+ * @return AnnotationScanner
+ */
+ public function getAnnotations(AnnotationManager $annotationManager)
+ {
+ if (null !== $this->annotations) {
+ return $this->annotations;
+ }
+
+ if (($docComment = $this->getDocComment()) == '') {
+ return false;
+ }
+
+ $class = $this->getDeclaringClass();
+ $cachingFileScanner = $this->createFileScanner($class->getFileName());
+ $nameInformation = $cachingFileScanner->getClassNameInformation($class->getName());
+
+ if (!$nameInformation) {
+ return false;
+ }
+
+ $this->annotations = new AnnotationScanner($annotationManager, $docComment, $nameInformation);
+
+ return $this->annotations;
+ }
+
+ public function toString()
+ {
+ return $this->__toString();
+ }
+
+ /**
+ * Creates a new FileScanner instance.
+ *
+ * By having this as a seperate method it allows the method to be overridden
+ * if a different FileScanner is needed.
+ *
+ * @param string $filename
+ *
+ * @return CachingFileScanner
+ */
+ protected function createFileScanner($filename)
+ {
+ return new CachingFileScanner($filename);
+ }
+}
diff --git a/library/Zend/Code/Reflection/ReflectionInterface.php b/library/Zend/Code/Reflection/ReflectionInterface.php
new file mode 100755
index 0000000000..ed5ffd46c0
--- /dev/null
+++ b/library/Zend/Code/Reflection/ReflectionInterface.php
@@ -0,0 +1,20 @@
+directories as $scanner) {
+ $classes += $scanner->getClasses();
+ }
+ if ($returnScannerClass) {
+ foreach ($classes as $index => $class) {
+ $classes[$index] = $this->getClass($class, $returnScannerClass, $returnDerivedScannerClass);
+ }
+ }
+
+ return $classes;
+ }
+
+ /**
+ * @param string $class
+ * @return bool
+ */
+ public function hasClass($class)
+ {
+ foreach ($this->directories as $scanner) {
+ if ($scanner->hasClass($class)) {
+ break;
+ } else {
+ unset($scanner);
+ }
+ }
+
+ return (isset($scanner));
+ }
+
+ /**
+ * @param string $class
+ * @param bool $returnScannerClass
+ * @param bool $returnDerivedScannerClass
+ * @return ClassScanner|DerivedClassScanner
+ * @throws Exception\RuntimeException
+ */
+ public function getClass($class, $returnScannerClass = true, $returnDerivedScannerClass = false)
+ {
+ foreach ($this->directories as $scanner) {
+ if ($scanner->hasClass($class)) {
+ break;
+ } else {
+ unset($scanner);
+ }
+ }
+
+ if (!isset($scanner)) {
+ throw new Exception\RuntimeException('Class by that name was not found.');
+ }
+
+ $classScanner = $scanner->getClass($class);
+
+ return new DerivedClassScanner($classScanner, $this);
+ }
+
+ /**
+ * @param bool $returnScannerClass
+ */
+ public function getFunctions($returnScannerClass = false)
+ {
+ $this->scan();
+
+ if (!$returnScannerClass) {
+ $functions = array();
+ foreach ($this->infos as $info) {
+ if ($info['type'] == 'function') {
+ $functions[] = $info['name'];
+ }
+ }
+
+ return $functions;
+ }
+ $scannerClass = new FunctionScanner();
+ // @todo
+ }
+}
diff --git a/library/Zend/Code/Scanner/AnnotationScanner.php b/library/Zend/Code/Scanner/AnnotationScanner.php
new file mode 100755
index 0000000000..76e8144c01
--- /dev/null
+++ b/library/Zend/Code/Scanner/AnnotationScanner.php
@@ -0,0 +1,338 @@
+annotationManager = $annotationManager;
+ $this->docComment = $docComment;
+ $this->nameInformation = $nameInformation;
+ $this->scan($this->tokenize());
+ }
+
+ /**
+ * @param NameInformation $nameInformation
+ */
+ public function setNameInformation(NameInformation $nameInformation)
+ {
+ $this->nameInformation = $nameInformation;
+ }
+
+ /**
+ * @param array $tokens
+ */
+ protected function scan(array $tokens)
+ {
+ $annotations = array();
+ $annotationIndex = -1;
+ $contentEnd = false;
+
+ reset($tokens);
+
+ SCANNER_TOP:
+ $token = current($tokens);
+
+ switch ($token[0]) {
+
+ case 'ANNOTATION_CLASS':
+
+ $contentEnd = false;
+ $annotationIndex++;
+ $class = substr($token[1], 1);
+ $class = $this->nameInformation->resolveName($class);
+ $annotations[$annotationIndex] = array($class, null);
+ goto SCANNER_CONTINUE;
+ // goto no break needed
+
+ case 'ANNOTATION_CONTENT_START':
+
+ $annotations[$annotationIndex][1] = '';
+ //fall-through
+
+ case 'ANNOTATION_CONTENT_END':
+ case 'ANNOTATION_CONTENT':
+ case 'ANNOTATION_WHITESPACE':
+ case 'ANNOTATION_NEWLINE':
+
+ if (!$contentEnd && isset($annotations[$annotationIndex]) && is_string($annotations[$annotationIndex][1])) {
+ $annotations[$annotationIndex][1] .= $token[1];
+ }
+
+ if ($token[0] === 'ANNOTATION_CONTENT_END') {
+ $contentEnd = true;
+ }
+
+ goto SCANNER_CONTINUE;
+ }
+
+ SCANNER_CONTINUE:
+ if (next($tokens) === false) {
+ goto SCANNER_END;
+ }
+ goto SCANNER_TOP;
+
+ SCANNER_END:
+
+ foreach ($annotations as $annotation) {
+ $annotation[] = '@' . $annotation[0] . $annotation[1];
+ $annotationObject = $this->annotationManager->createAnnotation($annotation);
+ if ($annotationObject) {
+ $this->append($annotationObject);
+ }
+ }
+ }
+
+ /**
+ * @return array
+ */
+ protected function tokenize()
+ {
+ static $CONTEXT_DOCBLOCK = 0x01;
+ static $CONTEXT_ASTERISK = 0x02;
+ static $CONTEXT_CLASS = 0x04;
+ static $CONTEXT_CONTENT = 0x08;
+
+ $context = 0x00;
+ $stream = $this->docComment;
+ $streamIndex = null;
+ $tokens = array();
+ $tokenIndex = null;
+ $currentChar = null;
+ $currentWord = null;
+ $currentLine = null;
+
+ $annotationParentCount = 0;
+
+ $MACRO_STREAM_ADVANCE_CHAR = function ($positionsForward = 1) use (&$stream, &$streamIndex, &$currentChar, &$currentWord, &$currentLine) {
+ $positionsForward = ($positionsForward > 0) ? $positionsForward : 1;
+ $streamIndex = ($streamIndex === null) ? 0 : $streamIndex + $positionsForward;
+ if (!isset($stream[$streamIndex])) {
+ $currentChar = false;
+
+ return false;
+ }
+ $currentChar = $stream[$streamIndex];
+ $matches = array();
+ $currentLine = (preg_match('#(.*)\n#', $stream, $matches, null, $streamIndex) === 1) ? $matches[1] : substr($stream, $streamIndex);
+ if ($currentChar === ' ') {
+ $currentWord = (preg_match('#( +)#', $currentLine, $matches) === 1) ? $matches[1] : $currentLine;
+ } else {
+ $currentWord = (($matches = strpos($currentLine, ' ')) !== false) ? substr($currentLine, 0, $matches) : $currentLine;
+ }
+
+ return $currentChar;
+ };
+ $MACRO_STREAM_ADVANCE_WORD = function () use (&$currentWord, &$MACRO_STREAM_ADVANCE_CHAR) {
+ return $MACRO_STREAM_ADVANCE_CHAR(strlen($currentWord));
+ };
+ $MACRO_STREAM_ADVANCE_LINE = function () use (&$currentLine, &$MACRO_STREAM_ADVANCE_CHAR) {
+ return $MACRO_STREAM_ADVANCE_CHAR(strlen($currentLine));
+ };
+ $MACRO_TOKEN_ADVANCE = function () use (&$tokenIndex, &$tokens) {
+ $tokenIndex = ($tokenIndex === null) ? 0 : $tokenIndex + 1;
+ $tokens[$tokenIndex] = array('ANNOTATION_UNKNOWN', '');
+ };
+ $MACRO_TOKEN_SET_TYPE = function ($type) use (&$tokenIndex, &$tokens) {
+ $tokens[$tokenIndex][0] = $type;
+ };
+ $MACRO_TOKEN_APPEND_CHAR = function () use (&$currentChar, &$tokens, &$tokenIndex) {
+ $tokens[$tokenIndex][1] .= $currentChar;
+ };
+ $MACRO_TOKEN_APPEND_WORD = function () use (&$currentWord, &$tokens, &$tokenIndex) {
+ $tokens[$tokenIndex][1] .= $currentWord;
+ };
+ $MACRO_TOKEN_APPEND_LINE = function () use (&$currentLine, &$tokens, &$tokenIndex) {
+ $tokens[$tokenIndex][1] .= $currentLine;
+ };
+ $MACRO_HAS_CONTEXT = function ($which) use (&$context) {
+ return (($context & $which) === $which);
+ };
+
+ $MACRO_STREAM_ADVANCE_CHAR();
+ $MACRO_TOKEN_ADVANCE();
+
+ TOKENIZER_TOP:
+
+ if ($context === 0x00 && $currentChar === '/' && $currentWord === '/**') {
+ $MACRO_TOKEN_SET_TYPE('ANNOTATION_COMMENTSTART');
+ $MACRO_TOKEN_APPEND_WORD();
+ $MACRO_TOKEN_ADVANCE();
+ $context |= $CONTEXT_DOCBLOCK;
+ $context |= $CONTEXT_ASTERISK;
+ if ($MACRO_STREAM_ADVANCE_WORD() === false) {
+ goto TOKENIZER_END;
+ }
+ goto TOKENIZER_TOP;
+ }
+
+ if ($MACRO_HAS_CONTEXT($CONTEXT_CLASS)) {
+ if (in_array($currentChar, array(' ', '(', "\n"))) {
+ $context &= ~$CONTEXT_CLASS;
+ $MACRO_TOKEN_ADVANCE();
+ } else {
+ $MACRO_TOKEN_APPEND_CHAR();
+ if ($MACRO_STREAM_ADVANCE_CHAR() === false) {
+ goto TOKENIZER_END;
+ }
+ goto TOKENIZER_TOP;
+ }
+ }
+
+ if ($currentChar === "\n") {
+ $MACRO_TOKEN_SET_TYPE('ANNOTATION_NEWLINE');
+ $MACRO_TOKEN_APPEND_CHAR();
+ $MACRO_TOKEN_ADVANCE();
+ $context &= ~$CONTEXT_ASTERISK;
+ $context &= ~$CONTEXT_CLASS;
+ if ($MACRO_STREAM_ADVANCE_CHAR() === false) {
+ goto TOKENIZER_END;
+ }
+ goto TOKENIZER_TOP;
+ }
+
+ if ($currentChar === ' ') {
+ $MACRO_TOKEN_SET_TYPE(($MACRO_HAS_CONTEXT($CONTEXT_ASTERISK)) ? 'ANNOTATION_WHITESPACE' : 'ANNOTATION_WHITESPACE_INDENT');
+ $MACRO_TOKEN_APPEND_WORD();
+ $MACRO_TOKEN_ADVANCE();
+ if ($MACRO_STREAM_ADVANCE_WORD() === false) {
+ goto TOKENIZER_END;
+ }
+ goto TOKENIZER_TOP;
+ }
+
+ if ($MACRO_HAS_CONTEXT($CONTEXT_CONTENT) && $MACRO_HAS_CONTEXT($CONTEXT_ASTERISK)) {
+ $MACRO_TOKEN_SET_TYPE('ANNOTATION_CONTENT');
+ $annotationParentCount += substr_count($currentWord, '(');
+ $annotationParentCount -= substr_count($currentWord, ')');
+
+ if ($annotationParentCount === 0) {
+ $context &= ~$CONTEXT_CONTENT;
+ $MACRO_TOKEN_SET_TYPE('ANNOTATION_CONTENT_END');
+ }
+ $MACRO_TOKEN_APPEND_WORD();
+ $MACRO_TOKEN_ADVANCE();
+ if ($MACRO_STREAM_ADVANCE_WORD() === false) {
+ goto TOKENIZER_END;
+ }
+ goto TOKENIZER_TOP;
+ }
+
+ if ($currentChar === '(' && $tokens[$tokenIndex - 1][0] === 'ANNOTATION_CLASS') {
+ $context |= $CONTEXT_CONTENT;
+ $annotationParentCount = 1;
+ $MACRO_TOKEN_SET_TYPE('ANNOTATION_CONTENT_START');
+ $MACRO_TOKEN_APPEND_CHAR();
+ $MACRO_TOKEN_ADVANCE();
+ if ($MACRO_STREAM_ADVANCE_CHAR() === false) {
+ goto TOKENIZER_END;
+ }
+ goto TOKENIZER_TOP;
+ }
+
+ if ($MACRO_HAS_CONTEXT($CONTEXT_DOCBLOCK) && $currentWord === '*/') {
+ $MACRO_TOKEN_SET_TYPE('ANNOTATION_COMMENTEND');
+ $MACRO_TOKEN_APPEND_WORD();
+ $MACRO_TOKEN_ADVANCE();
+ $context &= ~$CONTEXT_DOCBLOCK;
+ if ($MACRO_STREAM_ADVANCE_WORD() === false) {
+ goto TOKENIZER_END;
+ }
+ goto TOKENIZER_TOP;
+ }
+
+ if ($currentChar === '*') {
+ if ($MACRO_HAS_CONTEXT($CONTEXT_DOCBLOCK) && ($MACRO_HAS_CONTEXT($CONTEXT_ASTERISK))) {
+ $MACRO_TOKEN_SET_TYPE('ANNOTATION_IGNORE');
+ } else {
+ $MACRO_TOKEN_SET_TYPE('ANNOTATION_ASTERISK');
+ $context |= $CONTEXT_ASTERISK;
+ }
+ $MACRO_TOKEN_APPEND_CHAR();
+ $MACRO_TOKEN_ADVANCE();
+ if ($MACRO_STREAM_ADVANCE_CHAR() === false) {
+ goto TOKENIZER_END;
+ }
+ goto TOKENIZER_TOP;
+ }
+
+ if ($currentChar === '@') {
+ $MACRO_TOKEN_SET_TYPE('ANNOTATION_CLASS');
+ $context |= $CONTEXT_CLASS;
+ $MACRO_TOKEN_APPEND_CHAR();
+ if ($MACRO_STREAM_ADVANCE_CHAR() === false) {
+ goto TOKENIZER_END;
+ }
+ goto TOKENIZER_TOP;
+ }
+
+ TOKENIZER_CONTINUE:
+
+ if ($context && $CONTEXT_CONTENT) {
+ $MACRO_TOKEN_APPEND_CHAR();
+ if ($MACRO_STREAM_ADVANCE_CHAR() === false) {
+ goto TOKENIZER_END;
+ }
+ } else {
+ $MACRO_TOKEN_SET_TYPE('ANNOTATION_IGNORE');
+ $MACRO_TOKEN_APPEND_LINE();
+ $MACRO_TOKEN_ADVANCE();
+ if ($MACRO_STREAM_ADVANCE_LINE() === false) {
+ goto TOKENIZER_END;
+ }
+ }
+ goto TOKENIZER_TOP;
+
+ TOKENIZER_END:
+
+ array_pop($tokens);
+
+ return $tokens;
+ }
+}
diff --git a/library/Zend/Code/Scanner/CachingFileScanner.php b/library/Zend/Code/Scanner/CachingFileScanner.php
new file mode 100755
index 0000000000..cb72867046
--- /dev/null
+++ b/library/Zend/Code/Scanner/CachingFileScanner.php
@@ -0,0 +1,160 @@
+fileScanner = static::$cache[$cacheId];
+ } else {
+ $this->fileScanner = new FileScanner($file, $annotationManager);
+ static::$cache[$cacheId] = $this->fileScanner;
+ }
+ }
+
+ /**
+ * @return void
+ */
+ public static function clearCache()
+ {
+ static::$cache = array();
+ }
+
+ /**
+ * @return AnnotationManager
+ */
+ public function getAnnotationManager()
+ {
+ return $this->fileScanner->getAnnotationManager();
+ }
+
+ /**
+ * @return array|null|string
+ */
+ public function getFile()
+ {
+ return $this->fileScanner->getFile();
+ }
+
+ /**
+ * @return null|string
+ */
+ public function getDocComment()
+ {
+ return $this->fileScanner->getDocComment();
+ }
+
+ /**
+ * @return array
+ */
+ public function getNamespaces()
+ {
+ return $this->fileScanner->getNamespaces();
+ }
+
+ /**
+ * @param null|string $namespace
+ * @return array|null
+ */
+ public function getUses($namespace = null)
+ {
+ return $this->fileScanner->getUses($namespace);
+ }
+
+ /**
+ * @return array
+ */
+ public function getIncludes()
+ {
+ return $this->fileScanner->getIncludes();
+ }
+
+ /**
+ * @return array
+ */
+ public function getClassNames()
+ {
+ return $this->fileScanner->getClassNames();
+ }
+
+ /**
+ * @return array
+ */
+ public function getClasses()
+ {
+ return $this->fileScanner->getClasses();
+ }
+
+ /**
+ * @param int|string $className
+ * @return ClassScanner
+ */
+ public function getClass($className)
+ {
+ return $this->fileScanner->getClass($className);
+ }
+
+ /**
+ * @param string $className
+ * @return bool|null|NameInformation
+ */
+ public function getClassNameInformation($className)
+ {
+ return $this->fileScanner->getClassNameInformation($className);
+ }
+
+ /**
+ * @return array
+ */
+ public function getFunctionNames()
+ {
+ return $this->fileScanner->getFunctionNames();
+ }
+
+ /**
+ * @return array
+ */
+ public function getFunctions()
+ {
+ return $this->fileScanner->getFunctions();
+ }
+}
diff --git a/library/Zend/Code/Scanner/ClassScanner.php b/library/Zend/Code/Scanner/ClassScanner.php
new file mode 100755
index 0000000000..2efc5539c1
--- /dev/null
+++ b/library/Zend/Code/Scanner/ClassScanner.php
@@ -0,0 +1,972 @@
+tokens = $classTokens;
+ $this->nameInformation = $nameInformation;
+ }
+
+ /**
+ * Get annotations
+ *
+ * @param Annotation\AnnotationManager $annotationManager
+ * @return Annotation\AnnotationCollection
+ */
+ public function getAnnotations(Annotation\AnnotationManager $annotationManager)
+ {
+ if (($docComment = $this->getDocComment()) == '') {
+ return false;
+ }
+
+ return new AnnotationScanner($annotationManager, $docComment, $this->nameInformation);
+ }
+
+ /**
+ * Return documentation comment
+ *
+ * @return null|string
+ */
+ public function getDocComment()
+ {
+ $this->scan();
+
+ return $this->docComment;
+ }
+
+ /**
+ * Return documentation block
+ *
+ * @return false|DocBlockScanner
+ */
+ public function getDocBlock()
+ {
+ if (!$docComment = $this->getDocComment()) {
+ return false;
+ }
+
+ return new DocBlockScanner($docComment);
+ }
+
+ /**
+ * Return a name of class
+ *
+ * @return null|string
+ */
+ public function getName()
+ {
+ $this->scan();
+ return $this->name;
+ }
+
+ /**
+ * Return short name of class
+ *
+ * @return null|string
+ */
+ public function getShortName()
+ {
+ $this->scan();
+ return $this->shortName;
+ }
+
+ /**
+ * Return number of first line
+ *
+ * @return int|null
+ */
+ public function getLineStart()
+ {
+ $this->scan();
+ return $this->lineStart;
+ }
+
+ /**
+ * Return number of last line
+ *
+ * @return int|null
+ */
+ public function getLineEnd()
+ {
+ $this->scan();
+ return $this->lineEnd;
+ }
+
+ /**
+ * Verify if class is final
+ *
+ * @return bool
+ */
+ public function isFinal()
+ {
+ $this->scan();
+ return $this->isFinal;
+ }
+
+ /**
+ * Verify if class is instantiable
+ *
+ * @return bool
+ */
+ public function isInstantiable()
+ {
+ $this->scan();
+ return (!$this->isAbstract && !$this->isInterface);
+ }
+
+ /**
+ * Verify if class is an abstract class
+ *
+ * @return bool
+ */
+ public function isAbstract()
+ {
+ $this->scan();
+ return $this->isAbstract;
+ }
+
+ /**
+ * Verify if class is an interface
+ *
+ * @return bool
+ */
+ public function isInterface()
+ {
+ $this->scan();
+ return $this->isInterface;
+ }
+
+ /**
+ * Verify if class has parent
+ *
+ * @return bool
+ */
+ public function hasParentClass()
+ {
+ $this->scan();
+ return ($this->parentClass != null);
+ }
+
+ /**
+ * Return a name of parent class
+ *
+ * @return null|string
+ */
+ public function getParentClass()
+ {
+ $this->scan();
+ return $this->parentClass;
+ }
+
+ /**
+ * Return a list of interface names
+ *
+ * @return array
+ */
+ public function getInterfaces()
+ {
+ $this->scan();
+ return $this->interfaces;
+ }
+
+ /**
+ * Return a list of constant names
+ *
+ * @return array
+ */
+ public function getConstantNames()
+ {
+ $this->scan();
+
+ $return = array();
+ foreach ($this->infos as $info) {
+ if ($info['type'] != 'constant') {
+ continue;
+ }
+
+ $return[] = $info['name'];
+ }
+
+ return $return;
+ }
+
+ /**
+ * Return a list of constants
+ *
+ * @param bool $namesOnly Set false to return instances of ConstantScanner
+ * @return array|ConstantScanner[]
+ */
+ public function getConstants($namesOnly = true)
+ {
+ if (true === $namesOnly) {
+ trigger_error('Use method getConstantNames() instead', E_USER_DEPRECATED);
+ return $this->getConstantNames();
+ }
+
+ $this->scan();
+
+ $return = array();
+ foreach ($this->infos as $info) {
+ if ($info['type'] != 'constant') {
+ continue;
+ }
+
+ $return[] = $this->getConstant($info['name']);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Return a single constant by given name or index of info
+ *
+ * @param string|int $constantNameOrInfoIndex
+ * @throws Exception\InvalidArgumentException
+ * @return bool|ConstantScanner
+ */
+ public function getConstant($constantNameOrInfoIndex)
+ {
+ $this->scan();
+
+ if (is_int($constantNameOrInfoIndex)) {
+ $info = $this->infos[$constantNameOrInfoIndex];
+ if ($info['type'] != 'constant') {
+ throw new Exception\InvalidArgumentException('Index of info offset is not about a constant');
+ }
+ } elseif (is_string($constantNameOrInfoIndex)) {
+ $constantFound = false;
+ foreach ($this->infos as $info) {
+ if ($info['type'] === 'constant' && $info['name'] === $constantNameOrInfoIndex) {
+ $constantFound = true;
+ break;
+ }
+ }
+ if (!$constantFound) {
+ return false;
+ }
+ } else {
+ throw new Exception\InvalidArgumentException('Invalid constant name of info index type. Must be of type int or string');
+ }
+ if (!isset($info)) {
+ return false;
+ }
+ $p = new ConstantScanner(
+ array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart'] + 1),
+ $this->nameInformation
+ );
+ $p->setClass($this->name);
+ $p->setScannerClass($this);
+ return $p;
+ }
+
+ /**
+ * Verify if class has constant
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function hasConstant($name)
+ {
+ $this->scan();
+
+ foreach ($this->infos as $info) {
+ if ($info['type'] === 'constant' && $info['name'] === $name) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Return a list of property names
+ *
+ * @return array
+ */
+ public function getPropertyNames()
+ {
+ $this->scan();
+
+ $return = array();
+ foreach ($this->infos as $info) {
+ if ($info['type'] != 'property') {
+ continue;
+ }
+
+ $return[] = $info['name'];
+ }
+
+ return $return;
+ }
+
+ /**
+ * Return a list of properties
+ *
+ * @return PropertyScanner
+ */
+ public function getProperties()
+ {
+ $this->scan();
+
+ $return = array();
+ foreach ($this->infos as $info) {
+ if ($info['type'] != 'property') {
+ continue;
+ }
+
+ $return[] = $this->getProperty($info['name']);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Return a single property by given name or index of info
+ *
+ * @param string|int $propertyNameOrInfoIndex
+ * @throws Exception\InvalidArgumentException
+ * @return bool|PropertyScanner
+ */
+ public function getProperty($propertyNameOrInfoIndex)
+ {
+ $this->scan();
+
+ if (is_int($propertyNameOrInfoIndex)) {
+ $info = $this->infos[$propertyNameOrInfoIndex];
+ if ($info['type'] != 'property') {
+ throw new Exception\InvalidArgumentException('Index of info offset is not about a property');
+ }
+ } elseif (is_string($propertyNameOrInfoIndex)) {
+ $propertyFound = false;
+ foreach ($this->infos as $info) {
+ if ($info['type'] === 'property' && $info['name'] === $propertyNameOrInfoIndex) {
+ $propertyFound = true;
+ break;
+ }
+ }
+ if (!$propertyFound) {
+ return false;
+ }
+ } else {
+ throw new Exception\InvalidArgumentException('Invalid property name of info index type. Must be of type int or string');
+ }
+ if (!isset($info)) {
+ return false;
+ }
+ $p = new PropertyScanner(
+ array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart'] + 1),
+ $this->nameInformation
+ );
+ $p->setClass($this->name);
+ $p->setScannerClass($this);
+ return $p;
+ }
+
+ /**
+ * Verify if class has property
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function hasProperty($name)
+ {
+ $this->scan();
+
+ foreach ($this->infos as $info) {
+ if ($info['type'] === 'property' && $info['name'] === $name) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Return a list of method names
+ *
+ * @return array
+ */
+ public function getMethodNames()
+ {
+ $this->scan();
+
+ $return = array();
+ foreach ($this->infos as $info) {
+ if ($info['type'] != 'method') {
+ continue;
+ }
+
+ $return[] = $info['name'];
+ }
+
+ return $return;
+ }
+
+ /**
+ * Return a list of methods
+ *
+ * @return MethodScanner[]
+ */
+ public function getMethods()
+ {
+ $this->scan();
+
+ $return = array();
+ foreach ($this->infos as $info) {
+ if ($info['type'] != 'method') {
+ continue;
+ }
+
+ $return[] = $this->getMethod($info['name']);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Return a single method by given name or index of info
+ *
+ * @param string|int $methodNameOrInfoIndex
+ * @throws Exception\InvalidArgumentException
+ * @return MethodScanner
+ */
+ public function getMethod($methodNameOrInfoIndex)
+ {
+ $this->scan();
+
+ if (is_int($methodNameOrInfoIndex)) {
+ $info = $this->infos[$methodNameOrInfoIndex];
+ if ($info['type'] != 'method') {
+ throw new Exception\InvalidArgumentException('Index of info offset is not about a method');
+ }
+ } elseif (is_string($methodNameOrInfoIndex)) {
+ $methodFound = false;
+ foreach ($this->infos as $info) {
+ if ($info['type'] === 'method' && $info['name'] === $methodNameOrInfoIndex) {
+ $methodFound = true;
+ break;
+ }
+ }
+ if (!$methodFound) {
+ return false;
+ }
+ }
+ if (!isset($info)) {
+ // @todo find a way to test this
+ die('Massive Failure, test this');
+ }
+
+ $m = new MethodScanner(
+ array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart'] + 1),
+ $this->nameInformation
+ );
+ $m->setClass($this->name);
+ $m->setScannerClass($this);
+
+ return $m;
+ }
+
+ /**
+ * Verify if class has method by given name
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function hasMethod($name)
+ {
+ $this->scan();
+
+ foreach ($this->infos as $info) {
+ if ($info['type'] === 'method' && $info['name'] === $name) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static function export()
+ {
+ // @todo
+ }
+
+ public function __toString()
+ {
+ // @todo
+ }
+
+ /**
+ * Scan tokens
+ *
+ * @return void
+ * @throws Exception\RuntimeException
+ */
+ protected function scan()
+ {
+ if ($this->isScanned) {
+ return;
+ }
+
+ if (!$this->tokens) {
+ throw new Exception\RuntimeException('No tokens were provided');
+ }
+
+ /**
+ * Variables & Setup
+ */
+
+ $tokens = &$this->tokens; // localize
+ $infos = &$this->infos; // localize
+ $tokenIndex = null;
+ $token = null;
+ $tokenType = null;
+ $tokenContent = null;
+ $tokenLine = null;
+ $namespace = null;
+ $infoIndex = 0;
+ $braceCount = 0;
+
+ /*
+ * MACRO creation
+ */
+ $MACRO_TOKEN_ADVANCE = function () use (&$tokens, &$tokenIndex, &$token, &$tokenType, &$tokenContent, &$tokenLine) {
+ static $lastTokenArray = null;
+ $tokenIndex = ($tokenIndex === null) ? 0 : $tokenIndex + 1;
+ if (!isset($tokens[$tokenIndex])) {
+ $token = false;
+ $tokenContent = false;
+ $tokenType = false;
+ $tokenLine = false;
+
+ return false;
+ }
+ $token = $tokens[$tokenIndex];
+
+ if (is_string($token)) {
+ $tokenType = null;
+ $tokenContent = $token;
+ $tokenLine = $tokenLine + substr_count(
+ $lastTokenArray[1],
+ "\n"
+ ); // adjust token line by last known newline count
+ } else {
+ $lastTokenArray = $token;
+ list($tokenType, $tokenContent, $tokenLine) = $token;
+ }
+
+ return $tokenIndex;
+ };
+ $MACRO_INFO_ADVANCE = function () use (&$infoIndex, &$infos, &$tokenIndex, &$tokenLine) {
+ $infos[$infoIndex]['tokenEnd'] = $tokenIndex;
+ $infos[$infoIndex]['lineEnd'] = $tokenLine;
+ $infoIndex++;
+
+ return $infoIndex;
+ };
+
+ /**
+ * START FINITE STATE MACHINE FOR SCANNING TOKENS
+ */
+
+ // Initialize token
+ $MACRO_TOKEN_ADVANCE();
+
+ SCANNER_TOP:
+
+ switch ($tokenType) {
+
+ case T_DOC_COMMENT:
+
+ $this->docComment = $tokenContent;
+ goto SCANNER_CONTINUE;
+ //goto no break needed
+
+ case T_FINAL:
+ case T_ABSTRACT:
+ case T_CLASS:
+ case T_INTERFACE:
+
+ // CLASS INFORMATION
+
+ $classContext = null;
+ $classInterfaceIndex = 0;
+
+ SCANNER_CLASS_INFO_TOP:
+
+ if (is_string($tokens[$tokenIndex + 1]) && $tokens[$tokenIndex + 1] === '{') {
+ goto SCANNER_CLASS_INFO_END;
+ }
+
+ $this->lineStart = $tokenLine;
+
+ switch ($tokenType) {
+
+ case T_FINAL:
+ $this->isFinal = true;
+ goto SCANNER_CLASS_INFO_CONTINUE;
+ //goto no break needed
+
+ case T_ABSTRACT:
+ $this->isAbstract = true;
+ goto SCANNER_CLASS_INFO_CONTINUE;
+ //goto no break needed
+
+ case T_INTERFACE:
+ $this->isInterface = true;
+ //fall-through
+ case T_CLASS:
+ $this->shortName = $tokens[$tokenIndex + 2][1];
+ if ($this->nameInformation && $this->nameInformation->hasNamespace()) {
+ $this->name = $this->nameInformation->getNamespace() . '\\' . $this->shortName;
+ } else {
+ $this->name = $this->shortName;
+ }
+ goto SCANNER_CLASS_INFO_CONTINUE;
+ //goto no break needed
+
+ case T_NS_SEPARATOR:
+ case T_STRING:
+ switch ($classContext) {
+ case T_EXTENDS:
+ $this->shortParentClass .= $tokenContent;
+ break;
+ case T_IMPLEMENTS:
+ $this->shortInterfaces[$classInterfaceIndex] .= $tokenContent;
+ break;
+ }
+ goto SCANNER_CLASS_INFO_CONTINUE;
+ //goto no break needed
+
+ case T_EXTENDS:
+ case T_IMPLEMENTS:
+ $classContext = $tokenType;
+ if (($this->isInterface && $classContext === T_EXTENDS) || $classContext === T_IMPLEMENTS) {
+ $this->shortInterfaces[$classInterfaceIndex] = '';
+ } elseif (!$this->isInterface && $classContext === T_EXTENDS) {
+ $this->shortParentClass = '';
+ }
+ goto SCANNER_CLASS_INFO_CONTINUE;
+ //goto no break needed
+
+ case null:
+ if ($classContext == T_IMPLEMENTS && $tokenContent == ',') {
+ $classInterfaceIndex++;
+ $this->shortInterfaces[$classInterfaceIndex] = '';
+ }
+
+ }
+
+ SCANNER_CLASS_INFO_CONTINUE:
+
+ if ($MACRO_TOKEN_ADVANCE() === false) {
+ goto SCANNER_END;
+ }
+ goto SCANNER_CLASS_INFO_TOP;
+
+ SCANNER_CLASS_INFO_END:
+
+ goto SCANNER_CONTINUE;
+
+ }
+
+ if ($tokenType === null && $tokenContent === '{' && $braceCount === 0) {
+ $braceCount++;
+ if ($MACRO_TOKEN_ADVANCE() === false) {
+ goto SCANNER_END;
+ }
+
+ SCANNER_CLASS_BODY_TOP:
+
+ if ($braceCount === 0) {
+ goto SCANNER_CLASS_BODY_END;
+ }
+
+ switch ($tokenType) {
+
+ case T_CONST:
+
+ $infos[$infoIndex] = array(
+ 'type' => 'constant',
+ 'tokenStart' => $tokenIndex,
+ 'tokenEnd' => null,
+ 'lineStart' => $tokenLine,
+ 'lineEnd' => null,
+ 'name' => null,
+ 'value' => null,
+ );
+
+ SCANNER_CLASS_BODY_CONST_TOP:
+
+ if ($tokenContent === ';') {
+ goto SCANNER_CLASS_BODY_CONST_END;
+ }
+
+ if ($tokenType === T_STRING && null === $infos[$infoIndex]['name']) {
+ $infos[$infoIndex]['name'] = $tokenContent;
+ }
+
+ SCANNER_CLASS_BODY_CONST_CONTINUE:
+
+ if ($MACRO_TOKEN_ADVANCE() === false) {
+ goto SCANNER_END;
+ }
+ goto SCANNER_CLASS_BODY_CONST_TOP;
+
+ SCANNER_CLASS_BODY_CONST_END:
+
+ $MACRO_INFO_ADVANCE();
+ goto SCANNER_CLASS_BODY_CONTINUE;
+ //goto no break needed
+
+ case T_DOC_COMMENT:
+ case T_PUBLIC:
+ case T_PROTECTED:
+ case T_PRIVATE:
+ case T_ABSTRACT:
+ case T_FINAL:
+ case T_VAR:
+ case T_FUNCTION:
+
+ $infos[$infoIndex] = array(
+ 'type' => null,
+ 'tokenStart' => $tokenIndex,
+ 'tokenEnd' => null,
+ 'lineStart' => $tokenLine,
+ 'lineEnd' => null,
+ 'name' => null,
+ );
+
+ $memberContext = null;
+ $methodBodyStarted = false;
+
+ SCANNER_CLASS_BODY_MEMBER_TOP:
+
+ if ($memberContext === 'method') {
+ switch ($tokenContent) {
+ case '{':
+ $methodBodyStarted = true;
+ $braceCount++;
+ goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
+ //goto no break needed
+ case '}':
+ $braceCount--;
+ goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
+
+ case ';':
+ $infos[$infoIndex]['tokenEnd'] = $tokenIndex;
+ goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
+ }
+ }
+
+ if ($memberContext !== null) {
+ if (
+ ($memberContext === 'property' && $tokenContent === ';')
+ || ($memberContext === 'method' && $methodBodyStarted && $braceCount === 1)
+ || ($memberContext === 'method' && $this->isInterface && $tokenContent === ';')
+ ) {
+ goto SCANNER_CLASS_BODY_MEMBER_END;
+ }
+ }
+
+ switch ($tokenType) {
+
+ case T_CONST:
+ $memberContext = 'constant';
+ $infos[$infoIndex]['type'] = 'constant';
+ goto SCANNER_CLASS_BODY_CONST_CONTINUE;
+ //goto no break needed
+
+ case T_VARIABLE:
+ if ($memberContext === null) {
+ $memberContext = 'property';
+ $infos[$infoIndex]['type'] = 'property';
+ $infos[$infoIndex]['name'] = ltrim($tokenContent, '$');
+ }
+ goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
+ //goto no break needed
+
+ case T_FUNCTION:
+ $memberContext = 'method';
+ $infos[$infoIndex]['type'] = 'method';
+ goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
+ //goto no break needed
+
+ case T_STRING:
+ if ($memberContext === 'method' && null === $infos[$infoIndex]['name']) {
+ $infos[$infoIndex]['name'] = $tokenContent;
+ }
+ goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
+ //goto no break needed
+ }
+
+ SCANNER_CLASS_BODY_MEMBER_CONTINUE:
+
+ if ($MACRO_TOKEN_ADVANCE() === false) {
+ goto SCANNER_END;
+ }
+ goto SCANNER_CLASS_BODY_MEMBER_TOP;
+
+ SCANNER_CLASS_BODY_MEMBER_END:
+
+ $memberContext = null;
+ $MACRO_INFO_ADVANCE();
+ goto SCANNER_CLASS_BODY_CONTINUE;
+ //goto no break needed
+
+ case null: // no type, is a string
+
+ switch ($tokenContent) {
+ case '{':
+ $braceCount++;
+ goto SCANNER_CLASS_BODY_CONTINUE;
+ //fall-through
+ case '}':
+ $braceCount--;
+ goto SCANNER_CLASS_BODY_CONTINUE;
+ }
+ }
+
+ SCANNER_CLASS_BODY_CONTINUE:
+
+ if ($braceCount === 0 || $MACRO_TOKEN_ADVANCE() === false) {
+ goto SCANNER_CONTINUE;
+ }
+ goto SCANNER_CLASS_BODY_TOP;
+
+ SCANNER_CLASS_BODY_END:
+
+ goto SCANNER_CONTINUE;
+ }
+
+ SCANNER_CONTINUE:
+
+ if ($tokenContent === '}') {
+ $this->lineEnd = $tokenLine;
+ }
+
+ if ($MACRO_TOKEN_ADVANCE() === false) {
+ goto SCANNER_END;
+ }
+ goto SCANNER_TOP;
+
+ SCANNER_END:
+
+ // process short names
+ if ($this->nameInformation) {
+ if ($this->shortParentClass) {
+ $this->parentClass = $this->nameInformation->resolveName($this->shortParentClass);
+ }
+ if ($this->shortInterfaces) {
+ foreach ($this->shortInterfaces as $siIndex => $si) {
+ $this->interfaces[$siIndex] = $this->nameInformation->resolveName($si);
+ }
+ }
+ } else {
+ $this->parentClass = $this->shortParentClass;
+ $this->interfaces = $this->shortInterfaces;
+ }
+
+ $this->isScanned = true;
+
+ return;
+ }
+}
diff --git a/library/Zend/Code/Scanner/ConstantScanner.php b/library/Zend/Code/Scanner/ConstantScanner.php
new file mode 100755
index 0000000000..c37c7a22c6
--- /dev/null
+++ b/library/Zend/Code/Scanner/ConstantScanner.php
@@ -0,0 +1,236 @@
+tokens = $constantTokens;
+ $this->nameInformation = $nameInformation;
+ }
+
+ /**
+ * @param string $class
+ */
+ public function setClass($class)
+ {
+ $this->class = $class;
+ }
+
+ /**
+ * @param ClassScanner $scannerClass
+ */
+ public function setScannerClass(ClassScanner $scannerClass)
+ {
+ $this->scannerClass = $scannerClass;
+ }
+
+ /**
+ * @return ClassScanner
+ */
+ public function getClassScanner()
+ {
+ return $this->scannerClass;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ $this->scan();
+ return $this->name;
+ }
+
+ /**
+ * @return string
+ */
+ public function getValue()
+ {
+ $this->scan();
+ return $this->value;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDocComment()
+ {
+ $this->scan();
+ return $this->docComment;
+ }
+
+ /**
+ * @param Annotation\AnnotationManager $annotationManager
+ * @return AnnotationScanner
+ */
+ public function getAnnotations(Annotation\AnnotationManager $annotationManager)
+ {
+ if (($docComment = $this->getDocComment()) == '') {
+ return false;
+ }
+
+ return new AnnotationScanner($annotationManager, $docComment, $this->nameInformation);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ $this->scan();
+ return var_export($this, true);
+ }
+
+ /**
+ * Scan tokens
+ *
+ * @throws Exception\RuntimeException
+ */
+ protected function scan()
+ {
+ if ($this->isScanned) {
+ return;
+ }
+
+ if (!$this->tokens) {
+ throw new Exception\RuntimeException('No tokens were provided');
+ }
+
+ /**
+ * Variables & Setup
+ */
+ $tokens = &$this->tokens;
+
+ reset($tokens);
+
+ SCANNER_TOP:
+
+ $token = current($tokens);
+
+ if (!is_string($token)) {
+ list($tokenType, $tokenContent, $tokenLine) = $token;
+
+ switch ($tokenType) {
+ case T_DOC_COMMENT:
+ if ($this->docComment === null && $this->name === null) {
+ $this->docComment = $tokenContent;
+ }
+ goto SCANNER_CONTINUE;
+ // fall-through
+
+ case T_STRING:
+ $string = (is_string($token)) ? $token : $tokenContent;
+
+ if (null === $this->name) {
+ $this->name = $string;
+ } else {
+ if ('self' == strtolower($string)) {
+ list($tokenNextType, $tokenNextContent, $tokenNextLine) = next($tokens);
+
+ if ('::' == $tokenNextContent) {
+ list($tokenNextType, $tokenNextContent, $tokenNextLine) = next($tokens);
+
+ if ($this->getClassScanner()->getConstant($tokenNextContent)) {
+ $this->value = $this->getClassScanner()->getConstant($tokenNextContent)->getValue();
+ }
+ }
+ }
+ }
+
+ goto SCANNER_CONTINUE;
+ // fall-through
+
+ case T_CONSTANT_ENCAPSED_STRING:
+ case T_DNUMBER:
+ case T_LNUMBER:
+ $string = (is_string($token)) ? $token : $tokenContent;
+
+ if (substr($string, 0, 1) === '"' || substr($string, 0, 1) === "'") {
+ $this->value = substr($string, 1, -1); // Remove quotes
+ } else {
+ $this->value = $string;
+ }
+ goto SCANNER_CONTINUE;
+ // fall-trough
+
+ default:
+ goto SCANNER_CONTINUE;
+ }
+ }
+
+ SCANNER_CONTINUE:
+
+ if (next($this->tokens) === false) {
+ goto SCANNER_END;
+ }
+ goto SCANNER_TOP;
+
+ SCANNER_END:
+
+ $this->isScanned = true;
+ }
+}
diff --git a/library/Zend/Code/Scanner/DerivedClassScanner.php b/library/Zend/Code/Scanner/DerivedClassScanner.php
new file mode 100755
index 0000000000..6c8463ede2
--- /dev/null
+++ b/library/Zend/Code/Scanner/DerivedClassScanner.php
@@ -0,0 +1,381 @@
+classScanner = $classScanner;
+ $this->directoryScanner = $directoryScanner;
+
+ $currentScannerClass = $classScanner;
+
+ while ($currentScannerClass && $currentScannerClass->hasParentClass()) {
+ $currentParentClassName = $currentScannerClass->getParentClass();
+ if ($directoryScanner->hasClass($currentParentClassName)) {
+ $currentParentClass = $directoryScanner->getClass($currentParentClassName);
+ $this->parentClassScanners[$currentParentClassName] = $currentParentClass;
+ $currentScannerClass = $currentParentClass;
+ } else {
+ $currentScannerClass = false;
+ }
+ }
+
+ foreach ($interfaces = $this->classScanner->getInterfaces() as $iName) {
+ if ($directoryScanner->hasClass($iName)) {
+ $this->interfaceClassScanners[$iName] = $directoryScanner->getClass($iName);
+ }
+ }
+ }
+
+ /**
+ * @return null|string
+ */
+ public function getName()
+ {
+ return $this->classScanner->getName();
+ }
+
+ /**
+ * @return null|string
+ */
+ public function getShortName()
+ {
+ return $this->classScanner->getShortName();
+ }
+
+ /**
+ * @return bool
+ */
+ public function isInstantiable()
+ {
+ return $this->classScanner->isInstantiable();
+ }
+
+ /**
+ * @return bool
+ */
+ public function isFinal()
+ {
+ return $this->classScanner->isFinal();
+ }
+
+ /**
+ * @return bool
+ */
+ public function isAbstract()
+ {
+ return $this->classScanner->isAbstract();
+ }
+
+ /**
+ * @return bool
+ */
+ public function isInterface()
+ {
+ return $this->classScanner->isInterface();
+ }
+
+ /**
+ * @return array
+ */
+ public function getParentClasses()
+ {
+ return array_keys($this->parentClassScanners);
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasParentClass()
+ {
+ return ($this->classScanner->getParentClass() != null);
+ }
+
+ /**
+ * @return null|string
+ */
+ public function getParentClass()
+ {
+ return $this->classScanner->getParentClass();
+ }
+
+ /**
+ * @param bool $returnClassScanners
+ * @return array
+ */
+ public function getInterfaces($returnClassScanners = false)
+ {
+ if ($returnClassScanners) {
+ return $this->interfaceClassScanners;
+ }
+
+ $interfaces = $this->classScanner->getInterfaces();
+ foreach ($this->parentClassScanners as $pClassScanner) {
+ $interfaces = array_merge($interfaces, $pClassScanner->getInterfaces());
+ }
+
+ return $interfaces;
+ }
+
+ /**
+ * Return a list of constant names
+ *
+ * @return array
+ */
+ public function getConstantNames()
+ {
+ $constants = $this->classScanner->getConstantNames();
+ foreach ($this->parentClassScanners as $pClassScanner) {
+ $constants = array_merge($constants, $pClassScanner->getConstantNames());
+ }
+
+ return $constants;
+ }
+
+ /**
+ * Return a list of constants
+ *
+ * @param bool $namesOnly Set false to return instances of ConstantScanner
+ * @return array|ConstantScanner[]
+ */
+ public function getConstants($namesOnly = true)
+ {
+ if (true === $namesOnly) {
+ trigger_error('Use method getConstantNames() instead', E_USER_DEPRECATED);
+ return $this->getConstantNames();
+ }
+
+ $constants = $this->classScanner->getConstants();
+ foreach ($this->parentClassScanners as $pClassScanner) {
+ $constants = array_merge($constants, $pClassScanner->getConstants($namesOnly));
+ }
+
+ return $constants;
+ }
+
+ /**
+ * Return a single constant by given name or index of info
+ *
+ * @param string|int $constantNameOrInfoIndex
+ * @throws Exception\InvalidArgumentException
+ * @return bool|ConstantScanner
+ */
+ public function getConstant($constantNameOrInfoIndex)
+ {
+ if ($this->classScanner->hasConstant($constantNameOrInfoIndex)) {
+ return $this->classScanner->getConstant($constantNameOrInfoIndex);
+ }
+
+ foreach ($this->parentClassScanners as $pClassScanner) {
+ if ($pClassScanner->hasConstant($constantNameOrInfoIndex)) {
+ return $pClassScanner->getConstant($constantNameOrInfoIndex);
+ }
+ }
+
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Constant %s not found in %s',
+ $constantNameOrInfoIndex,
+ $this->classScanner->getName()
+ ));
+ }
+
+ /**
+ * Verify if class or parent class has constant
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function hasConstant($name)
+ {
+ if ($this->classScanner->hasConstant($name)) {
+ return true;
+ }
+ foreach ($this->parentClassScanners as $pClassScanner) {
+ if ($pClassScanner->hasConstant($name)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Return a list of property names
+ *
+ * @return array
+ */
+ public function getPropertyNames()
+ {
+ $properties = $this->classScanner->getPropertyNames();
+ foreach ($this->parentClassScanners as $pClassScanner) {
+ $properties = array_merge($properties, $pClassScanner->getPropertyNames());
+ }
+
+ return $properties;
+ }
+
+ /**
+ * @param bool $returnScannerProperty
+ * @return array
+ */
+ public function getProperties($returnScannerProperty = false)
+ {
+ $properties = $this->classScanner->getProperties($returnScannerProperty);
+ foreach ($this->parentClassScanners as $pClassScanner) {
+ $properties = array_merge($properties, $pClassScanner->getProperties($returnScannerProperty));
+ }
+
+ return $properties;
+ }
+
+ /**
+ * Return a single property by given name or index of info
+ *
+ * @param string|int $propertyNameOrInfoIndex
+ * @throws Exception\InvalidArgumentException
+ * @return bool|PropertyScanner
+ */
+ public function getProperty($propertyNameOrInfoIndex)
+ {
+ if ($this->classScanner->hasProperty($propertyNameOrInfoIndex)) {
+ return $this->classScanner->getProperty($propertyNameOrInfoIndex);
+ }
+
+ foreach ($this->parentClassScanners as $pClassScanner) {
+ if ($pClassScanner->hasProperty($propertyNameOrInfoIndex)) {
+ return $pClassScanner->getProperty($propertyNameOrInfoIndex);
+ }
+ }
+
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Property %s not found in %s',
+ $propertyNameOrInfoIndex,
+ $this->classScanner->getName()
+ ));
+ }
+
+ /**
+ * Verify if class or parent class has property
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function hasProperty($name)
+ {
+ if ($this->classScanner->hasProperty($name)) {
+ return true;
+ }
+ foreach ($this->parentClassScanners as $pClassScanner) {
+ if ($pClassScanner->hasProperty($name)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @return array
+ */
+ public function getMethodNames()
+ {
+ $methods = $this->classScanner->getMethodNames();
+ foreach ($this->parentClassScanners as $pClassScanner) {
+ $methods = array_merge($methods, $pClassScanner->getMethodNames());
+ }
+
+ return $methods;
+ }
+
+ /**
+ * @return MethodScanner[]
+ */
+ public function getMethods()
+ {
+ $methods = $this->classScanner->getMethods();
+ foreach ($this->parentClassScanners as $pClassScanner) {
+ $methods = array_merge($methods, $pClassScanner->getMethods());
+ }
+
+ return $methods;
+ }
+
+ /**
+ * @param int|string $methodNameOrInfoIndex
+ * @return MethodScanner
+ * @throws Exception\InvalidArgumentException
+ */
+ public function getMethod($methodNameOrInfoIndex)
+ {
+ if ($this->classScanner->hasMethod($methodNameOrInfoIndex)) {
+ return $this->classScanner->getMethod($methodNameOrInfoIndex);
+ }
+
+ foreach ($this->parentClassScanners as $pClassScanner) {
+ if ($pClassScanner->hasMethod($methodNameOrInfoIndex)) {
+ return $pClassScanner->getMethod($methodNameOrInfoIndex);
+ }
+ }
+
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Method %s not found in %s',
+ $methodNameOrInfoIndex,
+ $this->classScanner->getName()
+ ));
+ }
+
+ /**
+ * Verify if class or parent class has method by given name
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function hasMethod($name)
+ {
+ if ($this->classScanner->hasMethod($name)) {
+ return true;
+ }
+ foreach ($this->parentClassScanners as $pClassScanner) {
+ if ($pClassScanner->hasMethod($name)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/library/Zend/Code/Scanner/DirectoryScanner.php b/library/Zend/Code/Scanner/DirectoryScanner.php
new file mode 100755
index 0000000000..b1a0223daa
--- /dev/null
+++ b/library/Zend/Code/Scanner/DirectoryScanner.php
@@ -0,0 +1,272 @@
+addDirectory($directory);
+ } elseif (is_array($directory)) {
+ foreach ($directory as $d) {
+ $this->addDirectory($d);
+ }
+ }
+ }
+ }
+
+ /**
+ * @param DirectoryScanner|string $directory
+ * @return void
+ * @throws Exception\InvalidArgumentException
+ */
+ public function addDirectory($directory)
+ {
+ if ($directory instanceof DirectoryScanner) {
+ $this->directories[] = $directory;
+ } elseif (is_string($directory)) {
+ $realDir = realpath($directory);
+ if (!$realDir || !is_dir($realDir)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Directory "%s" does not exist',
+ $realDir
+ ));
+ }
+ $this->directories[] = $realDir;
+ } else {
+ throw new Exception\InvalidArgumentException(
+ 'The argument provided was neither a DirectoryScanner or directory path'
+ );
+ }
+ }
+
+ /**
+ * @param DirectoryScanner $directoryScanner
+ * @return void
+ */
+ public function addDirectoryScanner(DirectoryScanner $directoryScanner)
+ {
+ $this->addDirectory($directoryScanner);
+ }
+
+ /**
+ * @param FileScanner $fileScanner
+ * @return void
+ */
+ public function addFileScanner(FileScanner $fileScanner)
+ {
+ $this->fileScanners[] = $fileScanner;
+ }
+
+ /**
+ * @return void
+ */
+ protected function scan()
+ {
+ if ($this->isScanned) {
+ return;
+ }
+
+ // iterate directories creating file scanners
+ foreach ($this->directories as $directory) {
+ if ($directory instanceof DirectoryScanner) {
+ $directory->scan();
+ if ($directory->fileScanners) {
+ $this->fileScanners = array_merge($this->fileScanners, $directory->fileScanners);
+ }
+ } else {
+ $rdi = new RecursiveDirectoryIterator($directory);
+ foreach (new RecursiveIteratorIterator($rdi) as $item) {
+ if ($item->isFile() && pathinfo($item->getRealPath(), PATHINFO_EXTENSION) == 'php') {
+ $this->fileScanners[] = new FileScanner($item->getRealPath());
+ }
+ }
+ }
+ }
+
+ $this->isScanned = true;
+ }
+
+ /**
+ * @todo implement method
+ */
+ public function getNamespaces()
+ {
+ // @todo
+ }
+
+ /**
+ * @param bool $returnFileScanners
+ * @return array
+ */
+ public function getFiles($returnFileScanners = false)
+ {
+ $this->scan();
+
+ $return = array();
+ foreach ($this->fileScanners as $fileScanner) {
+ $return[] = ($returnFileScanners) ? $fileScanner : $fileScanner->getFile();
+ }
+
+ return $return;
+ }
+
+ /**
+ * @return array
+ */
+ public function getClassNames()
+ {
+ $this->scan();
+
+ if ($this->classToFileScanner === null) {
+ $this->createClassToFileScannerCache();
+ }
+
+ return array_keys($this->classToFileScanner);
+ }
+
+ /**
+ * @param bool $returnDerivedScannerClass
+ * @return array
+ */
+ public function getClasses($returnDerivedScannerClass = false)
+ {
+ $this->scan();
+
+ if ($this->classToFileScanner === null) {
+ $this->createClassToFileScannerCache();
+ }
+
+ $returnClasses = array();
+ foreach ($this->classToFileScanner as $className => $fsIndex) {
+ $classScanner = $this->fileScanners[$fsIndex]->getClass($className);
+ if ($returnDerivedScannerClass) {
+ $classScanner = new DerivedClassScanner($classScanner, $this);
+ }
+ $returnClasses[] = $classScanner;
+ }
+
+ return $returnClasses;
+ }
+
+ /**
+ * @param string $class
+ * @return bool
+ */
+ public function hasClass($class)
+ {
+ $this->scan();
+
+ if ($this->classToFileScanner === null) {
+ $this->createClassToFileScannerCache();
+ }
+
+ return (isset($this->classToFileScanner[$class]));
+ }
+
+ /**
+ * @param string $class
+ * @param bool $returnDerivedScannerClass
+ * @return ClassScanner|DerivedClassScanner
+ * @throws Exception\InvalidArgumentException
+ */
+ public function getClass($class, $returnDerivedScannerClass = false)
+ {
+ $this->scan();
+
+ if ($this->classToFileScanner === null) {
+ $this->createClassToFileScannerCache();
+ }
+
+ if (!isset($this->classToFileScanner[$class])) {
+ throw new Exception\InvalidArgumentException('Class not found.');
+ }
+
+ /** @var FileScanner $fs */
+ $fs = $this->fileScanners[$this->classToFileScanner[$class]];
+ $returnClass = $fs->getClass($class);
+
+ if (($returnClass instanceof ClassScanner) && $returnDerivedScannerClass) {
+ return new DerivedClassScanner($returnClass, $this);
+ }
+
+ return $returnClass;
+ }
+
+ /**
+ * Create class to file scanner cache
+ *
+ * @return void
+ */
+ protected function createClassToFileScannerCache()
+ {
+ if ($this->classToFileScanner !== null) {
+ return;
+ }
+
+ $this->classToFileScanner = array();
+ /** @var FileScanner $fileScanner */
+ foreach ($this->fileScanners as $fsIndex => $fileScanner) {
+ $fsClasses = $fileScanner->getClassNames();
+ foreach ($fsClasses as $fsClassName) {
+ $this->classToFileScanner[$fsClassName] = $fsIndex;
+ }
+ }
+ }
+
+ /**
+ * Export
+ *
+ * @todo implement method
+ */
+ public static function export()
+ {
+ // @todo
+ }
+
+ /**
+ * __ToString
+ *
+ * @todo implement method
+ */
+ public function __toString()
+ {
+ // @todo
+ }
+}
diff --git a/library/Zend/Code/Scanner/DocBlockScanner.php b/library/Zend/Code/Scanner/DocBlockScanner.php
new file mode 100755
index 0000000000..3bb28ece12
--- /dev/null
+++ b/library/Zend/Code/Scanner/DocBlockScanner.php
@@ -0,0 +1,326 @@
+docComment = $docComment;
+ $this->nameInformation = $nameInformation;
+ }
+
+ /**
+ * @return string
+ */
+ public function getShortDescription()
+ {
+ $this->scan();
+
+ return $this->shortDescription;
+ }
+
+ /**
+ * @return string
+ */
+ public function getLongDescription()
+ {
+ $this->scan();
+
+ return $this->longDescription;
+ }
+
+ /**
+ * @return array
+ */
+ public function getTags()
+ {
+ $this->scan();
+
+ return $this->tags;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAnnotations()
+ {
+ $this->scan();
+
+ return $this->annotations;
+ }
+
+ /**
+ * @return void
+ */
+ protected function scan()
+ {
+ if ($this->isScanned) {
+ return;
+ }
+
+ $mode = 1;
+
+ $tokens = $this->tokenize();
+ $tagIndex = null;
+ reset($tokens);
+
+ SCANNER_TOP:
+ $token = current($tokens);
+
+ switch ($token[0]) {
+ case 'DOCBLOCK_NEWLINE':
+ if ($this->shortDescription != '' && $tagIndex === null) {
+ $mode = 2;
+ } else {
+ $this->longDescription .= $token[1];
+ }
+ goto SCANNER_CONTINUE;
+ //goto no break needed
+
+ case 'DOCBLOCK_WHITESPACE':
+ case 'DOCBLOCK_TEXT':
+ if ($tagIndex !== null) {
+ $this->tags[$tagIndex]['value'] .= ($this->tags[$tagIndex]['value'] == '') ? $token[1] : ' ' . $token[1];
+ goto SCANNER_CONTINUE;
+ } elseif ($mode <= 2) {
+ if ($mode == 1) {
+ $this->shortDescription .= $token[1];
+ } else {
+ $this->longDescription .= $token[1];
+ }
+ goto SCANNER_CONTINUE;
+ }
+ //gotos no break needed
+ case 'DOCBLOCK_TAG':
+ array_push($this->tags, array('name' => $token[1],
+ 'value' => ''));
+ end($this->tags);
+ $tagIndex = key($this->tags);
+ $mode = 3;
+ goto SCANNER_CONTINUE;
+ //goto no break needed
+
+ case 'DOCBLOCK_COMMENTEND':
+ goto SCANNER_END;
+
+ }
+
+ SCANNER_CONTINUE:
+ if (next($tokens) === false) {
+ goto SCANNER_END;
+ }
+ goto SCANNER_TOP;
+
+ SCANNER_END:
+
+ $this->shortDescription = trim($this->shortDescription);
+ $this->longDescription = trim($this->longDescription);
+ $this->isScanned = true;
+ }
+
+ /**
+ * @return array
+ */
+ protected function tokenize()
+ {
+ static $CONTEXT_INSIDE_DOCBLOCK = 0x01;
+ static $CONTEXT_INSIDE_ASTERISK = 0x02;
+
+ $context = 0x00;
+ $stream = $this->docComment;
+ $streamIndex = null;
+ $tokens = array();
+ $tokenIndex = null;
+ $currentChar = null;
+ $currentWord = null;
+ $currentLine = null;
+
+ $MACRO_STREAM_ADVANCE_CHAR = function ($positionsForward = 1) use (&$stream, &$streamIndex, &$currentChar, &$currentWord, &$currentLine) {
+ $positionsForward = ($positionsForward > 0) ? $positionsForward : 1;
+ $streamIndex = ($streamIndex === null) ? 0 : $streamIndex + $positionsForward;
+ if (!isset($stream[$streamIndex])) {
+ $currentChar = false;
+
+ return false;
+ }
+ $currentChar = $stream[$streamIndex];
+ $matches = array();
+ $currentLine = (preg_match('#(.*?)\r?\n#', $stream, $matches, null, $streamIndex) === 1) ? $matches[1] : substr($stream, $streamIndex);
+ if ($currentChar === ' ') {
+ $currentWord = (preg_match('#( +)#', $currentLine, $matches) === 1) ? $matches[1] : $currentLine;
+ } else {
+ $currentWord = (($matches = strpos($currentLine, ' ')) !== false) ? substr($currentLine, 0, $matches) : $currentLine;
+ }
+
+ return $currentChar;
+ };
+ $MACRO_STREAM_ADVANCE_WORD = function () use (&$currentWord, &$MACRO_STREAM_ADVANCE_CHAR) {
+ return $MACRO_STREAM_ADVANCE_CHAR(strlen($currentWord));
+ };
+ $MACRO_STREAM_ADVANCE_LINE = function () use (&$currentLine, &$MACRO_STREAM_ADVANCE_CHAR) {
+ return $MACRO_STREAM_ADVANCE_CHAR(strlen($currentLine));
+ };
+ $MACRO_TOKEN_ADVANCE = function () use (&$tokenIndex, &$tokens) {
+ $tokenIndex = ($tokenIndex === null) ? 0 : $tokenIndex + 1;
+ $tokens[$tokenIndex] = array('DOCBLOCK_UNKNOWN', '');
+ };
+ $MACRO_TOKEN_SET_TYPE = function ($type) use (&$tokenIndex, &$tokens) {
+ $tokens[$tokenIndex][0] = $type;
+ };
+ $MACRO_TOKEN_APPEND_CHAR = function () use (&$currentChar, &$tokens, &$tokenIndex) {
+ $tokens[$tokenIndex][1] .= $currentChar;
+ };
+ $MACRO_TOKEN_APPEND_WORD = function () use (&$currentWord, &$tokens, &$tokenIndex) {
+ $tokens[$tokenIndex][1] .= $currentWord;
+ };
+ $MACRO_TOKEN_APPEND_WORD_PARTIAL = function ($length) use (&$currentWord, &$tokens, &$tokenIndex) {
+ $tokens[$tokenIndex][1] .= substr($currentWord, 0, $length);
+ };
+ $MACRO_TOKEN_APPEND_LINE = function () use (&$currentLine, &$tokens, &$tokenIndex) {
+ $tokens[$tokenIndex][1] .= $currentLine;
+ };
+
+ $MACRO_STREAM_ADVANCE_CHAR();
+ $MACRO_TOKEN_ADVANCE();
+
+ TOKENIZER_TOP:
+
+ if ($context === 0x00 && $currentChar === '/' && $currentWord === '/**') {
+ $MACRO_TOKEN_SET_TYPE('DOCBLOCK_COMMENTSTART');
+ $MACRO_TOKEN_APPEND_WORD();
+ $MACRO_TOKEN_ADVANCE();
+ $context |= $CONTEXT_INSIDE_DOCBLOCK;
+ $context |= $CONTEXT_INSIDE_ASTERISK;
+ if ($MACRO_STREAM_ADVANCE_WORD() === false) {
+ goto TOKENIZER_END;
+ }
+ goto TOKENIZER_TOP;
+ }
+
+ if ($context & $CONTEXT_INSIDE_DOCBLOCK && $currentWord === '*/') {
+ $MACRO_TOKEN_SET_TYPE('DOCBLOCK_COMMENTEND');
+ $MACRO_TOKEN_APPEND_WORD();
+ $MACRO_TOKEN_ADVANCE();
+ $context &= ~$CONTEXT_INSIDE_DOCBLOCK;
+ if ($MACRO_STREAM_ADVANCE_WORD() === false) {
+ goto TOKENIZER_END;
+ }
+ goto TOKENIZER_TOP;
+ }
+
+ if ($currentChar === ' ' || $currentChar === "\t") {
+ $MACRO_TOKEN_SET_TYPE(($context & $CONTEXT_INSIDE_ASTERISK) ? 'DOCBLOCK_WHITESPACE' : 'DOCBLOCK_WHITESPACE_INDENT');
+ $MACRO_TOKEN_APPEND_WORD();
+ $MACRO_TOKEN_ADVANCE();
+ if ($MACRO_STREAM_ADVANCE_WORD() === false) {
+ goto TOKENIZER_END;
+ }
+ goto TOKENIZER_TOP;
+ }
+
+ if ($currentChar === '*') {
+ if (($context & $CONTEXT_INSIDE_DOCBLOCK) && ($context & $CONTEXT_INSIDE_ASTERISK)) {
+ $MACRO_TOKEN_SET_TYPE('DOCBLOCK_TEXT');
+ } else {
+ $MACRO_TOKEN_SET_TYPE('DOCBLOCK_ASTERISK');
+ $context |= $CONTEXT_INSIDE_ASTERISK;
+ }
+ $MACRO_TOKEN_APPEND_CHAR();
+ $MACRO_TOKEN_ADVANCE();
+ if ($MACRO_STREAM_ADVANCE_CHAR() === false) {
+ goto TOKENIZER_END;
+ }
+ goto TOKENIZER_TOP;
+ }
+
+ if ($currentChar === '@') {
+ $MACRO_TOKEN_SET_TYPE('DOCBLOCK_TAG');
+ $MACRO_TOKEN_APPEND_WORD();
+ $MACRO_TOKEN_ADVANCE();
+ if ($MACRO_STREAM_ADVANCE_WORD() === false) {
+ goto TOKENIZER_END;
+ }
+ goto TOKENIZER_TOP;
+ }
+
+ if ($currentChar === "\n") {
+ $MACRO_TOKEN_SET_TYPE('DOCBLOCK_NEWLINE');
+ $MACRO_TOKEN_APPEND_CHAR();
+ $MACRO_TOKEN_ADVANCE();
+ $context &= ~$CONTEXT_INSIDE_ASTERISK;
+ if ($MACRO_STREAM_ADVANCE_CHAR() === false) {
+ goto TOKENIZER_END;
+ }
+ goto TOKENIZER_TOP;
+ }
+
+ $MACRO_TOKEN_SET_TYPE('DOCBLOCK_TEXT');
+ $MACRO_TOKEN_APPEND_LINE();
+ $MACRO_TOKEN_ADVANCE();
+ if ($MACRO_STREAM_ADVANCE_LINE() === false) {
+ goto TOKENIZER_END;
+ }
+ goto TOKENIZER_TOP;
+
+ TOKENIZER_END:
+
+ array_pop($tokens);
+
+ return $tokens;
+ }
+}
diff --git a/library/Zend/Code/Scanner/FileScanner.php b/library/Zend/Code/Scanner/FileScanner.php
new file mode 100755
index 0000000000..64700f611b
--- /dev/null
+++ b/library/Zend/Code/Scanner/FileScanner.php
@@ -0,0 +1,46 @@
+file = $file;
+ if (!file_exists($file)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'File "%s" not found',
+ $file
+ ));
+ }
+ parent::__construct(token_get_all(file_get_contents($file)), $annotationManager);
+ }
+
+ /**
+ * @return string
+ */
+ public function getFile()
+ {
+ return $this->file;
+ }
+}
diff --git a/library/Zend/Code/Scanner/FunctionScanner.php b/library/Zend/Code/Scanner/FunctionScanner.php
new file mode 100755
index 0000000000..dbb082b23b
--- /dev/null
+++ b/library/Zend/Code/Scanner/FunctionScanner.php
@@ -0,0 +1,16 @@
+tokens = $methodTokens;
+ $this->nameInformation = $nameInformation;
+ }
+
+ /**
+ * @param string $class
+ * @return MethodScanner
+ */
+ public function setClass($class)
+ {
+ $this->class = (string) $class;
+ return $this;
+ }
+
+ /**
+ * @param ClassScanner $scannerClass
+ * @return MethodScanner
+ */
+ public function setScannerClass(ClassScanner $scannerClass)
+ {
+ $this->scannerClass = $scannerClass;
+ return $this;
+ }
+
+ /**
+ * @return MethodScanner
+ */
+ public function getClassScanner()
+ {
+ return $this->scannerClass;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ $this->scan();
+
+ return $this->name;
+ }
+
+ /**
+ * @return int
+ */
+ public function getLineStart()
+ {
+ $this->scan();
+
+ return $this->lineStart;
+ }
+
+ /**
+ * @return int
+ */
+ public function getLineEnd()
+ {
+ $this->scan();
+
+ return $this->lineEnd;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDocComment()
+ {
+ $this->scan();
+
+ return $this->docComment;
+ }
+
+ /**
+ * @param AnnotationManager $annotationManager
+ * @return AnnotationScanner
+ */
+ public function getAnnotations(AnnotationManager $annotationManager)
+ {
+ if (($docComment = $this->getDocComment()) == '') {
+ return false;
+ }
+
+ return new AnnotationScanner($annotationManager, $docComment, $this->nameInformation);
+ }
+
+ /**
+ * @return bool
+ */
+ public function isFinal()
+ {
+ $this->scan();
+
+ return $this->isFinal;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isAbstract()
+ {
+ $this->scan();
+
+ return $this->isAbstract;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isPublic()
+ {
+ $this->scan();
+
+ return $this->isPublic;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isProtected()
+ {
+ $this->scan();
+
+ return $this->isProtected;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isPrivate()
+ {
+ $this->scan();
+
+ return $this->isPrivate;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isStatic()
+ {
+ $this->scan();
+
+ return $this->isStatic;
+ }
+
+ /**
+ * @return int
+ */
+ public function getNumberOfParameters()
+ {
+ return count($this->getParameters());
+ }
+
+ /**
+ * @param bool $returnScanner
+ * @return array
+ */
+ public function getParameters($returnScanner = false)
+ {
+ $this->scan();
+
+ $return = array();
+
+ foreach ($this->infos as $info) {
+ if ($info['type'] != 'parameter') {
+ continue;
+ }
+
+ if (!$returnScanner) {
+ $return[] = $info['name'];
+ } else {
+ $return[] = $this->getParameter($info['name']);
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * @param int|string $parameterNameOrInfoIndex
+ * @return ParameterScanner
+ * @throws Exception\InvalidArgumentException
+ */
+ public function getParameter($parameterNameOrInfoIndex)
+ {
+ $this->scan();
+
+ if (is_int($parameterNameOrInfoIndex)) {
+ $info = $this->infos[$parameterNameOrInfoIndex];
+ if ($info['type'] != 'parameter') {
+ throw new Exception\InvalidArgumentException('Index of info offset is not about a parameter');
+ }
+ } elseif (is_string($parameterNameOrInfoIndex)) {
+ foreach ($this->infos as $info) {
+ if ($info['type'] === 'parameter' && $info['name'] === $parameterNameOrInfoIndex) {
+ break;
+ }
+ unset($info);
+ }
+ if (!isset($info)) {
+ throw new Exception\InvalidArgumentException('Index of info offset is not about a parameter');
+ }
+ }
+
+ $p = new ParameterScanner(
+ array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart']),
+ $this->nameInformation
+ );
+ $p->setDeclaringFunction($this->name);
+ $p->setDeclaringScannerFunction($this);
+ $p->setDeclaringClass($this->class);
+ $p->setDeclaringScannerClass($this->scannerClass);
+ $p->setPosition($info['position']);
+
+ return $p;
+ }
+
+ /**
+ * @return string
+ */
+ public function getBody()
+ {
+ $this->scan();
+
+ return $this->body;
+ }
+
+ public static function export()
+ {
+ // @todo
+ }
+
+ public function __toString()
+ {
+ $this->scan();
+
+ return var_export($this, true);
+ }
+
+ protected function scan()
+ {
+ if ($this->isScanned) {
+ return;
+ }
+
+ if (!$this->tokens) {
+ throw new Exception\RuntimeException('No tokens were provided');
+ }
+
+ /**
+ * Variables & Setup
+ */
+
+ $tokens = &$this->tokens; // localize
+ $infos = &$this->infos; // localize
+ $tokenIndex = null;
+ $token = null;
+ $tokenType = null;
+ $tokenContent = null;
+ $tokenLine = null;
+ $infoIndex = 0;
+ $parentCount = 0;
+
+ /*
+ * MACRO creation
+ */
+ $MACRO_TOKEN_ADVANCE = function () use (&$tokens, &$tokenIndex, &$token, &$tokenType, &$tokenContent, &$tokenLine) {
+ static $lastTokenArray = null;
+ $tokenIndex = ($tokenIndex === null) ? 0 : $tokenIndex + 1;
+ if (!isset($tokens[$tokenIndex])) {
+ $token = false;
+ $tokenContent = false;
+ $tokenType = false;
+ $tokenLine = false;
+
+ return false;
+ }
+ $token = $tokens[$tokenIndex];
+ if (is_string($token)) {
+ $tokenType = null;
+ $tokenContent = $token;
+ $tokenLine = $tokenLine + substr_count(
+ $lastTokenArray[1],
+ "\n"
+ ); // adjust token line by last known newline count
+ } else {
+ list($tokenType, $tokenContent, $tokenLine) = $token;
+ }
+
+ return $tokenIndex;
+ };
+ $MACRO_INFO_START = function () use (&$infoIndex, &$infos, &$tokenIndex, &$tokenLine) {
+ $infos[$infoIndex] = array(
+ 'type' => 'parameter',
+ 'tokenStart' => $tokenIndex,
+ 'tokenEnd' => null,
+ 'lineStart' => $tokenLine,
+ 'lineEnd' => $tokenLine,
+ 'name' => null,
+ 'position' => $infoIndex + 1, // position is +1 of infoIndex
+ );
+ };
+ $MACRO_INFO_ADVANCE = function () use (&$infoIndex, &$infos, &$tokenIndex, &$tokenLine) {
+ $infos[$infoIndex]['tokenEnd'] = $tokenIndex;
+ $infos[$infoIndex]['lineEnd'] = $tokenLine;
+ $infoIndex++;
+
+ return $infoIndex;
+ };
+
+ /**
+ * START FINITE STATE MACHINE FOR SCANNING TOKENS
+ */
+
+ // Initialize token
+ $MACRO_TOKEN_ADVANCE();
+
+ SCANNER_TOP:
+
+ $this->lineStart = ($this->lineStart) ? : $tokenLine;
+
+ switch ($tokenType) {
+ case T_DOC_COMMENT:
+ $this->lineStart = null;
+ if ($this->docComment === null && $this->name === null) {
+ $this->docComment = $tokenContent;
+ }
+ goto SCANNER_CONTINUE_SIGNATURE;
+ //goto (no break needed);
+
+ case T_FINAL:
+ $this->isFinal = true;
+ goto SCANNER_CONTINUE_SIGNATURE;
+ //goto (no break needed);
+
+ case T_ABSTRACT:
+ $this->isAbstract = true;
+ goto SCANNER_CONTINUE_SIGNATURE;
+ //goto (no break needed);
+
+ case T_PUBLIC:
+ // use defaults
+ goto SCANNER_CONTINUE_SIGNATURE;
+ //goto (no break needed);
+
+ case T_PROTECTED:
+ $this->isProtected = true;
+ $this->isPublic = false;
+ goto SCANNER_CONTINUE_SIGNATURE;
+ //goto (no break needed);
+
+ case T_PRIVATE:
+ $this->isPrivate = true;
+ $this->isPublic = false;
+ goto SCANNER_CONTINUE_SIGNATURE;
+ //goto (no break needed);
+
+ case T_STATIC:
+ $this->isStatic = true;
+ goto SCANNER_CONTINUE_SIGNATURE;
+ //goto (no break needed);
+
+ case T_VARIABLE:
+ case T_STRING:
+
+ if ($tokenType === T_STRING && $parentCount === 0) {
+ $this->name = $tokenContent;
+ }
+
+ if ($parentCount === 1) {
+ if (!isset($infos[$infoIndex])) {
+ $MACRO_INFO_START();
+ }
+ if ($tokenType === T_VARIABLE) {
+ $infos[$infoIndex]['name'] = ltrim($tokenContent, '$');
+ }
+ }
+
+ goto SCANNER_CONTINUE_SIGNATURE;
+ //goto (no break needed);
+
+ case null:
+
+ switch ($tokenContent) {
+ case '&':
+ if (!isset($infos[$infoIndex])) {
+ $MACRO_INFO_START();
+ }
+ goto SCANNER_CONTINUE_SIGNATURE;
+ //goto (no break needed);
+ case '(':
+ $parentCount++;
+ goto SCANNER_CONTINUE_SIGNATURE;
+ //goto (no break needed);
+ case ')':
+ $parentCount--;
+ if ($parentCount > 0) {
+ goto SCANNER_CONTINUE_SIGNATURE;
+ }
+ if ($parentCount === 0) {
+ if ($infos) {
+ $MACRO_INFO_ADVANCE();
+ }
+ $context = 'body';
+ }
+ goto SCANNER_CONTINUE_BODY;
+ //goto (no break needed);
+ case ',':
+ if ($parentCount === 1) {
+ $MACRO_INFO_ADVANCE();
+ }
+ goto SCANNER_CONTINUE_SIGNATURE;
+ }
+ }
+
+ SCANNER_CONTINUE_SIGNATURE:
+
+ if ($MACRO_TOKEN_ADVANCE() === false) {
+ goto SCANNER_END;
+ }
+ goto SCANNER_TOP;
+
+ SCANNER_CONTINUE_BODY:
+
+ $braceCount = 0;
+ while ($MACRO_TOKEN_ADVANCE() !== false) {
+ if ($tokenContent == '}') {
+ $braceCount--;
+ }
+ if ($braceCount > 0) {
+ $this->body .= $tokenContent;
+ }
+ if ($tokenContent == '{') {
+ $braceCount++;
+ }
+ $this->lineEnd = $tokenLine;
+ }
+
+ SCANNER_END:
+
+ $this->isScanned = true;
+
+ return;
+ }
+}
diff --git a/library/Zend/Code/Scanner/ParameterScanner.php b/library/Zend/Code/Scanner/ParameterScanner.php
new file mode 100755
index 0000000000..ef15a16c87
--- /dev/null
+++ b/library/Zend/Code/Scanner/ParameterScanner.php
@@ -0,0 +1,352 @@
+tokens = $parameterTokens;
+ $this->nameInformation = $nameInformation;
+ }
+
+ /**
+ * Set declaring class
+ *
+ * @param string $class
+ * @return void
+ */
+ public function setDeclaringClass($class)
+ {
+ $this->declaringClass = (string) $class;
+ }
+
+ /**
+ * Set declaring scanner class
+ *
+ * @param ClassScanner $scannerClass
+ * @return void
+ */
+ public function setDeclaringScannerClass(ClassScanner $scannerClass)
+ {
+ $this->declaringScannerClass = $scannerClass;
+ }
+
+ /**
+ * Set declaring function
+ *
+ * @param string $function
+ * @return void
+ */
+ public function setDeclaringFunction($function)
+ {
+ $this->declaringFunction = $function;
+ }
+
+ /**
+ * Set declaring scanner function
+ *
+ * @param MethodScanner $scannerFunction
+ * @return void
+ */
+ public function setDeclaringScannerFunction(MethodScanner $scannerFunction)
+ {
+ $this->declaringScannerFunction = $scannerFunction;
+ }
+
+ /**
+ * Set position
+ *
+ * @param int $position
+ * @return void
+ */
+ public function setPosition($position)
+ {
+ $this->position = $position;
+ }
+
+ /**
+ * Scan
+ *
+ * @return void
+ */
+ protected function scan()
+ {
+ if ($this->isScanned) {
+ return;
+ }
+
+ $tokens = &$this->tokens;
+
+ reset($tokens);
+
+ SCANNER_TOP:
+
+ $token = current($tokens);
+
+ if (is_string($token)) {
+ // check pass by ref
+ if ($token === '&') {
+ $this->isPassedByReference = true;
+ goto SCANNER_CONTINUE;
+ }
+ if ($token === '=') {
+ $this->isOptional = true;
+ $this->isDefaultValueAvailable = true;
+ goto SCANNER_CONTINUE;
+ }
+ } else {
+ if ($this->name === null && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR)) {
+ $this->class .= $token[1];
+ goto SCANNER_CONTINUE;
+ }
+ if ($token[0] === T_VARIABLE) {
+ $this->name = ltrim($token[1], '$');
+ goto SCANNER_CONTINUE;
+ }
+ }
+
+ if ($this->name !== null) {
+ $this->defaultValue .= trim((is_string($token)) ? $token : $token[1]);
+ }
+
+ SCANNER_CONTINUE:
+
+ if (next($this->tokens) === false) {
+ goto SCANNER_END;
+ }
+ goto SCANNER_TOP;
+
+ SCANNER_END:
+
+ if ($this->class && $this->nameInformation) {
+ $this->class = $this->nameInformation->resolveName($this->class);
+ }
+
+ $this->isScanned = true;
+ }
+
+ /**
+ * Get declaring scanner class
+ *
+ * @return ClassScanner
+ */
+ public function getDeclaringScannerClass()
+ {
+ return $this->declaringScannerClass;
+ }
+
+ /**
+ * Get declaring class
+ *
+ * @return string
+ */
+ public function getDeclaringClass()
+ {
+ return $this->declaringClass;
+ }
+
+ /**
+ * Get declaring scanner function
+ *
+ * @return MethodScanner
+ */
+ public function getDeclaringScannerFunction()
+ {
+ return $this->declaringScannerFunction;
+ }
+
+ /**
+ * Get declaring function
+ *
+ * @return string
+ */
+ public function getDeclaringFunction()
+ {
+ return $this->declaringFunction;
+ }
+
+ /**
+ * Get default value
+ *
+ * @return string
+ */
+ public function getDefaultValue()
+ {
+ $this->scan();
+
+ return $this->defaultValue;
+ }
+
+ /**
+ * Get class
+ *
+ * @return string
+ */
+ public function getClass()
+ {
+ $this->scan();
+
+ return $this->class;
+ }
+
+ /**
+ * Get name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ $this->scan();
+
+ return $this->name;
+ }
+
+ /**
+ * Get position
+ *
+ * @return int
+ */
+ public function getPosition()
+ {
+ $this->scan();
+
+ return $this->position;
+ }
+
+ /**
+ * Check if is array
+ *
+ * @return bool
+ */
+ public function isArray()
+ {
+ $this->scan();
+
+ return $this->isArray;
+ }
+
+ /**
+ * Check if default value is available
+ *
+ * @return bool
+ */
+ public function isDefaultValueAvailable()
+ {
+ $this->scan();
+
+ return $this->isDefaultValueAvailable;
+ }
+
+ /**
+ * Check if is optional
+ *
+ * @return bool
+ */
+ public function isOptional()
+ {
+ $this->scan();
+
+ return $this->isOptional;
+ }
+
+ /**
+ * Check if is passed by reference
+ *
+ * @return bool
+ */
+ public function isPassedByReference()
+ {
+ $this->scan();
+
+ return $this->isPassedByReference;
+ }
+}
diff --git a/library/Zend/Code/Scanner/PropertyScanner.php b/library/Zend/Code/Scanner/PropertyScanner.php
new file mode 100755
index 0000000000..ce38b15856
--- /dev/null
+++ b/library/Zend/Code/Scanner/PropertyScanner.php
@@ -0,0 +1,316 @@
+tokens = $propertyTokens;
+ $this->nameInformation = $nameInformation;
+ }
+
+ /**
+ * @param string $class
+ */
+ public function setClass($class)
+ {
+ $this->class = $class;
+ }
+
+ /**
+ * @param ClassScanner $scannerClass
+ */
+ public function setScannerClass(ClassScanner $scannerClass)
+ {
+ $this->scannerClass = $scannerClass;
+ }
+
+ /**
+ * @return ClassScanner
+ */
+ public function getClassScanner()
+ {
+ return $this->scannerClass;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ $this->scan();
+ return $this->name;
+ }
+
+ /**
+ * @return string
+ */
+ public function getValueType()
+ {
+ return $this->valueType;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isPublic()
+ {
+ $this->scan();
+ return $this->isPublic;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isPrivate()
+ {
+ $this->scan();
+ return $this->isPrivate;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isProtected()
+ {
+ $this->scan();
+ return $this->isProtected;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isStatic()
+ {
+ $this->scan();
+ return $this->isStatic;
+ }
+
+ /**
+ * @return string
+ */
+ public function getValue()
+ {
+ $this->scan();
+ return $this->value;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDocComment()
+ {
+ $this->scan();
+ return $this->docComment;
+ }
+
+ /**
+ * @param Annotation\AnnotationManager $annotationManager
+ * @return AnnotationScanner
+ */
+ public function getAnnotations(Annotation\AnnotationManager $annotationManager)
+ {
+ if (($docComment = $this->getDocComment()) == '') {
+ return false;
+ }
+
+ return new AnnotationScanner($annotationManager, $docComment, $this->nameInformation);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ $this->scan();
+ return var_export($this, true);
+ }
+
+ /**
+ * Scan tokens
+ *
+ * @throws \Zend\Code\Exception\RuntimeException
+ */
+ protected function scan()
+ {
+ if ($this->isScanned) {
+ return;
+ }
+
+ if (!$this->tokens) {
+ throw new Exception\RuntimeException('No tokens were provided');
+ }
+
+ /**
+ * Variables & Setup
+ */
+ $value = '';
+ $concatenateValue = false;
+
+ $tokens = &$this->tokens;
+ reset($tokens);
+
+ foreach ($tokens as $token) {
+ $tempValue = $token;
+ if (!is_string($token)) {
+ list($tokenType, $tokenContent, $tokenLine) = $token;
+
+ switch ($tokenType) {
+ case T_DOC_COMMENT:
+ if ($this->docComment === null && $this->name === null) {
+ $this->docComment = $tokenContent;
+ }
+ break;
+
+ case T_VARIABLE:
+ $this->name = ltrim($tokenContent, '$');
+ break;
+
+ case T_PUBLIC:
+ // use defaults
+ break;
+
+ case T_PROTECTED:
+ $this->isProtected = true;
+ $this->isPublic = false;
+ break;
+
+ case T_PRIVATE:
+ $this->isPrivate = true;
+ $this->isPublic = false;
+ break;
+
+ case T_STATIC:
+ $this->isStatic = true;
+ break;
+ default:
+ $tempValue = trim($tokenContent);
+ break;
+ }
+ }
+
+ //end value concatenation
+ if (!is_array($token) && trim($token) == ";") {
+ $concatenateValue = false;
+ }
+
+ if (true === $concatenateValue) {
+ $value .= $tempValue;
+ }
+
+ //start value concatenation
+ if (!is_array($token) && trim($token) == "=") {
+ $concatenateValue = true;
+ }
+ }
+
+ $this->valueType = self::T_UNKNOWN;
+ if ($value == "false" || $value == "true") {
+ $this->valueType = self::T_BOOLEAN;
+ } elseif (is_numeric($value)) {
+ $this->valueType = self::T_INTEGER;
+ } elseif (0 === strpos($value, 'array') || 0 === strpos($value, "[")) {
+ $this->valueType = self::T_ARRAY;
+ } elseif (substr($value, 0, 1) === '"' || substr($value, 0, 1) === "'") {
+ $value = substr($value, 1, -1); // Remove quotes
+ $this->valueType = self::T_STRING;
+ }
+
+ $this->value = empty($value) ? null : $value;
+ $this->isScanned = true;
+ }
+}
diff --git a/library/Zend/Code/Scanner/ScannerInterface.php b/library/Zend/Code/Scanner/ScannerInterface.php
new file mode 100755
index 0000000000..8acb04ecd4
--- /dev/null
+++ b/library/Zend/Code/Scanner/ScannerInterface.php
@@ -0,0 +1,16 @@
+tokens = $tokens;
+ $this->annotationManager = $annotationManager;
+ }
+
+ /**
+ * @return AnnotationManager
+ */
+ public function getAnnotationManager()
+ {
+ return $this->annotationManager;
+ }
+
+ /**
+ * Get doc comment
+ *
+ * @todo Assignment of $this->docComment should probably be done in scan()
+ * and then $this->getDocComment() just retrieves it.
+ *
+ * @return string
+ */
+ public function getDocComment()
+ {
+ foreach ($this->tokens as $token) {
+ $type = $token[0];
+ $value = $token[1];
+ if (($type == T_OPEN_TAG) || ($type == T_WHITESPACE)) {
+ continue;
+ } elseif ($type == T_DOC_COMMENT) {
+ $this->docComment = $value;
+
+ return $this->docComment;
+ } else {
+ // Only whitespace is allowed before file docblocks
+ return;
+ }
+ }
+ }
+
+ /**
+ * @return array
+ */
+ public function getNamespaces()
+ {
+ $this->scan();
+
+ $namespaces = array();
+ foreach ($this->infos as $info) {
+ if ($info['type'] == 'namespace') {
+ $namespaces[] = $info['namespace'];
+ }
+ }
+
+ return $namespaces;
+ }
+
+ /**
+ * @param null|string $namespace
+ * @return array|null
+ */
+ public function getUses($namespace = null)
+ {
+ $this->scan();
+
+ return $this->getUsesNoScan($namespace);
+ }
+
+ /**
+ * @return array
+ */
+ public function getIncludes()
+ {
+ $this->scan();
+ // @todo Implement getIncludes() in TokenArrayScanner
+ }
+
+ /**
+ * @return array
+ */
+ public function getClassNames()
+ {
+ $this->scan();
+
+ $return = array();
+ foreach ($this->infos as $info) {
+ if ($info['type'] != 'class') {
+ continue;
+ }
+
+ $return[] = $info['name'];
+ }
+
+ return $return;
+ }
+
+ /**
+ * @return ClassScanner[]
+ */
+ public function getClasses()
+ {
+ $this->scan();
+
+ $return = array();
+ foreach ($this->infos as $info) {
+ if ($info['type'] != 'class') {
+ continue;
+ }
+
+ $return[] = $this->getClass($info['name']);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Return the class object from this scanner
+ *
+ * @param string|int $name
+ * @throws Exception\InvalidArgumentException
+ * @return ClassScanner
+ */
+ public function getClass($name)
+ {
+ $this->scan();
+
+ if (is_int($name)) {
+ $info = $this->infos[$name];
+ if ($info['type'] != 'class') {
+ throw new Exception\InvalidArgumentException('Index of info offset is not about a class');
+ }
+ } elseif (is_string($name)) {
+ $classFound = false;
+ foreach ($this->infos as $info) {
+ if ($info['type'] === 'class' && $info['name'] === $name) {
+ $classFound = true;
+ break;
+ }
+ }
+
+ if (!$classFound) {
+ return false;
+ }
+ }
+
+ return new ClassScanner(
+ array_slice(
+ $this->tokens,
+ $info['tokenStart'],
+ ($info['tokenEnd'] - $info['tokenStart'] + 1)
+ ), // zero indexed array
+ new NameInformation($info['namespace'], $info['uses'])
+ );
+ }
+
+ /**
+ * @param string $className
+ * @return bool|null|NameInformation
+ */
+ public function getClassNameInformation($className)
+ {
+ $this->scan();
+
+ $classFound = false;
+ foreach ($this->infos as $info) {
+ if ($info['type'] === 'class' && $info['name'] === $className) {
+ $classFound = true;
+ break;
+ }
+ }
+
+ if (!$classFound) {
+ return false;
+ }
+
+ if (!isset($info)) {
+ return null;
+ }
+
+ return new NameInformation($info['namespace'], $info['uses']);
+ }
+
+ /**
+ * @return array
+ */
+ public function getFunctionNames()
+ {
+ $this->scan();
+ $functionNames = array();
+ foreach ($this->infos as $info) {
+ if ($info['type'] == 'function') {
+ $functionNames[] = $info['name'];
+ }
+ }
+
+ return $functionNames;
+ }
+
+ /**
+ * @return array
+ */
+ public function getFunctions()
+ {
+ $this->scan();
+
+ $functions = array();
+ foreach ($this->infos as $info) {
+ if ($info['type'] == 'function') {
+ // @todo $functions[] = new FunctionScanner($info['name']);
+ }
+ }
+
+ return $functions;
+ }
+
+ /**
+ * Export
+ *
+ * @param $tokens
+ */
+ public static function export($tokens)
+ {
+ // @todo
+ }
+
+ public function __toString()
+ {
+ // @todo
+ }
+
+ /**
+ * Scan
+ *
+ * @todo: $this->docComment should be assigned for valid docblock during
+ * the scan instead of $this->getDocComment() (starting with
+ * T_DOC_COMMENT case)
+ *
+ * @throws Exception\RuntimeException
+ */
+ protected function scan()
+ {
+ if ($this->isScanned) {
+ return;
+ }
+
+ if (!$this->tokens) {
+ throw new Exception\RuntimeException('No tokens were provided');
+ }
+
+ /**
+ * Define PHP 5.4 'trait' token constant.
+ */
+ if (!defined('T_TRAIT')) {
+ define('T_TRAIT', 42001);
+ }
+
+ /**
+ * Variables & Setup
+ */
+
+ $tokens = &$this->tokens; // localize
+ $infos = &$this->infos; // localize
+ $tokenIndex = null;
+ $token = null;
+ $tokenType = null;
+ $tokenContent = null;
+ $tokenLine = null;
+ $namespace = null;
+ $docCommentIndex = false;
+ $infoIndex = 0;
+
+ /*
+ * MACRO creation
+ */
+ $MACRO_TOKEN_ADVANCE = function () use (&$tokens, &$tokenIndex, &$token, &$tokenType, &$tokenContent, &$tokenLine) {
+ $tokenIndex = ($tokenIndex === null) ? 0 : $tokenIndex + 1;
+ if (!isset($tokens[$tokenIndex])) {
+ $token = false;
+ $tokenContent = false;
+ $tokenType = false;
+ $tokenLine = false;
+
+ return false;
+ }
+ if (is_string($tokens[$tokenIndex]) && $tokens[$tokenIndex] === '"') {
+ do {
+ $tokenIndex++;
+ } while (!(is_string($tokens[$tokenIndex]) && $tokens[$tokenIndex] === '"'));
+ }
+ $token = $tokens[$tokenIndex];
+ if (is_array($token)) {
+ list($tokenType, $tokenContent, $tokenLine) = $token;
+ } else {
+ $tokenType = null;
+ $tokenContent = $token;
+ }
+
+ return $tokenIndex;
+ };
+ $MACRO_TOKEN_LOGICAL_START_INDEX = function () use (&$tokenIndex, &$docCommentIndex) {
+ return ($docCommentIndex === false) ? $tokenIndex : $docCommentIndex;
+ };
+ $MACRO_DOC_COMMENT_START = function () use (&$tokenIndex, &$docCommentIndex) {
+ $docCommentIndex = $tokenIndex;
+
+ return $docCommentIndex;
+ };
+ $MACRO_DOC_COMMENT_VALIDATE = function () use (&$tokenType, &$docCommentIndex) {
+ static $validTrailingTokens = null;
+ if ($validTrailingTokens === null) {
+ $validTrailingTokens = array(T_WHITESPACE, T_FINAL, T_ABSTRACT, T_INTERFACE, T_CLASS, T_FUNCTION);
+ }
+ if ($docCommentIndex !== false && !in_array($tokenType, $validTrailingTokens)) {
+ $docCommentIndex = false;
+ }
+
+ return $docCommentIndex;
+ };
+ $MACRO_INFO_ADVANCE = function () use (&$infoIndex, &$infos, &$tokenIndex, &$tokenLine) {
+ $infos[$infoIndex]['tokenEnd'] = $tokenIndex;
+ $infos[$infoIndex]['lineEnd'] = $tokenLine;
+ $infoIndex++;
+
+ return $infoIndex;
+ };
+
+ /**
+ * START FINITE STATE MACHINE FOR SCANNING TOKENS
+ */
+
+ // Initialize token
+ $MACRO_TOKEN_ADVANCE();
+
+ SCANNER_TOP:
+
+ if ($token === false) {
+ goto SCANNER_END;
+ }
+
+ // Validate current doc comment index
+ $MACRO_DOC_COMMENT_VALIDATE();
+
+ switch ($tokenType) {
+
+ case T_DOC_COMMENT:
+
+ $MACRO_DOC_COMMENT_START();
+ goto SCANNER_CONTINUE;
+ //goto no break needed
+
+ case T_NAMESPACE:
+
+ $infos[$infoIndex] = array(
+ 'type' => 'namespace',
+ 'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(),
+ 'tokenEnd' => null,
+ 'lineStart' => $token[2],
+ 'lineEnd' => null,
+ 'namespace' => null,
+ );
+
+ // start processing with next token
+ if ($MACRO_TOKEN_ADVANCE() === false) {
+ goto SCANNER_END;
+ }
+
+ SCANNER_NAMESPACE_TOP:
+
+ if ($tokenType === null && $tokenContent === ';' || $tokenContent === '{') {
+ goto SCANNER_NAMESPACE_END;
+ }
+
+ if ($tokenType === T_WHITESPACE) {
+ goto SCANNER_NAMESPACE_CONTINUE;
+ }
+
+ if ($tokenType === T_NS_SEPARATOR || $tokenType === T_STRING) {
+ $infos[$infoIndex]['namespace'] .= $tokenContent;
+ }
+
+ SCANNER_NAMESPACE_CONTINUE:
+
+ if ($MACRO_TOKEN_ADVANCE() === false) {
+ goto SCANNER_END;
+ }
+ goto SCANNER_NAMESPACE_TOP;
+
+ SCANNER_NAMESPACE_END:
+
+ $namespace = $infos[$infoIndex]['namespace'];
+
+ $MACRO_INFO_ADVANCE();
+ goto SCANNER_CONTINUE;
+ //goto no break needed
+
+ case T_USE:
+
+ $infos[$infoIndex] = array(
+ 'type' => 'use',
+ 'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(),
+ 'tokenEnd' => null,
+ 'lineStart' => $tokens[$tokenIndex][2],
+ 'lineEnd' => null,
+ 'namespace' => $namespace,
+ 'statements' => array(0 => array('use' => null,
+ 'as' => null)),
+ );
+
+ $useStatementIndex = 0;
+ $useAsContext = false;
+
+ // start processing with next token
+ if ($MACRO_TOKEN_ADVANCE() === false) {
+ goto SCANNER_END;
+ }
+
+ SCANNER_USE_TOP:
+
+ if ($tokenType === null) {
+ if ($tokenContent === ';') {
+ goto SCANNER_USE_END;
+ } elseif ($tokenContent === ',') {
+ $useAsContext = false;
+ $useStatementIndex++;
+ $infos[$infoIndex]['statements'][$useStatementIndex] = array('use' => null,
+ 'as' => null);
+ }
+ }
+
+ // ANALYZE
+ if ($tokenType !== null) {
+ if ($tokenType == T_AS) {
+ $useAsContext = true;
+ goto SCANNER_USE_CONTINUE;
+ }
+
+ if ($tokenType == T_NS_SEPARATOR || $tokenType == T_STRING) {
+ if ($useAsContext == false) {
+ $infos[$infoIndex]['statements'][$useStatementIndex]['use'] .= $tokenContent;
+ } else {
+ $infos[$infoIndex]['statements'][$useStatementIndex]['as'] = $tokenContent;
+ }
+ }
+ }
+
+ SCANNER_USE_CONTINUE:
+
+ if ($MACRO_TOKEN_ADVANCE() === false) {
+ goto SCANNER_END;
+ }
+ goto SCANNER_USE_TOP;
+
+ SCANNER_USE_END:
+
+ $MACRO_INFO_ADVANCE();
+ goto SCANNER_CONTINUE;
+ //goto no break needed
+
+ case T_INCLUDE:
+ case T_INCLUDE_ONCE:
+ case T_REQUIRE:
+ case T_REQUIRE_ONCE:
+
+ // Static for performance
+ static $includeTypes = array(
+ T_INCLUDE => 'include',
+ T_INCLUDE_ONCE => 'include_once',
+ T_REQUIRE => 'require',
+ T_REQUIRE_ONCE => 'require_once'
+ );
+
+ $infos[$infoIndex] = array(
+ 'type' => 'include',
+ 'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(),
+ 'tokenEnd' => null,
+ 'lineStart' => $tokens[$tokenIndex][2],
+ 'lineEnd' => null,
+ 'includeType' => $includeTypes[$tokens[$tokenIndex][0]],
+ 'path' => '',
+ );
+
+ // start processing with next token
+ if ($MACRO_TOKEN_ADVANCE() === false) {
+ goto SCANNER_END;
+ }
+
+ SCANNER_INCLUDE_TOP:
+
+ if ($tokenType === null && $tokenContent === ';') {
+ goto SCANNER_INCLUDE_END;
+ }
+
+ $infos[$infoIndex]['path'] .= $tokenContent;
+
+ SCANNER_INCLUDE_CONTINUE:
+
+ if ($MACRO_TOKEN_ADVANCE() === false) {
+ goto SCANNER_END;
+ }
+ goto SCANNER_INCLUDE_TOP;
+
+ SCANNER_INCLUDE_END:
+
+ $MACRO_INFO_ADVANCE();
+ goto SCANNER_CONTINUE;
+ //goto no break needed
+
+ case T_FUNCTION:
+ case T_FINAL:
+ case T_ABSTRACT:
+ case T_CLASS:
+ case T_INTERFACE:
+ case T_TRAIT:
+
+ $infos[$infoIndex] = array(
+ 'type' => ($tokenType === T_FUNCTION) ? 'function' : 'class',
+ 'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(),
+ 'tokenEnd' => null,
+ 'lineStart' => $tokens[$tokenIndex][2],
+ 'lineEnd' => null,
+ 'namespace' => $namespace,
+ 'uses' => $this->getUsesNoScan($namespace),
+ 'name' => null,
+ 'shortName' => null,
+ );
+
+ $classBraceCount = 0;
+
+ // start processing with current token
+
+ SCANNER_CLASS_TOP:
+
+ // process the name
+ if ($infos[$infoIndex]['shortName'] == ''
+ && (($tokenType === T_CLASS || $tokenType === T_INTERFACE || $tokenType === T_TRAIT) && $infos[$infoIndex]['type'] === 'class'
+ || ($tokenType === T_FUNCTION && $infos[$infoIndex]['type'] === 'function'))
+ ) {
+ $infos[$infoIndex]['shortName'] = $tokens[$tokenIndex + 2][1];
+ $infos[$infoIndex]['name'] = (($namespace != null) ? $namespace . '\\' : '') . $infos[$infoIndex]['shortName'];
+ }
+
+ if ($tokenType === null) {
+ if ($tokenContent == '{') {
+ $classBraceCount++;
+ }
+ if ($tokenContent == '}') {
+ $classBraceCount--;
+ if ($classBraceCount === 0) {
+ goto SCANNER_CLASS_END;
+ }
+ }
+ }
+
+ SCANNER_CLASS_CONTINUE:
+
+ if ($MACRO_TOKEN_ADVANCE() === false) {
+ goto SCANNER_END;
+ }
+ goto SCANNER_CLASS_TOP;
+
+ SCANNER_CLASS_END:
+
+ $MACRO_INFO_ADVANCE();
+ goto SCANNER_CONTINUE;
+
+ }
+
+ SCANNER_CONTINUE:
+
+ if ($MACRO_TOKEN_ADVANCE() === false) {
+ goto SCANNER_END;
+ }
+ goto SCANNER_TOP;
+
+ SCANNER_END:
+
+ /**
+ * END FINITE STATE MACHINE FOR SCANNING TOKENS
+ */
+
+ $this->isScanned = true;
+ }
+
+ /**
+ * Check for namespace
+ *
+ * @param string $namespace
+ * @return bool
+ */
+ public function hasNamespace($namespace)
+ {
+ $this->scan();
+
+ foreach ($this->infos as $info) {
+ if ($info['type'] == 'namespace' && $info['namespace'] == $namespace) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param string $namespace
+ * @return null|array
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function getUsesNoScan($namespace)
+ {
+ $namespaces = array();
+ foreach ($this->infos as $info) {
+ if ($info['type'] == 'namespace') {
+ $namespaces[] = $info['namespace'];
+ }
+ }
+
+ if ($namespace === null) {
+ $namespace = array_shift($namespaces);
+ } elseif (!is_string($namespace)) {
+ throw new Exception\InvalidArgumentException('Invalid namespace provided');
+ } elseif (!in_array($namespace, $namespaces)) {
+ return null;
+ }
+
+ $uses = array();
+ foreach ($this->infos as $info) {
+ if ($info['type'] !== 'use') {
+ continue;
+ }
+ foreach ($info['statements'] as $statement) {
+ if ($info['namespace'] == $namespace) {
+ $uses[] = $statement;
+ }
+ }
+ }
+
+ return $uses;
+ }
+}
diff --git a/library/Zend/Code/Scanner/Util.php b/library/Zend/Code/Scanner/Util.php
new file mode 100755
index 0000000000..e54ae0b883
--- /dev/null
+++ b/library/Zend/Code/Scanner/Util.php
@@ -0,0 +1,74 @@
+namespace && !$data->uses && strlen($value) > 0 && $value{0} != '\\') {
+ $value = $data->namespace . '\\' . $value;
+
+ return;
+ }
+
+ if (!$data->uses || strlen($value) <= 0 || $value{0} == '\\') {
+ $value = ltrim($value, '\\');
+
+ return;
+ }
+
+ if ($data->namespace || $data->uses) {
+ $firstPart = $value;
+ if (($firstPartEnd = strpos($firstPart, '\\')) !== false) {
+ $firstPart = substr($firstPart, 0, $firstPartEnd);
+ } else {
+ $firstPartEnd = strlen($firstPart);
+ }
+
+ if (array_key_exists($firstPart, $data->uses)) {
+ $value = substr_replace($value, $data->uses[$firstPart], 0, $firstPartEnd);
+
+ return;
+ }
+
+ if ($data->namespace) {
+ $value = $data->namespace . '\\' . $value;
+
+ return;
+ }
+ }
+ }
+}
diff --git a/library/Zend/Code/Scanner/ValueScanner.php b/library/Zend/Code/Scanner/ValueScanner.php
new file mode 100755
index 0000000000..d0826cc245
--- /dev/null
+++ b/library/Zend/Code/Scanner/ValueScanner.php
@@ -0,0 +1,15 @@
+=5.3.23",
+ "zendframework/zend-eventmanager": "self.version"
+ },
+ "require-dev": {
+ "doctrine/common": ">=2.1",
+ "zendframework/zend-stdlib": "self.version"
+ },
+ "suggest": {
+ "doctrine/common": "Doctrine\\Common >=2.1 for annotation features",
+ "zendframework/zend-stdlib": "Zend\\Stdlib component"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev",
+ "dev-develop": "2.4-dev"
+ }
+ }
+}
diff --git a/library/Zend/Config/AbstractConfigFactory.php b/library/Zend/Config/AbstractConfigFactory.php
new file mode 100755
index 0000000000..20f04be024
--- /dev/null
+++ b/library/Zend/Config/AbstractConfigFactory.php
@@ -0,0 +1,172 @@
+configs[$requestedName])) {
+ return true;
+ }
+
+ if (!$serviceLocator->has('Config')) {
+ return false;
+ }
+
+ $key = $this->match($requestedName);
+ if (null === $key) {
+ return false;
+ }
+
+ $config = $serviceLocator->get('Config');
+ return isset($config[$key]);
+ }
+
+ /**
+ * Create service with name
+ *
+ * @param ServiceManager\ServiceLocatorInterface $serviceLocator
+ * @param string $name
+ * @param string $requestedName
+ * @return string|mixed|array
+ */
+ public function createServiceWithName(ServiceManager\ServiceLocatorInterface $serviceLocator, $name, $requestedName)
+ {
+ if (isset($this->configs[$requestedName])) {
+ return $this->configs[$requestedName];
+ }
+
+ $key = $this->match($requestedName);
+ if (isset($this->configs[$key])) {
+ $this->configs[$requestedName] = $this->configs[$key];
+ return $this->configs[$key];
+ }
+
+ $config = $serviceLocator->get('Config');
+ $this->configs[$requestedName] = $this->configs[$key] = $config[$key];
+ return $config;
+ }
+
+ /**
+ * @param string $pattern
+ * @return self
+ * @throws Exception\InvalidArgumentException
+ */
+ public function addPattern($pattern)
+ {
+ if (!is_string($pattern)) {
+ throw new Exception\InvalidArgumentException('pattern must be string');
+ }
+
+ $patterns = $this->getPatterns();
+ array_unshift($patterns, $pattern);
+ $this->setPatterns($patterns);
+ return $this;
+ }
+
+ /**
+ * @param array|Traversable $patterns
+ * @return self
+ * @throws Exception\InvalidArgumentException
+ */
+ public function addPatterns($patterns)
+ {
+ if ($patterns instanceof Traversable) {
+ $patterns = iterator_to_array($patterns);
+ }
+
+ if (!is_array($patterns)) {
+ throw new Exception\InvalidArgumentException("patterns must be array or Traversable");
+ }
+
+ foreach ($patterns as $pattern) {
+ $this->addPattern($pattern);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param array|Traversable $patterns
+ * @return self
+ * @throws \InvalidArgumentException
+ */
+ public function setPatterns($patterns)
+ {
+ if ($patterns instanceof Traversable) {
+ $patterns = iterator_to_array($patterns);
+ }
+
+ if (!is_array($patterns)) {
+ throw new \InvalidArgumentException("patterns must be array or Traversable");
+ }
+
+ $this->patterns = $patterns;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getPatterns()
+ {
+ if (null === $this->patterns) {
+ $this->setPatterns($this->defaultPatterns);
+ }
+ return $this->patterns;
+ }
+
+ /**
+ * @param string $requestedName
+ * @return null|string
+ */
+ protected function match($requestedName)
+ {
+ foreach ($this->getPatterns() as $pattern) {
+ if (preg_match($pattern, $requestedName, $matches)) {
+ return $matches[1];
+ }
+ }
+ return null;
+ }
+}
diff --git a/library/Zend/Config/CONTRIBUTING.md b/library/Zend/Config/CONTRIBUTING.md
new file mode 100755
index 0000000000..e77f5d2d5b
--- /dev/null
+++ b/library/Zend/Config/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# CONTRIBUTING
+
+Please don't open pull requests against this repository, please use https://github.com/zendframework/zf2.
\ No newline at end of file
diff --git a/library/Zend/Config/Config.php b/library/Zend/Config/Config.php
new file mode 100755
index 0000000000..17b6797820
--- /dev/null
+++ b/library/Zend/Config/Config.php
@@ -0,0 +1,400 @@
+allowModifications = (bool) $allowModifications;
+
+ foreach ($array as $key => $value) {
+ if (is_array($value)) {
+ $this->data[$key] = new static($value, $this->allowModifications);
+ } else {
+ $this->data[$key] = $value;
+ }
+
+ $this->count++;
+ }
+ }
+
+ /**
+ * Retrieve a value and return $default if there is no element set.
+ *
+ * @param string $name
+ * @param mixed $default
+ * @return mixed
+ */
+ public function get($name, $default = null)
+ {
+ if (array_key_exists($name, $this->data)) {
+ return $this->data[$name];
+ }
+
+ return $default;
+ }
+
+ /**
+ * Magic function so that $obj->value will work.
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ return $this->get($name);
+ }
+
+ /**
+ * Set a value in the config.
+ *
+ * Only allow setting of a property if $allowModifications was set to true
+ * on construction. Otherwise, throw an exception.
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return void
+ * @throws Exception\RuntimeException
+ */
+ public function __set($name, $value)
+ {
+ if ($this->allowModifications) {
+ if (is_array($value)) {
+ $value = new static($value, true);
+ }
+
+ if (null === $name) {
+ $this->data[] = $value;
+ } else {
+ $this->data[$name] = $value;
+ }
+
+ $this->count++;
+ } else {
+ throw new Exception\RuntimeException('Config is read only');
+ }
+ }
+
+ /**
+ * Deep clone of this instance to ensure that nested Zend\Configs are also
+ * cloned.
+ *
+ * @return void
+ */
+ public function __clone()
+ {
+ $array = array();
+
+ foreach ($this->data as $key => $value) {
+ if ($value instanceof self) {
+ $array[$key] = clone $value;
+ } else {
+ $array[$key] = $value;
+ }
+ }
+
+ $this->data = $array;
+ }
+
+ /**
+ * Return an associative array of the stored data.
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ $array = array();
+ $data = $this->data;
+
+ /** @var self $value */
+ foreach ($data as $key => $value) {
+ if ($value instanceof self) {
+ $array[$key] = $value->toArray();
+ } else {
+ $array[$key] = $value;
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * isset() overloading
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ return isset($this->data[$name]);
+ }
+
+ /**
+ * unset() overloading
+ *
+ * @param string $name
+ * @return void
+ * @throws Exception\InvalidArgumentException
+ */
+ public function __unset($name)
+ {
+ if (!$this->allowModifications) {
+ throw new Exception\InvalidArgumentException('Config is read only');
+ } elseif (isset($this->data[$name])) {
+ unset($this->data[$name]);
+ $this->count--;
+ $this->skipNextIteration = true;
+ }
+ }
+
+ /**
+ * count(): defined by Countable interface.
+ *
+ * @see Countable::count()
+ * @return int
+ */
+ public function count()
+ {
+ return $this->count;
+ }
+
+ /**
+ * current(): defined by Iterator interface.
+ *
+ * @see Iterator::current()
+ * @return mixed
+ */
+ public function current()
+ {
+ $this->skipNextIteration = false;
+ return current($this->data);
+ }
+
+ /**
+ * key(): defined by Iterator interface.
+ *
+ * @see Iterator::key()
+ * @return mixed
+ */
+ public function key()
+ {
+ return key($this->data);
+ }
+
+ /**
+ * next(): defined by Iterator interface.
+ *
+ * @see Iterator::next()
+ * @return void
+ */
+ public function next()
+ {
+ if ($this->skipNextIteration) {
+ $this->skipNextIteration = false;
+ return;
+ }
+
+ next($this->data);
+ }
+
+ /**
+ * rewind(): defined by Iterator interface.
+ *
+ * @see Iterator::rewind()
+ * @return void
+ */
+ public function rewind()
+ {
+ $this->skipNextIteration = false;
+ reset($this->data);
+ }
+
+ /**
+ * valid(): defined by Iterator interface.
+ *
+ * @see Iterator::valid()
+ * @return bool
+ */
+ public function valid()
+ {
+ return ($this->key() !== null);
+ }
+
+ /**
+ * offsetExists(): defined by ArrayAccess interface.
+ *
+ * @see ArrayAccess::offsetExists()
+ * @param mixed $offset
+ * @return bool
+ */
+ public function offsetExists($offset)
+ {
+ return $this->__isset($offset);
+ }
+
+ /**
+ * offsetGet(): defined by ArrayAccess interface.
+ *
+ * @see ArrayAccess::offsetGet()
+ * @param mixed $offset
+ * @return mixed
+ */
+ public function offsetGet($offset)
+ {
+ return $this->__get($offset);
+ }
+
+ /**
+ * offsetSet(): defined by ArrayAccess interface.
+ *
+ * @see ArrayAccess::offsetSet()
+ * @param mixed $offset
+ * @param mixed $value
+ * @return void
+ */
+ public function offsetSet($offset, $value)
+ {
+ $this->__set($offset, $value);
+ }
+
+ /**
+ * offsetUnset(): defined by ArrayAccess interface.
+ *
+ * @see ArrayAccess::offsetUnset()
+ * @param mixed $offset
+ * @return void
+ */
+ public function offsetUnset($offset)
+ {
+ $this->__unset($offset);
+ }
+
+ /**
+ * Merge another Config with this one.
+ *
+ * For duplicate keys, the following will be performed:
+ * - Nested Configs will be recursively merged.
+ * - Items in $merge with INTEGER keys will be appended.
+ * - Items in $merge with STRING keys will overwrite current values.
+ *
+ * @param Config $merge
+ * @return Config
+ */
+ public function merge(Config $merge)
+ {
+ /** @var Config $value */
+ foreach ($merge as $key => $value) {
+ if (array_key_exists($key, $this->data)) {
+ if (is_int($key)) {
+ $this->data[] = $value;
+ } elseif ($value instanceof self && $this->data[$key] instanceof self) {
+ $this->data[$key]->merge($value);
+ } else {
+ if ($value instanceof self) {
+ $this->data[$key] = new static($value->toArray(), $this->allowModifications);
+ } else {
+ $this->data[$key] = $value;
+ }
+ }
+ } else {
+ if ($value instanceof self) {
+ $this->data[$key] = new static($value->toArray(), $this->allowModifications);
+ } else {
+ $this->data[$key] = $value;
+ }
+
+ $this->count++;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Prevent any more modifications being made to this instance.
+ *
+ * Useful after merge() has been used to merge multiple Config objects
+ * into one object which should then not be modified again.
+ *
+ * @return void
+ */
+ public function setReadOnly()
+ {
+ $this->allowModifications = false;
+
+ /** @var Config $value */
+ foreach ($this->data as $value) {
+ if ($value instanceof self) {
+ $value->setReadOnly();
+ }
+ }
+ }
+
+ /**
+ * Returns whether this Config object is read only or not.
+ *
+ * @return bool
+ */
+ public function isReadOnly()
+ {
+ return !$this->allowModifications;
+ }
+}
diff --git a/library/Zend/Config/Exception/ExceptionInterface.php b/library/Zend/Config/Exception/ExceptionInterface.php
new file mode 100755
index 0000000000..d6e0240f10
--- /dev/null
+++ b/library/Zend/Config/Exception/ExceptionInterface.php
@@ -0,0 +1,14 @@
+ 'ini',
+ 'json' => 'json',
+ 'xml' => 'xml',
+ 'yaml' => 'yaml',
+ );
+
+ /**
+ * Register config file extensions for writing
+ * key is extension, value is writer instance or plugin name
+ *
+ * @var array
+ */
+ protected static $writerExtensions = array(
+ 'php' => 'php',
+ 'ini' => 'ini',
+ 'json' => 'json',
+ 'xml' => 'xml',
+ 'yaml' => 'yaml',
+ );
+
+ /**
+ * Read a config from a file.
+ *
+ * @param string $filename
+ * @param bool $returnConfigObject
+ * @param bool $useIncludePath
+ * @return array|Config
+ * @throws Exception\InvalidArgumentException
+ * @throws Exception\RuntimeException
+ */
+ public static function fromFile($filename, $returnConfigObject = false, $useIncludePath = false)
+ {
+ $filepath = $filename;
+ if (!file_exists($filename)) {
+ if (!$useIncludePath) {
+ throw new Exception\RuntimeException(sprintf(
+ 'Filename "%s" cannot be found relative to the working directory',
+ $filename
+ ));
+ }
+
+ $fromIncludePath = stream_resolve_include_path($filename);
+ if (!$fromIncludePath) {
+ throw new Exception\RuntimeException(sprintf(
+ 'Filename "%s" cannot be found relative to the working directory or the include_path ("%s")',
+ $filename,
+ get_include_path()
+ ));
+ }
+ $filepath = $fromIncludePath;
+ }
+
+ $pathinfo = pathinfo($filepath);
+
+ if (!isset($pathinfo['extension'])) {
+ throw new Exception\RuntimeException(sprintf(
+ 'Filename "%s" is missing an extension and cannot be auto-detected',
+ $filename
+ ));
+ }
+
+ $extension = strtolower($pathinfo['extension']);
+
+ if ($extension === 'php') {
+ if (!is_file($filepath) || !is_readable($filepath)) {
+ throw new Exception\RuntimeException(sprintf(
+ "File '%s' doesn't exist or not readable",
+ $filename
+ ));
+ }
+
+ $config = include $filepath;
+ } elseif (isset(static::$extensions[$extension])) {
+ $reader = static::$extensions[$extension];
+ if (!$reader instanceof Reader\ReaderInterface) {
+ $reader = static::getReaderPluginManager()->get($reader);
+ static::$extensions[$extension] = $reader;
+ }
+
+ /** @var Reader\ReaderInterface $reader */
+ $config = $reader->fromFile($filepath);
+ } else {
+ throw new Exception\RuntimeException(sprintf(
+ 'Unsupported config file extension: .%s',
+ $pathinfo['extension']
+ ));
+ }
+
+ return ($returnConfigObject) ? new Config($config) : $config;
+ }
+
+ /**
+ * Read configuration from multiple files and merge them.
+ *
+ * @param array $files
+ * @param bool $returnConfigObject
+ * @param bool $useIncludePath
+ * @return array|Config
+ */
+ public static function fromFiles(array $files, $returnConfigObject = false, $useIncludePath = false)
+ {
+ $config = array();
+
+ foreach ($files as $file) {
+ $config = ArrayUtils::merge($config, static::fromFile($file, false, $useIncludePath));
+ }
+
+ return ($returnConfigObject) ? new Config($config) : $config;
+ }
+
+ /**
+ * Writes a config to a file
+ *
+ * @param string $filename
+ * @param array|Config $config
+ * @return bool TRUE on success | FALSE on failure
+ * @throws Exception\RuntimeException
+ * @throws Exception\InvalidArgumentException
+ */
+ public static function toFile($filename, $config)
+ {
+ if (
+ (is_object($config) && !($config instanceof Config)) ||
+ (!is_object($config) && !is_array($config))
+ ) {
+ throw new Exception\InvalidArgumentException(
+ __METHOD__." \$config should be an array or instance of Zend\\Config\\Config"
+ );
+ }
+
+ $extension = substr(strrchr($filename, '.'), 1);
+ $directory = dirname($filename);
+
+ if (!is_dir($directory)) {
+ throw new Exception\RuntimeException(
+ "Directory '{$directory}' does not exists!"
+ );
+ }
+
+ if (!is_writable($directory)) {
+ throw new Exception\RuntimeException(
+ "Cannot write in directory '{$directory}'"
+ );
+ }
+
+ if (!isset(static::$writerExtensions[$extension])) {
+ throw new Exception\RuntimeException(
+ "Unsupported config file extension: '.{$extension}' for writing."
+ );
+ }
+
+ $writer = static::$writerExtensions[$extension];
+ if (($writer instanceof Writer\AbstractWriter) === false) {
+ $writer = self::getWriterPluginManager()->get($writer);
+ static::$writerExtensions[$extension] = $writer;
+ }
+
+ if (is_object($config)) {
+ $config = $config->toArray();
+ }
+
+ $content = $writer->processConfig($config);
+
+ return (bool) (file_put_contents($filename, $content) !== false);
+ }
+
+ /**
+ * Set reader plugin manager
+ *
+ * @param ReaderPluginManager $readers
+ * @return void
+ */
+ public static function setReaderPluginManager(ReaderPluginManager $readers)
+ {
+ static::$readers = $readers;
+ }
+
+ /**
+ * Get the reader plugin manager
+ *
+ * @return ReaderPluginManager
+ */
+ public static function getReaderPluginManager()
+ {
+ if (static::$readers === null) {
+ static::$readers = new ReaderPluginManager();
+ }
+ return static::$readers;
+ }
+
+ /**
+ * Set writer plugin manager
+ *
+ * @param WriterPluginManager $writers
+ * @return void
+ */
+ public static function setWriterPluginManager(WriterPluginManager $writers)
+ {
+ static::$writers = $writers;
+ }
+
+ /**
+ * Get the writer plugin manager
+ *
+ * @return WriterPluginManager
+ */
+ public static function getWriterPluginManager()
+ {
+ if (static::$writers === null) {
+ static::$writers = new WriterPluginManager();
+ }
+
+ return static::$writers;
+ }
+
+ /**
+ * Set config reader for file extension
+ *
+ * @param string $extension
+ * @param string|Reader\ReaderInterface $reader
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public static function registerReader($extension, $reader)
+ {
+ $extension = strtolower($extension);
+
+ if (!is_string($reader) && !$reader instanceof Reader\ReaderInterface) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Reader should be plugin name, class name or ' .
+ 'instance of %s\Reader\ReaderInterface; received "%s"',
+ __NAMESPACE__,
+ (is_object($reader) ? get_class($reader) : gettype($reader))
+ ));
+ }
+
+ static::$extensions[$extension] = $reader;
+ }
+
+ /**
+ * Set config writer for file extension
+ *
+ * @param string $extension
+ * @param string|Writer\AbstractWriter $writer
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public static function registerWriter($extension, $writer)
+ {
+ $extension = strtolower($extension);
+
+ if (!is_string($writer) && !$writer instanceof Writer\AbstractWriter) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Writer should be plugin name, class name or ' .
+ 'instance of %s\Writer\AbstractWriter; received "%s"',
+ __NAMESPACE__,
+ (is_object($writer) ? get_class($writer) : gettype($writer))
+ ));
+ }
+
+ static::$writerExtensions[$extension] = $writer;
+ }
+}
diff --git a/library/Zend/Config/Processor/Constant.php b/library/Zend/Config/Processor/Constant.php
new file mode 100755
index 0000000000..28f76b9a75
--- /dev/null
+++ b/library/Zend/Config/Processor/Constant.php
@@ -0,0 +1,83 @@
+setUserOnly($userOnly);
+ $this->setPrefix($prefix);
+ $this->setSuffix($suffix);
+
+ $this->loadConstants();
+ }
+
+ /**
+ * @return bool
+ */
+ public function getUserOnly()
+ {
+ return $this->userOnly;
+ }
+
+ /**
+ * Should we use only user-defined constants?
+ *
+ * @param bool $userOnly
+ * @return Constant
+ */
+ public function setUserOnly($userOnly)
+ {
+ $this->userOnly = (bool) $userOnly;
+ return $this;
+ }
+
+ /**
+ * Load all currently defined constants into parser.
+ *
+ * @return void
+ */
+ public function loadConstants()
+ {
+ if ($this->userOnly) {
+ $constants = get_defined_constants(true);
+ $constants = isset($constants['user']) ? $constants['user'] : array();
+ $this->setTokens($constants);
+ } else {
+ $this->setTokens(get_defined_constants());
+ }
+ }
+
+ /**
+ * Get current token registry.
+ * @return array
+ */
+ public function getTokens()
+ {
+ return $this->tokens;
+ }
+}
diff --git a/library/Zend/Config/Processor/Filter.php b/library/Zend/Config/Processor/Filter.php
new file mode 100755
index 0000000000..99909f289a
--- /dev/null
+++ b/library/Zend/Config/Processor/Filter.php
@@ -0,0 +1,88 @@
+setFilter($filter);
+ }
+
+ /**
+ * @param ZendFilter $filter
+ * @return Filter
+ */
+ public function setFilter(ZendFilter $filter)
+ {
+ $this->filter = $filter;
+ return $this;
+ }
+
+ /**
+ * @return ZendFilter
+ */
+ public function getFilter()
+ {
+ return $this->filter;
+ }
+
+ /**
+ * Process
+ *
+ * @param Config $config
+ * @return Config
+ * @throws Exception\InvalidArgumentException
+ */
+ public function process(Config $config)
+ {
+ if ($config->isReadOnly()) {
+ throw new Exception\InvalidArgumentException('Cannot process config because it is read-only');
+ }
+
+ /**
+ * Walk through config and replace values
+ */
+ foreach ($config as $key => $val) {
+ if ($val instanceof Config) {
+ $this->process($val);
+ } else {
+ $config->$key = $this->filter->filter($val);
+ }
+ }
+
+ return $config;
+ }
+
+ /**
+ * Process a single value
+ *
+ * @param mixed $value
+ * @return mixed
+ */
+ public function processValue($value)
+ {
+ return $this->filter->filter($value);
+ }
+}
diff --git a/library/Zend/Config/Processor/ProcessorInterface.php b/library/Zend/Config/Processor/ProcessorInterface.php
new file mode 100755
index 0000000000..6aa28e91c0
--- /dev/null
+++ b/library/Zend/Config/Processor/ProcessorInterface.php
@@ -0,0 +1,31 @@
+isReadOnly()) {
+ throw new Exception\InvalidArgumentException('Cannot process config because it is read-only');
+ }
+
+ foreach ($this as $parser) {
+ /** @var $parser ProcessorInterface */
+ $parser->process($config);
+ }
+ }
+
+ /**
+ * Process a single value
+ *
+ * @param mixed $value
+ * @return mixed
+ */
+ public function processValue($value)
+ {
+ foreach ($this as $parser) {
+ /** @var $parser ProcessorInterface */
+ $value = $parser->processValue($value);
+ }
+
+ return $value;
+ }
+}
diff --git a/library/Zend/Config/Processor/Token.php b/library/Zend/Config/Processor/Token.php
new file mode 100755
index 0000000000..2af2e1b3f4
--- /dev/null
+++ b/library/Zend/Config/Processor/Token.php
@@ -0,0 +1,274 @@
+ value
+ * to replace it with
+ * @param string $prefix
+ * @param string $suffix
+ * @internal param array $options
+ * @return Token
+ */
+ public function __construct($tokens = array(), $prefix = '', $suffix = '')
+ {
+ $this->setTokens($tokens);
+ $this->setPrefix($prefix);
+ $this->setSuffix($suffix);
+ }
+
+ /**
+ * @param string $prefix
+ * @return Token
+ */
+ public function setPrefix($prefix)
+ {
+ // reset map
+ $this->map = null;
+ $this->prefix = $prefix;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPrefix()
+ {
+ return $this->prefix;
+ }
+
+ /**
+ * @param string $suffix
+ * @return Token
+ */
+ public function setSuffix($suffix)
+ {
+ // reset map
+ $this->map = null;
+ $this->suffix = $suffix;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSuffix()
+ {
+ return $this->suffix;
+ }
+
+ /**
+ * Set token registry.
+ *
+ * @param array|Config|Traversable $tokens Associative array of TOKEN => value
+ * to replace it with
+ * @return Token
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setTokens($tokens)
+ {
+ if (is_array($tokens)) {
+ $this->tokens = $tokens;
+ } elseif ($tokens instanceof Config) {
+ $this->tokens = $tokens->toArray();
+ } elseif ($tokens instanceof Traversable) {
+ $this->tokens = array();
+ foreach ($tokens as $key => $val) {
+ $this->tokens[$key] = $val;
+ }
+ } else {
+ throw new Exception\InvalidArgumentException('Cannot use ' . gettype($tokens) . ' as token registry.');
+ }
+
+ // reset map
+ $this->map = null;
+
+ return $this;
+ }
+
+ /**
+ * Get current token registry.
+ *
+ * @return array
+ */
+ public function getTokens()
+ {
+ return $this->tokens;
+ }
+
+ /**
+ * Add new token.
+ *
+ * @param string $token
+ * @param mixed $value
+ * @return Token
+ * @throws Exception\InvalidArgumentException
+ */
+ public function addToken($token, $value)
+ {
+ if (!is_scalar($token)) {
+ throw new Exception\InvalidArgumentException('Cannot use ' . gettype($token) . ' as token name.');
+ }
+ $this->tokens[$token] = $value;
+
+ // reset map
+ $this->map = null;
+
+ return $this;
+ }
+
+ /**
+ * Add new token.
+ *
+ * @param string $token
+ * @param mixed $value
+ * @return Token
+ */
+ public function setToken($token, $value)
+ {
+ return $this->addToken($token, $value);
+ }
+
+ /**
+ * Build replacement map
+ *
+ * @return array
+ */
+ protected function buildMap()
+ {
+ if (null === $this->map) {
+ if (!$this->suffix && !$this->prefix) {
+ $this->map = $this->tokens;
+ } else {
+ $this->map = array();
+
+ foreach ($this->tokens as $token => $value) {
+ $this->map[$this->prefix . $token . $this->suffix] = $value;
+ }
+ }
+
+ foreach (array_keys($this->map) as $key) {
+ if (empty($key)) {
+ unset($this->map[$key]);
+ }
+ }
+ }
+
+ return $this->map;
+ }
+
+ /**
+ * Process
+ *
+ * @param Config $config
+ * @return Config
+ * @throws Exception\InvalidArgumentException
+ */
+ public function process(Config $config)
+ {
+ return $this->doProcess($config, $this->buildMap());
+ }
+
+ /**
+ * Process a single value
+ *
+ * @param $value
+ * @return mixed
+ */
+ public function processValue($value)
+ {
+ return $this->doProcess($value, $this->buildMap());
+ }
+
+ /**
+ * Applies replacement map to the given value by modifying the value itself
+ *
+ * @param mixed $value
+ * @param array $replacements
+ *
+ * @return mixed
+ *
+ * @throws Exception\InvalidArgumentException if the provided value is a read-only {@see Config}
+ */
+ private function doProcess($value, array $replacements)
+ {
+ if ($value instanceof Config) {
+ if ($value->isReadOnly()) {
+ throw new Exception\InvalidArgumentException('Cannot process config because it is read-only');
+ }
+
+ foreach ($value as $key => $val) {
+ $value->$key = $this->doProcess($val, $replacements);
+ }
+
+ return $value;
+ }
+
+ if ($value instanceof Traversable || is_array($value)) {
+ foreach ($value as & $val) {
+ $val = $this->doProcess($val, $replacements);
+ }
+
+ return $value;
+ }
+
+ if (!is_string($value) && (is_bool($value) || is_numeric($value))) {
+ $stringVal = (string) $value;
+ $changedVal = strtr($value, $this->map);
+
+ // replace the value only if a string replacement occurred
+ if ($changedVal !== $stringVal) {
+ return $changedVal;
+ }
+
+ return $value;
+ }
+
+ return strtr((string) $value, $this->map);
+ }
+}
diff --git a/library/Zend/Config/Processor/Translator.php b/library/Zend/Config/Processor/Translator.php
new file mode 100755
index 0000000000..48c0985de1
--- /dev/null
+++ b/library/Zend/Config/Processor/Translator.php
@@ -0,0 +1,139 @@
+setTranslator($translator);
+ $this->setTextDomain($textDomain);
+ $this->setLocale($locale);
+ }
+
+ /**
+ * @param ZendTranslator $translator
+ * @return Translator
+ */
+ public function setTranslator(ZendTranslator $translator)
+ {
+ $this->translator = $translator;
+ return $this;
+ }
+
+ /**
+ * @return ZendTranslator
+ */
+ public function getTranslator()
+ {
+ return $this->translator;
+ }
+
+ /**
+ * @param string|null $locale
+ * @return Translator
+ */
+ public function setLocale($locale)
+ {
+ $this->locale = $locale;
+ return $this;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getLocale()
+ {
+ return $this->locale;
+ }
+
+ /**
+ * @param string $textDomain
+ * @return Translator
+ */
+ public function setTextDomain($textDomain)
+ {
+ $this->textDomain = $textDomain;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTextDomain()
+ {
+ return $this->textDomain;
+ }
+
+ /**
+ * Process
+ *
+ * @param Config $config
+ * @return Config
+ * @throws Exception\InvalidArgumentException
+ */
+ public function process(Config $config)
+ {
+ if ($config->isReadOnly()) {
+ throw new Exception\InvalidArgumentException('Cannot process config because it is read-only');
+ }
+
+ /**
+ * Walk through config and replace values
+ */
+ foreach ($config as $key => $val) {
+ if ($val instanceof Config) {
+ $this->process($val);
+ } else {
+ $config->{$key} = $this->translator->translate($val, $this->textDomain, $this->locale);
+ }
+ }
+
+ return $config;
+ }
+
+ /**
+ * Process a single value
+ *
+ * @param $value
+ * @return string
+ */
+ public function processValue($value)
+ {
+ return $this->translator->translate($value, $this->textDomain, $this->locale);
+ }
+}
diff --git a/library/Zend/Config/README.md b/library/Zend/Config/README.md
new file mode 100755
index 0000000000..650ba568d8
--- /dev/null
+++ b/library/Zend/Config/README.md
@@ -0,0 +1,15 @@
+Config Component from ZF2
+=========================
+
+This is the Config component for ZF2.
+
+- File issues at https://github.com/zendframework/zf2/issues
+- Create pull requests against https://github.com/zendframework/zf2
+- Documentation is at http://framework.zend.com/docs
+
+LICENSE
+-------
+
+The files in this archive are released under the [Zend Framework
+license](http://framework.zend.com/license), which is a 3-clause BSD license.
+
diff --git a/library/Zend/Config/Reader/Ini.php b/library/Zend/Config/Reader/Ini.php
new file mode 100755
index 0000000000..343668549c
--- /dev/null
+++ b/library/Zend/Config/Reader/Ini.php
@@ -0,0 +1,225 @@
+nestSeparator = $separator;
+ return $this;
+ }
+
+ /**
+ * Get nest separator.
+ *
+ * @return string
+ */
+ public function getNestSeparator()
+ {
+ return $this->nestSeparator;
+ }
+
+ /**
+ * fromFile(): defined by Reader interface.
+ *
+ * @see ReaderInterface::fromFile()
+ * @param string $filename
+ * @return array
+ * @throws Exception\RuntimeException
+ */
+ public function fromFile($filename)
+ {
+ if (!is_file($filename) || !is_readable($filename)) {
+ throw new Exception\RuntimeException(sprintf(
+ "File '%s' doesn't exist or not readable",
+ $filename
+ ));
+ }
+
+ $this->directory = dirname($filename);
+
+ set_error_handler(
+ function ($error, $message = '', $file = '', $line = 0) use ($filename) {
+ throw new Exception\RuntimeException(
+ sprintf('Error reading INI file "%s": %s', $filename, $message),
+ $error
+ );
+ },
+ E_WARNING
+ );
+ $ini = parse_ini_file($filename, true);
+ restore_error_handler();
+
+ return $this->process($ini);
+ }
+
+ /**
+ * fromString(): defined by Reader interface.
+ *
+ * @param string $string
+ * @return array|bool
+ * @throws Exception\RuntimeException
+ */
+ public function fromString($string)
+ {
+ if (empty($string)) {
+ return array();
+ }
+ $this->directory = null;
+
+ set_error_handler(
+ function ($error, $message = '', $file = '', $line = 0) {
+ throw new Exception\RuntimeException(
+ sprintf('Error reading INI string: %s', $message),
+ $error
+ );
+ },
+ E_WARNING
+ );
+ $ini = parse_ini_string($string, true);
+ restore_error_handler();
+
+ return $this->process($ini);
+ }
+
+ /**
+ * Process data from the parsed ini file.
+ *
+ * @param array $data
+ * @return array
+ */
+ protected function process(array $data)
+ {
+ $config = array();
+
+ foreach ($data as $section => $value) {
+ if (is_array($value)) {
+ if (strpos($section, $this->nestSeparator) !== false) {
+ $sections = explode($this->nestSeparator, $section);
+ $config = array_merge_recursive($config, $this->buildNestedSection($sections, $value));
+ } else {
+ $config[$section] = $this->processSection($value);
+ }
+ } else {
+ $this->processKey($section, $value, $config);
+ }
+ }
+
+ return $config;
+ }
+
+ /**
+ * Process a nested section
+ *
+ * @param array $sections
+ * @param mixed $value
+ * @return array
+ */
+ private function buildNestedSection($sections, $value)
+ {
+ if (count($sections) == 0) {
+ return $this->processSection($value);
+ }
+
+ $nestedSection = array();
+
+ $first = array_shift($sections);
+ $nestedSection[$first] = $this->buildNestedSection($sections, $value);
+
+ return $nestedSection;
+ }
+
+ /**
+ * Process a section.
+ *
+ * @param array $section
+ * @return array
+ */
+ protected function processSection(array $section)
+ {
+ $config = array();
+
+ foreach ($section as $key => $value) {
+ $this->processKey($key, $value, $config);
+ }
+
+ return $config;
+ }
+
+ /**
+ * Process a key.
+ *
+ * @param string $key
+ * @param string $value
+ * @param array $config
+ * @return array
+ * @throws Exception\RuntimeException
+ */
+ protected function processKey($key, $value, array &$config)
+ {
+ if (strpos($key, $this->nestSeparator) !== false) {
+ $pieces = explode($this->nestSeparator, $key, 2);
+
+ if (!strlen($pieces[0]) || !strlen($pieces[1])) {
+ throw new Exception\RuntimeException(sprintf('Invalid key "%s"', $key));
+ } elseif (!isset($config[$pieces[0]])) {
+ if ($pieces[0] === '0' && !empty($config)) {
+ $config = array($pieces[0] => $config);
+ } else {
+ $config[$pieces[0]] = array();
+ }
+ } elseif (!is_array($config[$pieces[0]])) {
+ throw new Exception\RuntimeException(
+ sprintf('Cannot create sub-key for "%s", as key already exists', $pieces[0])
+ );
+ }
+
+ $this->processKey($pieces[1], $value, $config[$pieces[0]]);
+ } else {
+ if ($key === '@include') {
+ if ($this->directory === null) {
+ throw new Exception\RuntimeException('Cannot process @include statement for a string config');
+ }
+
+ $reader = clone $this;
+ $include = $reader->fromFile($this->directory . '/' . $value);
+ $config = array_replace_recursive($config, $include);
+ } else {
+ $config[$key] = $value;
+ }
+ }
+ }
+}
diff --git a/library/Zend/Config/Reader/JavaProperties.php b/library/Zend/Config/Reader/JavaProperties.php
new file mode 100755
index 0000000000..965bd3c802
--- /dev/null
+++ b/library/Zend/Config/Reader/JavaProperties.php
@@ -0,0 +1,138 @@
+directory = dirname($filename);
+
+ $config = $this->parse(file_get_contents($filename));
+
+ return $this->process($config);
+ }
+
+ /**
+ * fromString(): defined by Reader interface.
+ *
+ * @see ReaderInterface::fromString()
+ * @param string $string
+ * @return array
+ * @throws Exception\RuntimeException if an @include key is found
+ */
+ public function fromString($string)
+ {
+ if (empty($string)) {
+ return array();
+ }
+
+ $this->directory = null;
+
+ $config = $this->parse($string);
+
+ return $this->process($config);
+ }
+
+ /**
+ * Process the array for @include
+ *
+ * @param array $data
+ * @return array
+ * @throws Exception\RuntimeException if an @include key is found
+ */
+ protected function process(array $data)
+ {
+ foreach ($data as $key => $value) {
+ if (trim($key) === '@include') {
+ if ($this->directory === null) {
+ throw new Exception\RuntimeException('Cannot process @include statement for a string');
+ }
+ $reader = clone $this;
+ unset($data[$key]);
+ $data = array_replace_recursive($data, $reader->fromFile($this->directory . '/' . $value));
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * Parse Java-style properties string
+ *
+ * @todo Support use of the equals sign "key=value" as key-value delimiter
+ * @todo Ignore whitespace that precedes text past the first line of multiline values
+ *
+ * @param string $string
+ * @return array
+ */
+ protected function parse($string)
+ {
+ $result = array();
+ $lines = explode("\n", $string);
+ $key = "";
+ $isWaitingOtherLine = false;
+ foreach ($lines as $i => $line) {
+ // Ignore empty lines and commented lines
+ if (empty($line)
+ || (!$isWaitingOtherLine && strpos($line, "#") === 0)
+ || (!$isWaitingOtherLine && strpos($line, "!") === 0)) {
+ continue;
+ }
+
+ // Add a new key-value pair or append value to a previous pair
+ if (!$isWaitingOtherLine) {
+ $key = substr($line, 0, strpos($line, ':'));
+ $value = substr($line, strpos($line, ':') + 1, strlen($line));
+ } else {
+ $value .= $line;
+ }
+
+ // Check if ends with single '\' (indicating another line is expected)
+ if (strrpos($value, "\\") === strlen($value) - strlen("\\")) {
+ $value = substr($value, 0, strlen($value) - 1);
+ $isWaitingOtherLine = true;
+ } else {
+ $isWaitingOtherLine = false;
+ }
+
+ $result[$key] = stripslashes($value);
+ unset($lines[$i]);
+ }
+
+ return $result;
+ }
+}
diff --git a/library/Zend/Config/Reader/Json.php b/library/Zend/Config/Reader/Json.php
new file mode 100755
index 0000000000..407e2aafa5
--- /dev/null
+++ b/library/Zend/Config/Reader/Json.php
@@ -0,0 +1,105 @@
+directory = dirname($filename);
+
+ try {
+ $config = JsonFormat::decode(file_get_contents($filename), JsonFormat::TYPE_ARRAY);
+ } catch (JsonException\RuntimeException $e) {
+ throw new Exception\RuntimeException($e->getMessage());
+ }
+
+ return $this->process($config);
+ }
+
+ /**
+ * fromString(): defined by Reader interface.
+ *
+ * @see ReaderInterface::fromString()
+ * @param string $string
+ * @return array|bool
+ * @throws Exception\RuntimeException
+ */
+ public function fromString($string)
+ {
+ if (empty($string)) {
+ return array();
+ }
+
+ $this->directory = null;
+
+ try {
+ $config = JsonFormat::decode($string, JsonFormat::TYPE_ARRAY);
+ } catch (JsonException\RuntimeException $e) {
+ throw new Exception\RuntimeException($e->getMessage());
+ }
+
+ return $this->process($config);
+ }
+
+ /**
+ * Process the array for @include
+ *
+ * @param array $data
+ * @return array
+ * @throws Exception\RuntimeException
+ */
+ protected function process(array $data)
+ {
+ foreach ($data as $key => $value) {
+ if (is_array($value)) {
+ $data[$key] = $this->process($value);
+ }
+ if (trim($key) === '@include') {
+ if ($this->directory === null) {
+ throw new Exception\RuntimeException('Cannot process @include statement for a JSON string');
+ }
+ $reader = clone $this;
+ unset($data[$key]);
+ $data = array_replace_recursive($data, $reader->fromFile($this->directory . '/' . $value));
+ }
+ }
+ return $data;
+ }
+}
diff --git a/library/Zend/Config/Reader/ReaderInterface.php b/library/Zend/Config/Reader/ReaderInterface.php
new file mode 100755
index 0000000000..0393fe0528
--- /dev/null
+++ b/library/Zend/Config/Reader/ReaderInterface.php
@@ -0,0 +1,29 @@
+reader = new XMLReader();
+ $this->reader->open($filename, null, LIBXML_XINCLUDE);
+
+ $this->directory = dirname($filename);
+
+ set_error_handler(
+ function ($error, $message = '', $file = '', $line = 0) use ($filename) {
+ throw new Exception\RuntimeException(
+ sprintf('Error reading XML file "%s": %s', $filename, $message),
+ $error
+ );
+ },
+ E_WARNING
+ );
+ $return = $this->process();
+ restore_error_handler();
+
+ return $return;
+ }
+
+ /**
+ * fromString(): defined by Reader interface.
+ *
+ * @see ReaderInterface::fromString()
+ * @param string $string
+ * @return array|bool
+ * @throws Exception\RuntimeException
+ */
+ public function fromString($string)
+ {
+ if (empty($string)) {
+ return array();
+ }
+ $this->reader = new XMLReader();
+
+ $this->reader->xml($string, null, LIBXML_XINCLUDE);
+
+ $this->directory = null;
+
+ set_error_handler(
+ function ($error, $message = '', $file = '', $line = 0) {
+ throw new Exception\RuntimeException(
+ sprintf('Error reading XML string: %s', $message),
+ $error
+ );
+ },
+ E_WARNING
+ );
+ $return = $this->process();
+ restore_error_handler();
+
+ return $return;
+ }
+
+ /**
+ * Process data from the created XMLReader.
+ *
+ * @return array
+ */
+ protected function process()
+ {
+ return $this->processNextElement();
+ }
+
+ /**
+ * Process the next inner element.
+ *
+ * @return mixed
+ */
+ protected function processNextElement()
+ {
+ $children = array();
+ $text = '';
+
+ while ($this->reader->read()) {
+ if ($this->reader->nodeType === XMLReader::ELEMENT) {
+ if ($this->reader->depth === 0) {
+ return $this->processNextElement();
+ }
+
+ $attributes = $this->getAttributes();
+ $name = $this->reader->name;
+
+ if ($this->reader->isEmptyElement) {
+ $child = array();
+ } else {
+ $child = $this->processNextElement();
+ }
+
+ if ($attributes) {
+ if (is_string($child)) {
+ $child = array('_' => $child);
+ }
+
+ if (! is_array($child) ) {
+ $child = array();
+ }
+
+ $child = array_merge($child, $attributes);
+ }
+
+ if (isset($children[$name])) {
+ if (!is_array($children[$name]) || !array_key_exists(0, $children[$name])) {
+ $children[$name] = array($children[$name]);
+ }
+
+ $children[$name][] = $child;
+ } else {
+ $children[$name] = $child;
+ }
+ } elseif ($this->reader->nodeType === XMLReader::END_ELEMENT) {
+ break;
+ } elseif (in_array($this->reader->nodeType, $this->textNodes)) {
+ $text .= $this->reader->value;
+ }
+ }
+
+ return $children ?: $text;
+ }
+
+ /**
+ * Get all attributes on the current node.
+ *
+ * @return array
+ */
+ protected function getAttributes()
+ {
+ $attributes = array();
+
+ if ($this->reader->hasAttributes) {
+ while ($this->reader->moveToNextAttribute()) {
+ $attributes[$this->reader->localName] = $this->reader->value;
+ }
+
+ $this->reader->moveToElement();
+ }
+
+ return $attributes;
+ }
+}
diff --git a/library/Zend/Config/Reader/Yaml.php b/library/Zend/Config/Reader/Yaml.php
new file mode 100755
index 0000000000..709b8b8a69
--- /dev/null
+++ b/library/Zend/Config/Reader/Yaml.php
@@ -0,0 +1,159 @@
+setYamlDecoder($yamlDecoder);
+ } else {
+ if (function_exists('yaml_parse')) {
+ $this->setYamlDecoder('yaml_parse');
+ }
+ }
+ }
+
+ /**
+ * Set callback for decoding YAML
+ *
+ * @param string|callable $yamlDecoder the decoder to set
+ * @return Yaml
+ * @throws Exception\RuntimeException
+ */
+ public function setYamlDecoder($yamlDecoder)
+ {
+ if (!is_callable($yamlDecoder)) {
+ throw new Exception\RuntimeException(
+ 'Invalid parameter to setYamlDecoder() - must be callable'
+ );
+ }
+ $this->yamlDecoder = $yamlDecoder;
+ return $this;
+ }
+
+ /**
+ * Get callback for decoding YAML
+ *
+ * @return callable
+ */
+ public function getYamlDecoder()
+ {
+ return $this->yamlDecoder;
+ }
+
+ /**
+ * fromFile(): defined by Reader interface.
+ *
+ * @see ReaderInterface::fromFile()
+ * @param string $filename
+ * @return array
+ * @throws Exception\RuntimeException
+ */
+ public function fromFile($filename)
+ {
+ if (!is_file($filename) || !is_readable($filename)) {
+ throw new Exception\RuntimeException(sprintf(
+ "File '%s' doesn't exist or not readable",
+ $filename
+ ));
+ }
+
+ if (null === $this->getYamlDecoder()) {
+ throw new Exception\RuntimeException("You didn't specify a Yaml callback decoder");
+ }
+
+ $this->directory = dirname($filename);
+
+ $config = call_user_func($this->getYamlDecoder(), file_get_contents($filename));
+ if (null === $config) {
+ throw new Exception\RuntimeException("Error parsing YAML data");
+ }
+
+ return $this->process($config);
+ }
+
+ /**
+ * fromString(): defined by Reader interface.
+ *
+ * @see ReaderInterface::fromString()
+ * @param string $string
+ * @return array|bool
+ * @throws Exception\RuntimeException
+ */
+ public function fromString($string)
+ {
+ if (null === $this->getYamlDecoder()) {
+ throw new Exception\RuntimeException("You didn't specify a Yaml callback decoder");
+ }
+ if (empty($string)) {
+ return array();
+ }
+
+ $this->directory = null;
+
+ $config = call_user_func($this->getYamlDecoder(), $string);
+ if (null === $config) {
+ throw new Exception\RuntimeException("Error parsing YAML data");
+ }
+
+ return $this->process($config);
+ }
+
+ /**
+ * Process the array for @include
+ *
+ * @param array $data
+ * @return array
+ * @throws Exception\RuntimeException
+ */
+ protected function process(array $data)
+ {
+ foreach ($data as $key => $value) {
+ if (is_array($value)) {
+ $data[$key] = $this->process($value);
+ }
+ if (trim($key) === '@include') {
+ if ($this->directory === null) {
+ throw new Exception\RuntimeException('Cannot process @include statement for a json string');
+ }
+ $reader = clone $this;
+ unset($data[$key]);
+ $data = array_replace_recursive($data, $reader->fromFile($this->directory . '/' . $value));
+ }
+ }
+ return $data;
+ }
+}
diff --git a/library/Zend/Config/ReaderPluginManager.php b/library/Zend/Config/ReaderPluginManager.php
new file mode 100755
index 0000000000..3e74a5640c
--- /dev/null
+++ b/library/Zend/Config/ReaderPluginManager.php
@@ -0,0 +1,49 @@
+ 'Zend\Config\Reader\Ini',
+ 'json' => 'Zend\Config\Reader\Json',
+ 'xml' => 'Zend\Config\Reader\Xml',
+ 'yaml' => 'Zend\Config\Reader\Yaml',
+ );
+
+ /**
+ * Validate the plugin
+ * Checks that the reader loaded is an instance of Reader\ReaderInterface.
+ *
+ * @param Reader\ReaderInterface $plugin
+ * @return void
+ * @throws Exception\InvalidArgumentException if invalid
+ */
+ public function validatePlugin($plugin)
+ {
+ if ($plugin instanceof Reader\ReaderInterface) {
+ // we're okay
+ return;
+ }
+
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Plugin of type %s is invalid; must implement %s\Reader\ReaderInterface',
+ (is_object($plugin) ? get_class($plugin) : gettype($plugin)),
+ __NAMESPACE__
+ ));
+ }
+}
diff --git a/library/Zend/Config/Writer/AbstractWriter.php b/library/Zend/Config/Writer/AbstractWriter.php
new file mode 100755
index 0000000000..a02111f7db
--- /dev/null
+++ b/library/Zend/Config/Writer/AbstractWriter.php
@@ -0,0 +1,84 @@
+toString($config), $flags);
+ } catch (\Exception $e) {
+ restore_error_handler();
+ throw $e;
+ }
+
+ restore_error_handler();
+ }
+
+ /**
+ * toString(): defined by Writer interface.
+ *
+ * @see WriterInterface::toString()
+ * @param mixed $config
+ * @return string
+ * @throws Exception\InvalidArgumentException
+ */
+ public function toString($config)
+ {
+ if ($config instanceof Traversable) {
+ $config = ArrayUtils::iteratorToArray($config);
+ } elseif (!is_array($config)) {
+ throw new Exception\InvalidArgumentException(__METHOD__ . ' expects an array or Traversable config');
+ }
+
+ return $this->processConfig($config);
+ }
+
+ /**
+ * @param array $config
+ * @return string
+ */
+ abstract protected function processConfig(array $config);
+}
diff --git a/library/Zend/Config/Writer/Ini.php b/library/Zend/Config/Writer/Ini.php
new file mode 100755
index 0000000000..9244f73e62
--- /dev/null
+++ b/library/Zend/Config/Writer/Ini.php
@@ -0,0 +1,183 @@
+nestSeparator = $separator;
+ return $this;
+ }
+
+ /**
+ * Get nest separator.
+ *
+ * @return string
+ */
+ public function getNestSeparator()
+ {
+ return $this->nestSeparator;
+ }
+
+ /**
+ * Set if rendering should occur without sections or not.
+ *
+ * If set to true, the INI file is rendered without sections completely
+ * into the global namespace of the INI file.
+ *
+ * @param bool $withoutSections
+ * @return Ini
+ */
+ public function setRenderWithoutSectionsFlags($withoutSections)
+ {
+ $this->renderWithoutSections = (bool) $withoutSections;
+ return $this;
+ }
+
+ /**
+ * Return whether the writer should render without sections.
+ *
+ * @return bool
+ */
+ public function shouldRenderWithoutSections()
+ {
+ return $this->renderWithoutSections;
+ }
+
+ /**
+ * processConfig(): defined by AbstractWriter.
+ *
+ * @param array $config
+ * @return string
+ */
+ public function processConfig(array $config)
+ {
+ $iniString = '';
+
+ if ($this->shouldRenderWithoutSections()) {
+ $iniString .= $this->addBranch($config);
+ } else {
+ $config = $this->sortRootElements($config);
+
+ foreach ($config as $sectionName => $data) {
+ if (!is_array($data)) {
+ $iniString .= $sectionName
+ . ' = '
+ . $this->prepareValue($data)
+ . "\n";
+ } else {
+ $iniString .= '[' . $sectionName . ']' . "\n"
+ . $this->addBranch($data)
+ . "\n";
+ }
+ }
+ }
+
+ return $iniString;
+ }
+
+ /**
+ * Add a branch to an INI string recursively.
+ *
+ * @param array $config
+ * @param array $parents
+ * @return string
+ */
+ protected function addBranch(array $config, $parents = array())
+ {
+ $iniString = '';
+
+ foreach ($config as $key => $value) {
+ $group = array_merge($parents, array($key));
+
+ if (is_array($value)) {
+ $iniString .= $this->addBranch($value, $group);
+ } else {
+ $iniString .= implode($this->nestSeparator, $group)
+ . ' = '
+ . $this->prepareValue($value)
+ . "\n";
+ }
+ }
+
+ return $iniString;
+ }
+
+ /**
+ * Prepare a value for INI.
+ *
+ * @param mixed $value
+ * @return string
+ * @throws Exception\RuntimeException
+ */
+ protected function prepareValue($value)
+ {
+ if (is_int($value) || is_float($value)) {
+ return $value;
+ } elseif (is_bool($value)) {
+ return ($value ? 'true' : 'false');
+ } elseif (false === strpos($value, '"')) {
+ return '"' . $value . '"';
+ } else {
+ throw new Exception\RuntimeException('Value can not contain double quotes');
+ }
+ }
+
+ /**
+ * Root elements that are not assigned to any section needs to be on the
+ * top of config.
+ *
+ * @param array $config
+ * @return array
+ */
+ protected function sortRootElements(array $config)
+ {
+ $sections = array();
+
+ // Remove sections from config array.
+ foreach ($config as $key => $value) {
+ if (is_array($value)) {
+ $sections[$key] = $value;
+ unset($config[$key]);
+ }
+ }
+
+ // Read sections to the end.
+ foreach ($sections as $key => $value) {
+ $config[$key] = $value;
+ }
+
+ return $config;
+ }
+}
diff --git a/library/Zend/Config/Writer/Json.php b/library/Zend/Config/Writer/Json.php
new file mode 100755
index 0000000000..04548495b7
--- /dev/null
+++ b/library/Zend/Config/Writer/Json.php
@@ -0,0 +1,26 @@
+ $this->useBracketArraySyntax ? '[' : 'array(',
+ 'close' => $this->useBracketArraySyntax ? ']' : ')'
+ );
+
+ return "processIndented($config, $arraySyntax) .
+ $arraySyntax['close'] . ";\n";
+ }
+
+ /**
+ * Sets whether or not to use the PHP 5.4+ "[]" array syntax.
+ *
+ * @param bool $value
+ * @return self
+ */
+ public function setUseBracketArraySyntax($value)
+ {
+ $this->useBracketArraySyntax = $value;
+ return $this;
+ }
+
+ /**
+ * toFile(): defined by Writer interface.
+ *
+ * @see WriterInterface::toFile()
+ * @param string $filename
+ * @param mixed $config
+ * @param bool $exclusiveLock
+ * @return void
+ * @throws Exception\InvalidArgumentException
+ * @throws Exception\RuntimeException
+ */
+ public function toFile($filename, $config, $exclusiveLock = true)
+ {
+ if (empty($filename)) {
+ throw new Exception\InvalidArgumentException('No file name specified');
+ }
+
+ $flags = 0;
+ if ($exclusiveLock) {
+ $flags |= LOCK_EX;
+ }
+
+ set_error_handler(
+ function ($error, $message = '', $file = '', $line = 0) use ($filename) {
+ throw new Exception\RuntimeException(
+ sprintf('Error writing to "%s": %s', $filename, $message),
+ $error
+ );
+ },
+ E_WARNING
+ );
+
+ try {
+ // for Windows, paths are escaped.
+ $dirname = str_replace('\\', '\\\\', dirname($filename));
+
+ $string = $this->toString($config);
+ $string = str_replace("'" . $dirname, "__DIR__ . '", $string);
+
+ file_put_contents($filename, $string, $flags);
+ } catch (\Exception $e) {
+ restore_error_handler();
+ throw $e;
+ }
+
+ restore_error_handler();
+ }
+
+ /**
+ * Recursively processes a PHP config array structure into a readable format.
+ *
+ * @param array $config
+ * @param array $arraySyntax
+ * @param int $indentLevel
+ * @return string
+ */
+ protected function processIndented(array $config, array $arraySyntax, &$indentLevel = 1)
+ {
+ $arrayString = "";
+
+ foreach ($config as $key => $value) {
+ $arrayString .= str_repeat(self::INDENT_STRING, $indentLevel);
+ $arrayString .= (is_int($key) ? $key : "'" . addslashes($key) . "'") . ' => ';
+
+ if (is_array($value)) {
+ if ($value === array()) {
+ $arrayString .= $arraySyntax['open'] . $arraySyntax['close'] . ",\n";
+ } else {
+ $indentLevel++;
+ $arrayString .= $arraySyntax['open'] . "\n"
+ . $this->processIndented($value, $arraySyntax, $indentLevel)
+ . str_repeat(self::INDENT_STRING, --$indentLevel) . $arraySyntax['close'] . ",\n";
+ }
+ } elseif (is_object($value) || is_string($value)) {
+ $arrayString .= var_export($value, true) . ",\n";
+ } elseif (is_bool($value)) {
+ $arrayString .= ($value ? 'true' : 'false') . ",\n";
+ } elseif ($value === null) {
+ $arrayString .= "null,\n";
+ } else {
+ $arrayString .= $value . ",\n";
+ }
+ }
+
+ return $arrayString;
+ }
+}
diff --git a/library/Zend/Config/Writer/WriterInterface.php b/library/Zend/Config/Writer/WriterInterface.php
new file mode 100755
index 0000000000..afbecd104b
--- /dev/null
+++ b/library/Zend/Config/Writer/WriterInterface.php
@@ -0,0 +1,31 @@
+openMemory();
+ $writer->setIndent(true);
+ $writer->setIndentString(str_repeat(' ', 4));
+
+ $writer->startDocument('1.0', 'UTF-8');
+ $writer->startElement('zend-config');
+
+ foreach ($config as $sectionName => $data) {
+ if (!is_array($data)) {
+ $writer->writeElement($sectionName, (string) $data);
+ } else {
+ $this->addBranch($sectionName, $data, $writer);
+ }
+ }
+
+ $writer->endElement();
+ $writer->endDocument();
+
+ return $writer->outputMemory();
+ }
+
+ /**
+ * Add a branch to an XML object recursively.
+ *
+ * @param string $branchName
+ * @param array $config
+ * @param XMLWriter $writer
+ * @return void
+ * @throws Exception\RuntimeException
+ */
+ protected function addBranch($branchName, array $config, XMLWriter $writer)
+ {
+ $branchType = null;
+
+ foreach ($config as $key => $value) {
+ if ($branchType === null) {
+ if (is_numeric($key)) {
+ $branchType = 'numeric';
+ } else {
+ $writer->startElement($branchName);
+ $branchType = 'string';
+ }
+ } elseif ($branchType !== (is_numeric($key) ? 'numeric' : 'string')) {
+ throw new Exception\RuntimeException('Mixing of string and numeric keys is not allowed');
+ }
+
+ if ($branchType === 'numeric') {
+ if (is_array($value)) {
+ $this->addBranch($value, $value, $writer);
+ } else {
+ $writer->writeElement($branchName, (string) $value);
+ }
+ } else {
+ if (is_array($value)) {
+ $this->addBranch($key, $value, $writer);
+ } else {
+ $writer->writeElement($key, (string) $value);
+ }
+ }
+ }
+
+ if ($branchType === 'string') {
+ $writer->endElement();
+ }
+ }
+}
diff --git a/library/Zend/Config/Writer/Yaml.php b/library/Zend/Config/Writer/Yaml.php
new file mode 100755
index 0000000000..f741ad677f
--- /dev/null
+++ b/library/Zend/Config/Writer/Yaml.php
@@ -0,0 +1,85 @@
+setYamlEncoder($yamlEncoder);
+ } else {
+ if (function_exists('yaml_emit')) {
+ $this->setYamlEncoder('yaml_emit');
+ }
+ }
+ }
+
+ /**
+ * Get callback for decoding YAML
+ *
+ * @return callable
+ */
+ public function getYamlEncoder()
+ {
+ return $this->yamlEncoder;
+ }
+
+ /**
+ * Set callback for decoding YAML
+ *
+ * @param callable $yamlEncoder the decoder to set
+ * @return Yaml
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setYamlEncoder($yamlEncoder)
+ {
+ if (!is_callable($yamlEncoder)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter to setYamlEncoder() - must be callable');
+ }
+ $this->yamlEncoder = $yamlEncoder;
+ return $this;
+ }
+
+ /**
+ * processConfig(): defined by AbstractWriter.
+ *
+ * @param array $config
+ * @return string
+ * @throws Exception\RuntimeException
+ */
+ public function processConfig(array $config)
+ {
+ if (null === $this->getYamlEncoder()) {
+ throw new Exception\RuntimeException("You didn't specify a Yaml callback encoder");
+ }
+
+ $config = call_user_func($this->getYamlEncoder(), $config);
+ if (null === $config) {
+ throw new Exception\RuntimeException("Error generating YAML data");
+ }
+
+ return $config;
+ }
+}
diff --git a/library/Zend/Config/WriterPluginManager.php b/library/Zend/Config/WriterPluginManager.php
new file mode 100755
index 0000000000..c70e4415ce
--- /dev/null
+++ b/library/Zend/Config/WriterPluginManager.php
@@ -0,0 +1,36 @@
+ 'Zend\Config\Writer\Ini',
+ 'json' => 'Zend\Config\Writer\Json',
+ 'php' => 'Zend\Config\Writer\PhpArray',
+ 'yaml' => 'Zend\Config\Writer\Yaml',
+ 'xml' => 'Zend\Config\Writer\Xml',
+ );
+
+ public function validatePlugin($plugin)
+ {
+ if ($plugin instanceof Writer\AbstractWriter) {
+ return;
+ }
+
+ $type = is_object($plugin) ? get_class($plugin) : gettype($plugin);
+
+ throw new Exception\InvalidArgumentException(
+ "Plugin of type {$type} is invalid. Plugin must extend ". __NAMESPACE__ . '\Writer\AbstractWriter'
+ );
+ }
+}
diff --git a/library/Zend/Config/composer.json b/library/Zend/Config/composer.json
new file mode 100755
index 0000000000..6ea7fa11ef
--- /dev/null
+++ b/library/Zend/Config/composer.json
@@ -0,0 +1,38 @@
+{
+ "name": "zendframework/zend-config",
+ "description": "provides a nested object property based user interface for accessing this configuration data within application code",
+ "license": "BSD-3-Clause",
+ "keywords": [
+ "zf2",
+ "config"
+ ],
+ "homepage": "https://github.com/zendframework/zf2",
+ "autoload": {
+ "psr-0": {
+ "Zend\\Config\\": ""
+ }
+ },
+ "target-dir": "Zend/Config",
+ "require": {
+ "php": ">=5.3.23",
+ "zendframework/zend-stdlib": "self.version"
+ },
+ "require-dev": {
+ "zendframework/zend-filter": "self.version",
+ "zendframework/zend-i18n": "self.version",
+ "zendframework/zend-json": "self.version",
+ "zendframework/zend-servicemanager": "self.version"
+ },
+ "suggest": {
+ "zendframework/zend-filter": "Zend\\Filter component",
+ "zendframework/zend-i18n": "Zend\\I18n component",
+ "zendframework/zend-json": "Zend\\Json to use the Json reader or writer classes",
+ "zendframework/zend-servicemanager": "Zend\\ServiceManager for use with the Config Factory to retrieve reader and writer instances"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev",
+ "dev-develop": "2.4-dev"
+ }
+ }
+}
diff --git a/library/Zend/Console/Adapter/AbstractAdapter.php b/library/Zend/Console/Adapter/AbstractAdapter.php
new file mode 100755
index 0000000000..49718034fe
--- /dev/null
+++ b/library/Zend/Console/Adapter/AbstractAdapter.php
@@ -0,0 +1,561 @@
+encodeText($text);
+
+ if ($color !== null || $bgColor !== null) {
+ echo $this->colorize($text, $color, $bgColor);
+ } else {
+ echo $text;
+ }
+ }
+
+ /**
+ * Alias for write()
+ *
+ * @param string $text
+ * @param null|int $color
+ * @param null|int $bgColor
+ */
+ public function writeText($text, $color = null, $bgColor = null)
+ {
+ return $this->write($text, $color, $bgColor);
+ }
+
+ /**
+ * Write a single line of text to console and advance cursor to the next line.
+ *
+ * @param string $text
+ * @param null|int $color
+ * @param null|int $bgColor
+ */
+ public function writeLine($text = "", $color = null, $bgColor = null)
+ {
+ $this->write($text . PHP_EOL, $color, $bgColor);
+ }
+
+ /**
+ * Write a piece of text at the coordinates of $x and $y
+ *
+ *
+ * @param string $text Text to write
+ * @param int $x Console X coordinate (column)
+ * @param int $y Console Y coordinate (row)
+ * @param null|int $color
+ * @param null|int $bgColor
+ */
+ public function writeAt($text, $x, $y, $color = null, $bgColor = null)
+ {
+ $this->setPos($x, $y);
+ $this->write($text, $color, $bgColor);
+ }
+
+ /**
+ * Write a box at the specified coordinates.
+ * If X or Y coordinate value is negative, it will be calculated as the distance from far right or bottom edge
+ * of the console (respectively).
+ *
+ * @param int $x1 Top-left corner X coordinate (column)
+ * @param int $y1 Top-left corner Y coordinate (row)
+ * @param int $x2 Bottom-right corner X coordinate (column)
+ * @param int $y2 Bottom-right corner Y coordinate (row)
+ * @param int $lineStyle (optional) Box border style.
+ * @param int $fillStyle (optional) Box fill style or a single character to fill it with.
+ * @param int $color (optional) Foreground color
+ * @param int $bgColor (optional) Background color
+ * @param null|int $fillColor (optional) Foreground color of box fill
+ * @param null|int $fillBgColor (optional) Background color of box fill
+ * @throws Exception\BadMethodCallException if coordinates are invalid
+ */
+ public function writeBox(
+ $x1,
+ $y1,
+ $x2,
+ $y2,
+ $lineStyle = self::LINE_SINGLE,
+ $fillStyle = self::FILL_NONE,
+ $color = null,
+ $bgColor = null,
+ $fillColor = null,
+ $fillBgColor = null
+ ) {
+ // Sanitize coordinates
+ $x1 = (int) $x1;
+ $y1 = (int) $y1;
+ $x2 = (int) $x2;
+ $y2 = (int) $y2;
+
+ // Translate negative coordinates
+ if ($x2 < 0) {
+ $x2 = $this->getWidth() - $x2;
+ }
+
+ if ($y2 < 0) {
+ $y2 = $this->getHeight() - $y2;
+ }
+
+ // Validate coordinates
+ if ($x1 < 0
+ || $y1 < 0
+ || $x2 < $x1
+ || $y2 < $y1
+ ) {
+ throw new Exception\BadMethodCallException('Supplied X,Y coordinates are invalid.');
+ }
+
+ // Determine charset and dimensions
+ $charset = $this->getCharset();
+ $width = $x2 - $x1 + 1;
+ $height = $y2 - $y1 + 1;
+
+ if ($width <= 2) {
+ $lineStyle = static::LINE_NONE;
+ }
+
+ // Activate line drawing
+ $this->write($charset::ACTIVATE);
+
+ // Draw horizontal lines
+ if ($lineStyle !== static::LINE_NONE) {
+ switch ($lineStyle) {
+ case static::LINE_SINGLE:
+ $lineChar = $charset::LINE_SINGLE_EW;
+ break;
+
+ case static::LINE_DOUBLE:
+ $lineChar = $charset::LINE_DOUBLE_EW;
+ break;
+
+ case static::LINE_BLOCK:
+ default:
+ $lineChar = $charset::LINE_BLOCK_EW;
+ break;
+ }
+
+ $this->setPos($x1 + 1, $y1);
+ $this->write(str_repeat($lineChar, $width - 2), $color, $bgColor);
+ $this->setPos($x1 + 1, $y2);
+ $this->write(str_repeat($lineChar, $width - 2), $color, $bgColor);
+ }
+
+ // Draw vertical lines and fill
+ if (is_numeric($fillStyle)
+ && $fillStyle !== static::FILL_NONE) {
+ switch ($fillStyle) {
+ case static::FILL_SHADE_LIGHT:
+ $fillChar = $charset::SHADE_LIGHT;
+ break;
+ case static::FILL_SHADE_MEDIUM:
+ $fillChar = $charset::SHADE_MEDIUM;
+ break;
+ case static::FILL_SHADE_DARK:
+ $fillChar = $charset::SHADE_DARK;
+ break;
+ case static::FILL_BLOCK:
+ default:
+ $fillChar = $charset::BLOCK;
+ break;
+ }
+ } elseif ($fillStyle) {
+ $fillChar = StringUtils::getWrapper()->substr($fillStyle, 0, 1);
+ } else {
+ $fillChar = ' ';
+ }
+
+ if ($lineStyle === static::LINE_NONE) {
+ for ($y = $y1; $y <= $y2; $y++) {
+ $this->setPos($x1, $y);
+ $this->write(str_repeat($fillChar, $width), $fillColor, $fillBgColor);
+ }
+ } else {
+ switch ($lineStyle) {
+ case static::LINE_DOUBLE:
+ $lineChar = $charset::LINE_DOUBLE_NS;
+ break;
+ case static::LINE_BLOCK:
+ $lineChar = $charset::LINE_BLOCK_NS;
+ break;
+ case static::LINE_SINGLE:
+ default:
+ $lineChar = $charset::LINE_SINGLE_NS;
+ break;
+ }
+
+ for ($y = $y1 + 1; $y < $y2; $y++) {
+ $this->setPos($x1, $y);
+ $this->write($lineChar, $color, $bgColor);
+ $this->write(str_repeat($fillChar, $width - 2), $fillColor, $fillBgColor);
+ $this->write($lineChar, $color, $bgColor);
+ }
+ }
+
+ // Draw corners
+ if ($lineStyle !== static::LINE_NONE) {
+ if ($color !== null) {
+ $this->setColor($color);
+ }
+ if ($bgColor !== null) {
+ $this->setBgColor($bgColor);
+ }
+ if ($lineStyle === static::LINE_SINGLE) {
+ $this->writeAt($charset::LINE_SINGLE_NW, $x1, $y1);
+ $this->writeAt($charset::LINE_SINGLE_NE, $x2, $y1);
+ $this->writeAt($charset::LINE_SINGLE_SE, $x2, $y2);
+ $this->writeAt($charset::LINE_SINGLE_SW, $x1, $y2);
+ } elseif ($lineStyle === static::LINE_DOUBLE) {
+ $this->writeAt($charset::LINE_DOUBLE_NW, $x1, $y1);
+ $this->writeAt($charset::LINE_DOUBLE_NE, $x2, $y1);
+ $this->writeAt($charset::LINE_DOUBLE_SE, $x2, $y2);
+ $this->writeAt($charset::LINE_DOUBLE_SW, $x1, $y2);
+ } elseif ($lineStyle === static::LINE_BLOCK) {
+ $this->writeAt($charset::LINE_BLOCK_NW, $x1, $y1);
+ $this->writeAt($charset::LINE_BLOCK_NE, $x2, $y1);
+ $this->writeAt($charset::LINE_BLOCK_SE, $x2, $y2);
+ $this->writeAt($charset::LINE_BLOCK_SW, $x1, $y2);
+ }
+ }
+
+ // Deactivate line drawing and reset colors
+ $this->write($charset::DEACTIVATE);
+ $this->resetColor();
+ }
+
+ /**
+ * Write a block of text at the given coordinates, matching the supplied width and height.
+ * In case a line of text does not fit desired width, it will be wrapped to the next line.
+ * In case the whole text does not fit in desired height, it will be truncated.
+ *
+ * @param string $text Text to write
+ * @param int $width Maximum block width. Negative value means distance from right edge.
+ * @param int|null $height Maximum block height. Negative value means distance from bottom edge.
+ * @param int $x Block X coordinate (column)
+ * @param int $y Block Y coordinate (row)
+ * @param null|int $color (optional) Text color
+ * @param null|int $bgColor (optional) Text background color
+ * @throws Exception\InvalidArgumentException
+ */
+ public function writeTextBlock(
+ $text,
+ $width,
+ $height = null,
+ $x = 0,
+ $y = 0,
+ $color = null,
+ $bgColor = null
+ ) {
+ if ($x < 0 || $y < 0) {
+ throw new Exception\InvalidArgumentException('Supplied X,Y coordinates are invalid.');
+ }
+
+ if ($width < 1) {
+ throw new Exception\InvalidArgumentException('Invalid width supplied.');
+ }
+
+ if (null !== $height && $height < 1) {
+ throw new Exception\InvalidArgumentException('Invalid height supplied.');
+ }
+
+ // ensure the text is not wider than the width
+ if (strlen($text) <= $width) {
+ // just write the line at the spec'd position
+ $this->setPos($x, $y);
+ $this->write($text, $color, $bgColor);
+ return;
+ }
+
+ $text = wordwrap($text, $width, PHP_EOL, true);
+
+ // convert to array of lines
+ $lines = explode(PHP_EOL, $text);
+
+ // truncate if height was specified
+ if (null !== $height && count($lines) > $height) {
+ $lines = array_slice($lines, 0, $height);
+ }
+
+ // write each line
+ $curY = $y;
+ foreach ($lines as $line) {
+ $this->setPos($x, $curY);
+ $this->write($line, $color, $bgColor);
+ $curY++;//next line
+ }
+ }
+
+ /**
+ * Determine and return current console width.
+ *
+ * @return int
+ */
+ public function getWidth()
+ {
+ return 80;
+ }
+
+ /**
+ * Determine and return current console height.
+ *
+ * @return int
+ */
+ public function getHeight()
+ {
+ return 25;
+ }
+
+ /**
+ * Determine and return current console width and height.
+ *
+ * @return int[] array($width, $height)
+ */
+ public function getSize()
+ {
+ return array(
+ $this->getWidth(),
+ $this->getHeight(),
+ );
+ }
+
+ /**
+ * Check if console is UTF-8 compatible
+ *
+ * @return bool
+ */
+ public function isUtf8()
+ {
+ return true;
+ }
+
+ /**
+ * Set cursor position
+ *
+ * @param int $x
+ * @param int $y
+ */
+ public function setPos($x, $y)
+ {
+ }
+
+ /**
+ * Show console cursor
+ */
+ public function showCursor()
+ {
+ }
+
+ /**
+ * Hide console cursor
+ */
+ public function hideCursor()
+ {
+ }
+
+ /**
+ * Return current console window title.
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ return '';
+ }
+
+ /**
+ * Prepare a string that will be rendered in color.
+ *
+ * @param string $string
+ * @param int $color
+ * @param null|int $bgColor
+ * @return string
+ */
+ public function colorize($string, $color = null, $bgColor = null)
+ {
+ return $string;
+ }
+
+ /**
+ * Change current drawing color.
+ *
+ * @param int $color
+ */
+ public function setColor($color)
+ {
+ }
+
+ /**
+ * Change current drawing background color
+ *
+ * @param int $color
+ */
+ public function setBgColor($color)
+ {
+ }
+
+ /**
+ * Reset color to console default.
+ */
+ public function resetColor()
+ {
+ }
+
+ /**
+ * Set Console charset to use.
+ *
+ * @param Charset\CharsetInterface $charset
+ */
+ public function setCharset(Charset\CharsetInterface $charset)
+ {
+ $this->charset = $charset;
+ }
+
+ /**
+ * Get charset currently in use by this adapter.
+ *
+ * @return Charset\CharsetInterface $charset
+ */
+ public function getCharset()
+ {
+ if ($this->charset === null) {
+ $this->charset = $this->getDefaultCharset();
+ }
+
+ return $this->charset;
+ }
+
+ /**
+ * @return Charset\Utf8
+ */
+ public function getDefaultCharset()
+ {
+ return new Charset\Utf8;
+ }
+
+ /**
+ * Clear console screen
+ */
+ public function clear()
+ {
+ echo "\f";
+ }
+
+ /**
+ * Clear line at cursor position
+ */
+ public function clearLine()
+ {
+ echo "\r" . str_repeat(" ", $this->getWidth()) . "\r";
+ }
+
+ /**
+ * Clear console screen
+ */
+ public function clearScreen()
+ {
+ return $this->clear();
+ }
+
+ /**
+ * Read a single line from the console input
+ *
+ * @param int $maxLength Maximum response length
+ * @return string
+ */
+ public function readLine($maxLength = 2048)
+ {
+ $f = fopen('php://stdin', 'r');
+ $line = stream_get_line($f, $maxLength, PHP_EOL);
+ fclose($f);
+ return rtrim($line, "\n\r");
+ }
+
+ /**
+ * Read a single character from the console input
+ *
+ * @param string|null $mask A list of allowed chars
+ * @return string
+ */
+ public function readChar($mask = null)
+ {
+ $f = fopen('php://stdin', 'r');
+ do {
+ $char = fread($f, 1);
+ } while ("" === $char || ($mask !== null && false === strstr($mask, $char)));
+ fclose($f);
+ return $char;
+ }
+
+ /**
+ * Encode a text to match console encoding
+ *
+ * @param string $text
+ * @return string the encoding text
+ */
+ public function encodeText($text)
+ {
+ if ($this->isUtf8()) {
+ if (StringUtils::isValidUtf8($text)) {
+ return $text;
+ }
+
+ return utf8_encode($text);
+ }
+
+ if (StringUtils::isValidUtf8($text)) {
+ return utf8_decode($text);
+ }
+
+ return $text;
+ }
+}
diff --git a/library/Zend/Console/Adapter/AdapterInterface.php b/library/Zend/Console/Adapter/AdapterInterface.php
new file mode 100755
index 0000000000..45407b80a1
--- /dev/null
+++ b/library/Zend/Console/Adapter/AdapterInterface.php
@@ -0,0 +1,264 @@
+ array(
+ Color::NORMAL => '22;39',
+ Color::RESET => '22;39',
+
+ Color::BLACK => '0;30',
+ Color::RED => '0;31',
+ Color::GREEN => '0;32',
+ Color::YELLOW => '0;33',
+ Color::BLUE => '0;34',
+ Color::MAGENTA => '0;35',
+ Color::CYAN => '0;36',
+ Color::WHITE => '0;37',
+
+ Color::GRAY => '1;30',
+ Color::LIGHT_RED => '1;31',
+ Color::LIGHT_GREEN => '1;32',
+ Color::LIGHT_YELLOW => '1;33',
+ Color::LIGHT_BLUE => '1;34',
+ Color::LIGHT_MAGENTA => '1;35',
+ Color::LIGHT_CYAN => '1;36',
+ Color::LIGHT_WHITE => '1;37',
+ ),
+ 'bg' => array(
+ Color::NORMAL => '0;49',
+ Color::RESET => '0;49',
+
+ Color::BLACK => '40',
+ Color::RED => '41',
+ Color::GREEN => '42',
+ Color::YELLOW => '43',
+ Color::BLUE => '44',
+ Color::MAGENTA => '45',
+ Color::CYAN => '46',
+ Color::WHITE => '47',
+
+ Color::GRAY => '40',
+ Color::LIGHT_RED => '41',
+ Color::LIGHT_GREEN => '42',
+ Color::LIGHT_YELLOW => '43',
+ Color::LIGHT_BLUE => '44',
+ Color::LIGHT_MAGENTA => '45',
+ Color::LIGHT_CYAN => '46',
+ Color::LIGHT_WHITE => '47',
+ ),
+ );
+
+ /**
+ * Last fetched TTY mode
+ *
+ * @var string|null
+ */
+ protected $lastTTYMode = null;
+
+ /**
+ * Write a single line of text to console and advance cursor to the next line.
+ *
+ * This override works around a bug in some terminals that cause the background color
+ * to fill the next line after EOL. To remedy this, we are sending the colored string with
+ * appropriate color reset sequences before sending EOL character.
+ *
+ * @link https://github.com/zendframework/zf2/issues/4167
+ * @param string $text
+ * @param null|int $color
+ * @param null|int $bgColor
+ */
+ public function writeLine($text = "", $color = null, $bgColor = null)
+ {
+ $this->write($text, $color, $bgColor);
+ $this->write(PHP_EOL);
+ }
+
+ /**
+ * Determine and return current console width.
+ *
+ * @return int
+ */
+ public function getWidth()
+ {
+ static $width;
+ if ($width > 0) {
+ return $width;
+ }
+
+ /**
+ * Try to read env variable
+ */
+ if (($result = getenv('COLUMNS')) !== false) {
+ return $width = (int) $result;
+ }
+
+ /**
+ * Try to read console size from "tput" command
+ */
+ $result = exec('tput cols', $output, $return);
+ if (!$return && is_numeric($result)) {
+ return $width = (int) $result;
+ }
+
+ return $width = parent::getWidth();
+ }
+
+ /**
+ * Determine and return current console height.
+ *
+ * @return false|int
+ */
+ public function getHeight()
+ {
+ static $height;
+ if ($height > 0) {
+ return $height;
+ }
+
+ // Try to read env variable
+ if (($result = getenv('LINES')) !== false) {
+ return $height = (int) $result;
+ }
+
+ // Try to read console size from "tput" command
+ $result = exec('tput lines', $output, $return);
+ if (!$return && is_numeric($result)) {
+ return $height = (int) $result;
+ }
+
+ return $height = parent::getHeight();
+ }
+
+ /**
+ * Run a mode command and store results
+ *
+ * @return void
+ */
+ protected function runModeCommand()
+ {
+ exec('mode', $output, $return);
+ if ($return || !count($output)) {
+ $this->modeResult = '';
+ } else {
+ $this->modeResult = trim(implode('', $output));
+ }
+ }
+
+ /**
+ * Check if console is UTF-8 compatible
+ *
+ * @return bool
+ */
+ public function isUtf8()
+ {
+ // Try to retrieve it from LANG env variable
+ if (($lang = getenv('LANG')) !== false) {
+ return stristr($lang, 'utf-8') || stristr($lang, 'utf8');
+ }
+
+ return false;
+ }
+
+ /**
+ * Show console cursor
+ */
+ public function showCursor()
+ {
+ echo "\x1b[?25h";
+ }
+
+ /**
+ * Hide console cursor
+ */
+ public function hideCursor()
+ {
+ echo "\x1b[?25l";
+ }
+
+ /**
+ * Set cursor position
+ * @param int $x
+ * @param int $y
+ */
+ public function setPos($x, $y)
+ {
+ echo "\x1b[" . $y . ';' . $x . 'f';
+ }
+
+ /**
+ * Prepare a string that will be rendered in color.
+ *
+ * @param string $string
+ * @param int $color
+ * @param null|int $bgColor
+ * @throws Exception\BadMethodCallException
+ * @return string
+ */
+ public function colorize($string, $color = null, $bgColor = null)
+ {
+ $color = $this->getColorCode($color, 'fg');
+ $bgColor = $this->getColorCode($bgColor, 'bg');
+ return ($color !== null ? "\x1b[" . $color . 'm' : '')
+ . ($bgColor !== null ? "\x1b[" . $bgColor . 'm' : '')
+ . $string
+ . "\x1b[22;39m\x1b[0;49m";
+ }
+
+ /**
+ * Change current drawing color.
+ *
+ * @param int $color
+ * @throws Exception\BadMethodCallException
+ */
+ public function setColor($color)
+ {
+ $color = $this->getColorCode($color, 'fg');
+ echo "\x1b[" . $color . 'm';
+ }
+
+ /**
+ * Change current drawing background color
+ *
+ * @param int $bgColor
+ * @throws Exception\BadMethodCallException
+ */
+ public function setBgColor($bgColor)
+ {
+ $bgColor = $this->getColorCode($bgColor, 'bg');
+ echo "\x1b[" . ($bgColor) . 'm';
+ }
+
+ /**
+ * Reset color to console default.
+ */
+ public function resetColor()
+ {
+ echo "\x1b[0;49m"; // reset bg color
+ echo "\x1b[22;39m"; // reset fg bold, bright and faint
+ echo "\x1b[25;39m"; // reset fg blink
+ echo "\x1b[24;39m"; // reset fg underline
+ }
+
+ /**
+ * Set Console charset to use.
+ *
+ * @param Charset\CharsetInterface $charset
+ */
+ public function setCharset(Charset\CharsetInterface $charset)
+ {
+ $this->charset = $charset;
+ }
+
+ /**
+ * Get charset currently in use by this adapter.
+ *
+ * @return Charset\CharsetInterface $charset
+ */
+ public function getCharset()
+ {
+ if ($this->charset === null) {
+ $this->charset = $this->getDefaultCharset();
+ }
+
+ return $this->charset;
+ }
+
+ /**
+ * @return Charset\CharsetInterface
+ */
+ public function getDefaultCharset()
+ {
+ if ($this->isUtf8()) {
+ return new Charset\Utf8;
+ }
+ return new Charset\DECSG();
+ }
+
+ /**
+ * Read a single character from the console input
+ *
+ * @param string|null $mask A list of allowed chars
+ * @return string
+ */
+ public function readChar($mask = null)
+ {
+ $this->setTTYMode('-icanon -echo');
+
+ $stream = fopen('php://stdin', 'rb');
+ do {
+ $char = fgetc($stream);
+ } while (strlen($char) !== 1 || ($mask !== null && false === strstr($mask, $char)));
+ fclose($stream);
+
+ $this->restoreTTYMode();
+ return $char;
+ }
+
+ /**
+ * Reset color to console default.
+ */
+ public function clear()
+ {
+ echo "\x1b[2J"; // reset bg color
+ $this->setPos(1, 1); // reset cursor position
+ }
+
+ /**
+ * Restore TTY (Console) mode to previous value.
+ *
+ * @return void
+ */
+ protected function restoreTTYMode()
+ {
+ if ($this->lastTTYMode === null) {
+ return;
+ }
+
+ shell_exec('stty ' . escapeshellarg($this->lastTTYMode));
+ }
+
+ /**
+ * Change TTY (Console) mode
+ *
+ * @link http://en.wikipedia.org/wiki/Stty
+ * @param string $mode
+ */
+ protected function setTTYMode($mode)
+ {
+ // Store last mode
+ $this->lastTTYMode = trim(`stty -g`);
+
+ // Set new mode
+ shell_exec('stty '.escapeshellcmd($mode));
+ }
+
+ /**
+ * Get the final color code and throw exception on error
+ *
+ * @param null|int|Xterm256 $color
+ * @param string $type (optional) Foreground 'fg' or background 'bg'.
+ * @throws Exception\BadMethodCallException
+ * @return string
+ */
+ protected function getColorCode($color, $type = 'fg')
+ {
+ if ($color instanceof Xterm256) {
+ $r = new ReflectionClass($color);
+ $code = $r->getStaticPropertyValue('color');
+ if ($type == 'fg') {
+ $code = sprintf($code, $color::FOREGROUND);
+ } else {
+ $code = sprintf($code, $color::BACKGROUND);
+ }
+ return $code;
+ }
+
+ if ($color !== null) {
+ if (!isset(static::$ansiColorMap[$type][$color])) {
+ throw new Exception\BadMethodCallException(sprintf(
+ 'Unknown color "%s". Please use one of the Zend\Console\ColorInterface constants '
+ . 'or use Zend\Console\Color\Xterm256::calculate',
+ $color
+ ));
+ }
+
+ return static::$ansiColorMap[$type][$color];
+ }
+
+ return null;
+ }
+}
diff --git a/library/Zend/Console/Adapter/Virtual.php b/library/Zend/Console/Adapter/Virtual.php
new file mode 100755
index 0000000000..f1b1eb95e3
--- /dev/null
+++ b/library/Zend/Console/Adapter/Virtual.php
@@ -0,0 +1,176 @@
+ 0) {
+ return $width;
+ }
+
+ // Try to read console size from "mode" command
+ if ($this->modeResult === null) {
+ $this->runProbeCommand();
+ }
+
+ if (preg_match('/Columns\:\s+(\d+)/', $this->modeResult, $matches)) {
+ $width = $matches[1];
+ } else {
+ $width = parent::getWidth();
+ }
+
+ return $width;
+ }
+
+ /**
+ * Determine and return current console height.
+ *
+ * @return false|int
+ */
+ public function getHeight()
+ {
+ static $height;
+ if ($height > 0) {
+ return $height;
+ }
+
+ // Try to read console size from "mode" command
+ if ($this->modeResult === null) {
+ $this->runProbeCommand();
+ }
+
+ if (preg_match('/Rows\:\s+(\d+)/', $this->modeResult, $matches)) {
+ $height = $matches[1];
+ } else {
+ $height = parent::getHeight();
+ }
+
+ return $height;
+ }
+
+ /**
+ * Run and store the results of mode command
+ *
+ * @return void
+ */
+ protected function runProbeCommand()
+ {
+ exec('mode', $output, $return);
+ if ($return || !count($output)) {
+ $this->modeResult = '';
+ } else {
+ $this->modeResult = trim(implode('', $output));
+ }
+ }
+
+ /**
+ * Check if console is UTF-8 compatible
+ *
+ * @return bool
+ */
+ public function isUtf8()
+ {
+ // Try to read code page info from "mode" command
+ if ($this->modeResult === null) {
+ $this->runProbeCommand();
+ }
+
+ if (preg_match('/Code page\:\s+(\d+)/', $this->modeResult, $matches)) {
+ return (int) $matches[1] == 65001;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return current console window title.
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ // Try to use powershell to retrieve console window title
+ exec('powershell -command "write $Host.UI.RawUI.WindowTitle"', $output, $result);
+ if ($result || !$output) {
+ return '';
+ }
+
+ return trim($output, "\r\n");
+ }
+
+ /**
+ * Set Console charset to use.
+ *
+ * @param Charset\CharsetInterface $charset
+ */
+ public function setCharset(Charset\CharsetInterface $charset)
+ {
+ $this->charset = $charset;
+ }
+
+ /**
+ * Get charset currently in use by this adapter.
+ *
+ * @return Charset\CharsetInterface $charset
+ */
+ public function getCharset()
+ {
+ if ($this->charset === null) {
+ $this->charset = $this->getDefaultCharset();
+ }
+
+ return $this->charset;
+ }
+
+ /**
+ * @return Charset\AsciiExtended
+ */
+ public function getDefaultCharset()
+ {
+ return new Charset\AsciiExtended;
+ }
+
+ /**
+ * Switch to UTF mode
+ *
+ * @return void
+ */
+ protected function switchToUtf8()
+ {
+ shell_exec('mode con cp select=65001');
+ }
+}
diff --git a/library/Zend/Console/Adapter/Windows.php b/library/Zend/Console/Adapter/Windows.php
new file mode 100755
index 0000000000..c1bf8b734b
--- /dev/null
+++ b/library/Zend/Console/Adapter/Windows.php
@@ -0,0 +1,356 @@
+ 0) {
+ return $width;
+ }
+
+ // Try to read console size from "mode" command
+ if ($this->probeResult === null) {
+ $this->runProbeCommand();
+ }
+
+ if (count($this->probeResult) && (int) $this->probeResult[0]) {
+ $width = (int) $this->probeResult[0];
+ } else {
+ $width = parent::getWidth();
+ }
+
+ return $width;
+ }
+
+ /**
+ * Determine and return current console height.
+ *
+ * @return int
+ */
+ public function getHeight()
+ {
+ static $height;
+ if ($height > 0) {
+ return $height;
+ }
+
+ // Try to read console size from "mode" command
+ if ($this->probeResult === null) {
+ $this->runProbeCommand();
+ }
+
+ if (count($this->probeResult) && (int) $this->probeResult[1]) {
+ $height = (int) $this->probeResult[1];
+ } else {
+ $height = parent::getheight();
+ }
+
+ return $height;
+ }
+
+ /**
+ * Probe for system capabilities and cache results
+ *
+ * Run a Windows Powershell command that determines parameters of console window. The command is fed through
+ * standard input (with echo) to prevent Powershell from creating a sub-thread and hanging PHP when run through
+ * a debugger/IDE.
+ *
+ * @return void
+ */
+ protected function runProbeCommand()
+ {
+ exec(
+ 'echo $size = $Host.ui.rawui.windowsize; write $($size.width) $($size.height) | powershell -NonInteractive -NoProfile -NoLogo -OutputFormat Text -Command -',
+ $output,
+ $return
+ );
+ if ($return || empty($output)) {
+ $this->probeResult = '';
+ } else {
+ $this->probeResult = $output;
+ }
+ }
+
+ /**
+ * Run and cache results of mode command
+ *
+ * @return void
+ */
+ protected function runModeCommand()
+ {
+ exec('mode', $output, $return);
+ if ($return || !count($output)) {
+ $this->modeResult = '';
+ } else {
+ $this->modeResult = trim(implode('', $output));
+ }
+ }
+
+ /**
+ * Check if console is UTF-8 compatible
+ *
+ * @return bool
+ */
+ public function isUtf8()
+ {
+ // Try to read code page info from "mode" command
+ if ($this->modeResult === null) {
+ $this->runModeCommand();
+ }
+
+ if (preg_match('/Code page\:\s+(\d+)/', $this->modeResult, $matches)) {
+ return (int) $matches[1] == 65001;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return current console window title.
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ // Try to use powershell to retrieve console window title
+ exec('powershell -command "write $Host.UI.RawUI.WindowTitle"', $output, $result);
+ if ($result || !$output) {
+ return '';
+ }
+
+ return trim($output, "\r\n");
+ }
+
+ /**
+ * Set Console charset to use.
+ *
+ * @param Charset\CharsetInterface $charset
+ */
+ public function setCharset(Charset\CharsetInterface $charset)
+ {
+ $this->charset = $charset;
+ }
+
+ /**
+ * Get charset currently in use by this adapter.
+ *
+ * @return Charset\CharsetInterface $charset
+ */
+ public function getCharset()
+ {
+ if ($this->charset === null) {
+ $this->charset = $this->getDefaultCharset();
+ }
+
+ return $this->charset;
+ }
+
+ /**
+ * @return Charset\AsciiExtended
+ */
+ public function getDefaultCharset()
+ {
+ return new Charset\AsciiExtended;
+ }
+
+ /**
+ * Switch to utf-8 encoding
+ *
+ * @return void
+ */
+ protected function switchToUtf8()
+ {
+ shell_exec('mode con cp select=65001');
+ }
+
+ /**
+ * Clear console screen
+ */
+ public function clear()
+ {
+ // Attempt to clear the screen using PowerShell command
+ exec("powershell -NonInteractive -NoProfile -NoLogo -OutputFormat Text -Command Clear-Host", $output, $return);
+
+ if ($return) {
+ // Could not run powershell... fall back to filling the buffer with newlines
+ echo str_repeat("\r\n", $this->getHeight());
+ }
+ }
+
+ /**
+ * Clear line at cursor position
+ */
+ public function clearLine()
+ {
+ echo "\r" . str_repeat(' ', $this->getWidth()) . "\r";
+ }
+
+ /**
+ * Read a single character from the console input
+ *
+ * @param string|null $mask A list of allowed chars
+ * @throws Exception\RuntimeException
+ * @return string
+ */
+ public function readChar($mask = null)
+ {
+ // Decide if we can use `choice` tool
+ $useChoice = $mask !== null && preg_match('/^[a-zA-Z0-9]+$/D', $mask);
+
+ if ($useChoice) {
+ // Use Windows 95+ "choice" command, which allows for reading a
+ // single character matching a mask, but is limited to lower ASCII
+ // range.
+ do {
+ exec('choice /n /cs /c:' . $mask, $output, $return);
+ if ($return == 255 || $return < 1 || $return > strlen($mask)) {
+ throw new Exception\RuntimeException('"choice" command failed to run. Are you using Windows XP or newer?');
+ }
+
+ // Fetch the char from mask
+ $char = substr($mask, $return - 1, 1);
+ } while ("" === $char || ($mask !== null && false === strstr($mask, $char)));
+
+ return $char;
+ }
+
+ // Try to use PowerShell, giving it console access. Because PowersShell
+ // interpreter can take a short while to load, we are emptying the
+ // whole keyboard buffer and picking the last key that has been pressed
+ // before or after PowerShell command has started. The ASCII code for
+ // that key is then converted to a character.
+ if ($mask === null) {
+ exec(
+ 'powershell -NonInteractive -NoProfile -NoLogo -OutputFormat Text -Command "'
+ . 'while ($Host.UI.RawUI.KeyAvailable) {$key = $Host.UI.RawUI.ReadKey(\'NoEcho,IncludeKeyDown\');}'
+ . 'write $key.VirtualKeyCode;'
+ . '"',
+ $result,
+ $return
+ );
+
+ // Retrieve char from the result.
+ $char = !empty($result) ? implode('', $result) : null;
+
+ if (!empty($char) && !$return) {
+ // We have obtained an ASCII code, convert back to a char ...
+ $char = chr($char);
+
+ // ... and return it...
+ return $char;
+ }
+ } else {
+ // Windows and DOS will return carriage-return char (ASCII 13) when
+ // the user presses [ENTER] key, but Console Adapter user might
+ // have provided a \n Newline (ASCII 10) in the mask, to allow [ENTER].
+ // We are going to replace all CR with NL to conform.
+ $mask = strtr($mask, "\n", "\r");
+
+ // Prepare a list of ASCII codes from mask chars
+ $asciiMask = array_map(function ($char) {
+ return ord($char);
+ }, str_split($mask));
+ $asciiMask = array_unique($asciiMask);
+
+ // Char mask filtering is now handled by the PowerShell itself,
+ // because it's a much faster method than invoking PS interpreter
+ // after each mismatch. The command should return ASCII code of a
+ // matching key.
+ $result = $return = null;
+
+ exec(
+ 'powershell -NonInteractive -NoProfile -NoLogo -OutputFormat Text -Command "'
+ . '[int[]] $mask = ' . join(',', $asciiMask) . ';'
+ . 'do {'
+ . '$key = $Host.UI.RawUI.ReadKey(\'NoEcho,IncludeKeyDown\').VirtualKeyCode;'
+ . '} while ( !($mask -contains $key) );'
+ . 'write $key;'
+ . '"',
+ $result,
+ $return
+ );
+
+ $char = !empty($result) ? trim(implode('', $result)) : null;
+
+ if (!$return && $char && ($mask === null || in_array($char, $asciiMask))) {
+ // Normalize CR to LF
+ if ($char == 13) {
+ $char = 10;
+ }
+
+ // Convert to a char
+ $char = chr($char);
+
+ // ... and return it...
+ return $char;
+ }
+ }
+
+ // Fall back to standard input, which on Windows does not allow reading
+ // a single character. This is a limitation of Windows streams
+ // implementation (not PHP) and this behavior cannot be changed with a
+ // command like "stty", known to POSIX systems.
+ $stream = fopen('php://stdin', 'rb');
+ do {
+ $char = fgetc($stream);
+ $char = substr(trim($char), 0, 1);
+ } while (!$char || ($mask !== null && !stristr($mask, $char)));
+ fclose($stream);
+
+ return $char;
+ }
+
+ /**
+ * Read a single line from the console input.
+ *
+ * @param int $maxLength Maximum response length
+ * @return string
+ */
+ public function readLine($maxLength = 2048)
+ {
+ $f = fopen('php://stdin', 'r');
+ $line = rtrim(fread($f, $maxLength), "\r\n");
+ fclose($f);
+
+ return $line;
+ }
+}
diff --git a/library/Zend/Console/Adapter/WindowsAnsicon.php b/library/Zend/Console/Adapter/WindowsAnsicon.php
new file mode 100755
index 0000000000..3519a9e21e
--- /dev/null
+++ b/library/Zend/Console/Adapter/WindowsAnsicon.php
@@ -0,0 +1,302 @@
+ 0) {
+ return $width;
+ }
+
+ // Try to read console size from ANSICON env var
+ if (preg_match('/\((\d+)x/', getenv('ANSICON'), $matches)) {
+ $width = $matches[1];
+ } else {
+ $width = AbstractAdapter::getWidth();
+ }
+
+ return $width;
+ }
+
+ /**
+ * Determine and return current console height.
+ *
+ * @return false|int
+ */
+ public function getHeight()
+ {
+ static $height;
+ if ($height > 0) {
+ return $height;
+ }
+
+ // Try to read console size from ANSICON env var
+ if (preg_match('/\(\d+x(\d+)/', getenv('ANSICON'), $matches)) {
+ $height = $matches[1];
+ } else {
+ $height = AbstractAdapter::getHeight();
+ }
+ return $height;
+ }
+
+ /**
+ * Run and cache results of mode command
+ *
+ * @return void
+ */
+ protected function runModeCommand()
+ {
+ exec('mode', $output, $return);
+ if ($return || !count($output)) {
+ $this->modeResult = '';
+ } else {
+ $this->modeResult = trim(implode('', $output));
+ }
+ }
+
+ /**
+ * Check if console is UTF-8 compatible
+ *
+ * @return bool
+ */
+ public function isUtf8()
+ {
+ // Try to read code page info from "mode" command
+ if ($this->modeResult === null) {
+ $this->runModeCommand();
+ }
+
+ if (preg_match('/Code page\:\s+(\d+)/', $this->modeResult, $matches)) {
+ return (int) $matches[1] == 65001;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return current console window title.
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ // Try to use powershell to retrieve console window title
+ exec('powershell -command "write $Host.UI.RawUI.WindowTitle"', $output, $result);
+ if ($result || !$output) {
+ return '';
+ }
+
+ return trim($output, "\r\n");
+ }
+
+ /**
+ * Clear console screen
+ */
+ public function clear()
+ {
+ echo chr(27) . '[1J' . chr(27) . '[u';
+ }
+
+ /**
+ * Clear line at cursor position
+ */
+ public function clearLine()
+ {
+ echo chr(27) . '[1K';
+ }
+
+ /**
+ * Set Console charset to use.
+ *
+ * @param CharsetInterface $charset
+ */
+ public function setCharset(CharsetInterface $charset)
+ {
+ $this->charset = $charset;
+ }
+
+ /**
+ * Get charset currently in use by this adapter.
+ *
+
+ * @return CharsetInterface $charset
+ */
+ public function getCharset()
+ {
+ if ($this->charset === null) {
+ $this->charset = $this->getDefaultCharset();
+ }
+
+ return $this->charset;
+ }
+
+ /**
+ * @return Charset\AsciiExtended
+ */
+ public function getDefaultCharset()
+ {
+ return new Charset\AsciiExtended();
+ }
+
+ /**
+ * Read a single character from the console input
+ *
+ * @param string|null $mask A list of allowed chars
+ * @return string
+ * @throws Exception\RuntimeException
+ */
+ public function readChar($mask = null)
+ {
+ // Decide if we can use `choice` tool
+ $useChoice = $mask !== null && preg_match('/^[a-zA-Z0-9]+$/D', $mask);
+
+ if ($useChoice) {
+ // Use Windows 98+ "choice" command, which allows for reading a
+ // single character matching a mask, but is limited to lower ASCII
+ // range.
+ do {
+ exec('choice /n /cs /c:' . $mask, $output, $return);
+ if ($return == 255 || $return < 1 || $return > strlen($mask)) {
+ throw new Exception\RuntimeException('"choice" command failed to run. Are you using Windows XP or newer?');
+ }
+
+ // Fetch the char from mask
+ $char = substr($mask, $return - 1, 1);
+ } while ("" === $char || ($mask !== null && false === strstr($mask, $char)));
+
+ return $char;
+ }
+
+ // Try to use PowerShell, giving it console access. Because PowersShell
+ // interpreter can take a short while to load, we are emptying the
+ // whole keyboard buffer and picking the last key that has been pressed
+ // before or after PowerShell command has started. The ASCII code for
+ // that key is then converted to a character.
+ if ($mask === null) {
+ exec(
+ 'powershell -NonInteractive -NoProfile -NoLogo -OutputFormat Text -Command "'
+ . 'while ($Host.UI.RawUI.KeyAvailable) {$key = $Host.UI.RawUI.ReadKey(\'NoEcho,IncludeKeyDown\');}'
+ . 'write $key.VirtualKeyCode;'
+ . '"',
+ $result,
+ $return
+ );
+
+ // Retrieve char from the result.
+ $char = !empty($result) ? implode('', $result) : null;
+
+ if (!empty($char) && !$return) {
+ // We have obtained an ASCII code, convert back to a char ...
+ $char = chr($char);
+
+ // ... and return it...
+ return $char;
+ }
+ } else {
+ // Windows and DOS will return carriage-return char (ASCII 13) when
+ // the user presses [ENTER] key, but Console Adapter user might
+ // have provided a \n Newline (ASCII 10) in the mask, to allow
+ // [ENTER]. We are going to replace all CR with NL to conform.
+ $mask = strtr($mask, "\n", "\r");
+
+ // Prepare a list of ASCII codes from mask chars
+ $asciiMask = array_map(function ($char) {
+ return ord($char);
+ }, str_split($mask));
+ $asciiMask = array_unique($asciiMask);
+
+ // Char mask filtering is now handled by the PowerShell itself,
+ // because it's a much faster method than invoking PS interpreter
+ // after each mismatch. The command should return ASCII code of a
+ // matching key.
+ $result = $return = null;
+ exec(
+ 'powershell -NonInteractive -NoProfile -NoLogo -OutputFormat Text -Command "'
+ . '[int[]] $mask = '.join(',', $asciiMask).';'
+ . 'do {'
+ . '$key = $Host.UI.RawUI.ReadKey(\'NoEcho,IncludeKeyDown\').VirtualKeyCode;'
+ . '} while ( !($mask -contains $key) );'
+ . 'write $key;'
+ . '"',
+ $result,
+ $return
+ );
+
+ $char = !empty($result) ? trim(implode('', $result)) : null;
+
+ if (!$return && $char && ($mask === null || in_array($char, $asciiMask))) {
+ // We have obtained an ASCII code, check if it is a carriage
+ // return and normalize it as needed
+ if ($char == 13) {
+ $char = 10;
+ }
+
+ // Convert to a character
+ $char = chr($char);
+
+ // ... and return it...
+ return $char;
+ }
+ }
+
+ // Fall back to standard input, which on Windows does not allow reading
+ // a single character. This is a limitation of Windows streams
+ // implementation (not PHP) and this behavior cannot be changed with a
+ // command like "stty", known to POSIX systems.
+ $stream = fopen('php://stdin', 'rb');
+ do {
+ $char = fgetc($stream);
+ $char = substr(trim($char), 0, 1);
+ } while (!$char || ($mask !== null && !stristr($mask, $char)));
+ fclose($stream);
+
+ return $char;
+ }
+}
diff --git a/library/Zend/Console/CONTRIBUTING.md b/library/Zend/Console/CONTRIBUTING.md
new file mode 100755
index 0000000000..e77f5d2d5b
--- /dev/null
+++ b/library/Zend/Console/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# CONTRIBUTING
+
+Please don't open pull requests against this repository, please use https://github.com/zendframework/zf2.
\ No newline at end of file
diff --git a/library/Zend/Console/Charset/Ascii.php b/library/Zend/Console/Charset/Ascii.php
new file mode 100755
index 0000000000..0cc3261488
--- /dev/null
+++ b/library/Zend/Console/Charset/Ascii.php
@@ -0,0 +1,48 @@
+ 0 ? (int) $val : 0;
+ }, $hex);
+
+ $dhex = array_map('hexdec', $hex);
+
+ if (array_fill(0, 3, $dhex[0]) === $dhex && (int) substr($dhex[0], -1) === 8) {
+ $x11 = 232 + (int) floor($dhex[0]/10);
+ return new static($x11);
+ }
+
+ $x11 = $ahex[0] * 36 + $ahex[1] * 6 + $ahex[2] + 16;
+
+ return new static($x11);
+ }
+}
diff --git a/library/Zend/Console/ColorInterface.php b/library/Zend/Console/ColorInterface.php
new file mode 100755
index 0000000000..b2cc770b4e
--- /dev/null
+++ b/library/Zend/Console/ColorInterface.php
@@ -0,0 +1,34 @@
+setCharset(new $className());
+ }
+
+ return static::$instance;
+ }
+
+ /**
+ * Reset the console instance
+ */
+ public static function resetInstance()
+ {
+ static::$instance = null;
+ }
+
+ /**
+ * Check if currently running under MS Windows
+ *
+ * @see http://stackoverflow.com/questions/738823/possible-values-for-php-os
+ * @return bool
+ */
+ public static function isWindows()
+ {
+ return
+ (defined('PHP_OS') && (substr_compare(PHP_OS, 'win', 0, 3, true) === 0)) ||
+ (getenv('OS') != false && substr_compare(getenv('OS'), 'windows', 0, 7, true))
+ ;
+ }
+
+ /**
+ * Check if running under MS Windows Ansicon
+ *
+ * @return bool
+ */
+ public static function isAnsicon()
+ {
+ return getenv('ANSICON') !== false;
+ }
+
+ /**
+ * Check if running in a console environment (CLI)
+ *
+ * By default, returns value of PHP_SAPI global constant. If $isConsole is
+ * set, and a boolean value, that value will be returned.
+ *
+ * @return bool
+ */
+ public static function isConsole()
+ {
+ if (null === static::$isConsole) {
+ static::$isConsole = (PHP_SAPI == 'cli');
+ }
+ return static::$isConsole;
+ }
+
+ /**
+ * Override the "is console environment" flag
+ *
+ * @param null|bool $flag
+ */
+ public static function overrideIsConsole($flag)
+ {
+ if (null != $flag) {
+ $flag = (bool) $flag;
+ }
+ static::$isConsole = $flag;
+ }
+
+ /**
+ * Try to detect best matching adapter
+ * @return string|null
+ */
+ public static function detectBestAdapter()
+ {
+ // Check if we are in a console environment
+ if (!static::isConsole()) {
+ return null;
+ }
+
+ // Check if we're on windows
+ if (static::isWindows()) {
+ if (static::isAnsicon()) {
+ $className = __NAMESPACE__ . '\Adapter\WindowsAnsicon';
+ } else {
+ $className = __NAMESPACE__ . '\Adapter\Windows';
+ }
+
+ return $className;
+ }
+
+ // Default is a Posix console
+ $className = __NAMESPACE__ . '\Adapter\Posix';
+ return $className;
+ }
+
+ /**
+ * Pass-thru static call to current AdapterInterface instance.
+ *
+ * @param $funcName
+ * @param $arguments
+ * @return mixed
+ */
+ public static function __callStatic($funcName, $arguments)
+ {
+ $instance = static::getInstance();
+ return call_user_func_array(array($instance, $funcName), $arguments);
+ }
+}
diff --git a/library/Zend/Console/Exception/BadMethodCallException.php b/library/Zend/Console/Exception/BadMethodCallException.php
new file mode 100755
index 0000000000..aa650fc049
--- /dev/null
+++ b/library/Zend/Console/Exception/BadMethodCallException.php
@@ -0,0 +1,14 @@
+usage = $usage;
+ parent::__construct($message);
+ }
+
+ /**
+ * Returns the usage
+ *
+ * @return string
+ */
+ public function getUsageMessage()
+ {
+ return $this->usage;
+ }
+}
diff --git a/library/Zend/Console/Getopt.php b/library/Zend/Console/Getopt.php
new file mode 100755
index 0000000000..12493264ad
--- /dev/null
+++ b/library/Zend/Console/Getopt.php
@@ -0,0 +1,1056 @@
+ self::MODE_ZEND,
+ self::CONFIG_DASHDASH => true,
+ self::CONFIG_IGNORECASE => false,
+ self::CONFIG_PARSEALL => true,
+ self::CONFIG_CUMULATIVE_PARAMETERS => false,
+ self::CONFIG_CUMULATIVE_FLAGS => false,
+ self::CONFIG_PARAMETER_SEPARATOR => null,
+ self::CONFIG_FREEFORM_FLAGS => false,
+ self::CONFIG_NUMERIC_FLAGS => false
+ );
+
+ /**
+ * Stores the command-line arguments for the calling application.
+ *
+ * @var array
+ */
+ protected $argv = array();
+
+ /**
+ * Stores the name of the calling application.
+ *
+ * @var string
+ */
+ protected $progname = '';
+
+ /**
+ * Stores the list of legal options for this application.
+ *
+ * @var array
+ */
+ protected $rules = array();
+
+ /**
+ * Stores alternate spellings of legal options.
+ *
+ * @var array
+ */
+ protected $ruleMap = array();
+
+ /**
+ * Stores options given by the user in the current invocation
+ * of the application, as well as parameters given in options.
+ *
+ * @var array
+ */
+ protected $options = array();
+
+ /**
+ * Stores the command-line arguments other than options.
+ *
+ * @var array
+ */
+ protected $remainingArgs = array();
+
+ /**
+ * State of the options: parsed or not yet parsed?
+ *
+ * @var bool
+ */
+ protected $parsed = false;
+
+ /**
+ * A list of callbacks to call when a particular option is present.
+ *
+ * @var array
+ */
+ protected $optionCallbacks = array();
+
+ /**
+ * The constructor takes one to three parameters.
+ *
+ * The first parameter is $rules, which may be a string for
+ * gnu-style format, or a structured array for Zend-style format.
+ *
+ * The second parameter is $argv, and it is optional. If not
+ * specified, $argv is inferred from the global argv.
+ *
+ * The third parameter is an array of configuration parameters
+ * to control the behavior of this instance of Getopt; it is optional.
+ *
+ * @param array $rules
+ * @param array $argv
+ * @param array $getoptConfig
+ * @throws Exception\InvalidArgumentException
+ */
+ public function __construct($rules, $argv = null, $getoptConfig = array())
+ {
+ if (!isset($_SERVER['argv'])) {
+ $errorDescription = (ini_get('register_argc_argv') == false)
+ ? "argv is not available, because ini option 'register_argc_argv' is set Off"
+ : '$_SERVER["argv"] is not set, but Zend\Console\Getopt cannot work without this information.';
+ throw new Exception\InvalidArgumentException($errorDescription);
+ }
+
+ $this->progname = $_SERVER['argv'][0];
+ $this->setOptions($getoptConfig);
+ $this->addRules($rules);
+ if (!is_array($argv)) {
+ $argv = array_slice($_SERVER['argv'], 1);
+ }
+ if (isset($argv)) {
+ $this->addArguments((array) $argv);
+ }
+ }
+
+ /**
+ * Return the state of the option seen on the command line of the
+ * current application invocation. This function returns true, or the
+ * parameter to the option, if any. If the option was not given,
+ * this function returns null.
+ *
+ * The magic __get method works in the context of naming the option
+ * as a virtual member of this class.
+ *
+ * @param string $key
+ * @return string
+ */
+ public function __get($key)
+ {
+ return $this->getOption($key);
+ }
+
+ /**
+ * Test whether a given option has been seen.
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function __isset($key)
+ {
+ $this->parse();
+ if (isset($this->ruleMap[$key])) {
+ $key = $this->ruleMap[$key];
+ return isset($this->options[$key]);
+ }
+ return false;
+ }
+
+ /**
+ * Set the value for a given option.
+ *
+ * @param string $key
+ * @param string $value
+ */
+ public function __set($key, $value)
+ {
+ $this->parse();
+ if (isset($this->ruleMap[$key])) {
+ $key = $this->ruleMap[$key];
+ $this->options[$key] = $value;
+ }
+ }
+
+ /**
+ * Return the current set of options and parameters seen as a string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * Unset an option.
+ *
+ * @param string $key
+ */
+ public function __unset($key)
+ {
+ $this->parse();
+ if (isset($this->ruleMap[$key])) {
+ $key = $this->ruleMap[$key];
+ unset($this->options[$key]);
+ }
+ }
+
+ /**
+ * Define additional command-line arguments.
+ * These are appended to those defined when the constructor was called.
+ *
+ * @param array $argv
+ * @throws Exception\InvalidArgumentException When not given an array as parameter
+ * @return self
+ */
+ public function addArguments($argv)
+ {
+ if (!is_array($argv)) {
+ throw new Exception\InvalidArgumentException("Parameter #1 to addArguments should be an array");
+ }
+ $this->argv = array_merge($this->argv, $argv);
+ $this->parsed = false;
+ return $this;
+ }
+
+ /**
+ * Define full set of command-line arguments.
+ * These replace any currently defined.
+ *
+ * @param array $argv
+ * @throws Exception\InvalidArgumentException When not given an array as parameter
+ * @return self
+ */
+ public function setArguments($argv)
+ {
+ if (!is_array($argv)) {
+ throw new Exception\InvalidArgumentException("Parameter #1 to setArguments should be an array");
+ }
+ $this->argv = $argv;
+ $this->parsed = false;
+ return $this;
+ }
+
+ /**
+ * Define multiple configuration options from an associative array.
+ * These are not program options, but properties to configure
+ * the behavior of Zend\Console\Getopt.
+ *
+ * @param array $getoptConfig
+ * @return self
+ */
+ public function setOptions($getoptConfig)
+ {
+ if (isset($getoptConfig)) {
+ foreach ($getoptConfig as $key => $value) {
+ $this->setOption($key, $value);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Define one configuration option as a key/value pair.
+ * These are not program options, but properties to configure
+ * the behavior of Zend\Console\Getopt.
+ *
+ * @param string $configKey
+ * @param string $configValue
+ * @return self
+ */
+ public function setOption($configKey, $configValue)
+ {
+ if ($configKey !== null) {
+ $this->getoptConfig[$configKey] = $configValue;
+ }
+ return $this;
+ }
+
+ /**
+ * Define additional option rules.
+ * These are appended to the rules defined when the constructor was called.
+ *
+ * @param array $rules
+ * @return self
+ */
+ public function addRules($rules)
+ {
+ $ruleMode = $this->getoptConfig['ruleMode'];
+ switch ($this->getoptConfig['ruleMode']) {
+ case self::MODE_ZEND:
+ if (is_array($rules)) {
+ $this->_addRulesModeZend($rules);
+ break;
+ }
+ // intentional fallthrough
+ case self::MODE_GNU:
+ $this->_addRulesModeGnu($rules);
+ break;
+ default:
+ /**
+ * Call addRulesModeFoo() for ruleMode 'foo'.
+ * The developer should subclass Getopt and
+ * provide this method.
+ */
+ $method = '_addRulesMode' . ucfirst($ruleMode);
+ $this->$method($rules);
+ }
+ $this->parsed = false;
+ return $this;
+ }
+
+ /**
+ * Return the current set of options and parameters seen as a string.
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ $this->parse();
+ $s = array();
+ foreach ($this->options as $flag => $value) {
+ $s[] = $flag . '=' . ($value === true ? 'true' : $value);
+ }
+ return implode(' ', $s);
+ }
+
+ /**
+ * Return the current set of options and parameters seen
+ * as an array of canonical options and parameters.
+ *
+ * Clusters have been expanded, and option aliases
+ * have been mapped to their primary option names.
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ $this->parse();
+ $s = array();
+ foreach ($this->options as $flag => $value) {
+ $s[] = $flag;
+ if ($value !== true) {
+ $s[] = $value;
+ }
+ }
+ return $s;
+ }
+
+ /**
+ * Return the current set of options and parameters seen in Json format.
+ *
+ * @return string
+ */
+ public function toJson()
+ {
+ $this->parse();
+ $j = array();
+ foreach ($this->options as $flag => $value) {
+ $j['options'][] = array(
+ 'option' => array(
+ 'flag' => $flag,
+ 'parameter' => $value
+ )
+ );
+ }
+
+ $json = \Zend\Json\Json::encode($j);
+ return $json;
+ }
+
+ /**
+ * Return the current set of options and parameters seen in XML format.
+ *
+ * @return string
+ */
+ public function toXml()
+ {
+ $this->parse();
+ $doc = new \DomDocument('1.0', 'utf-8');
+ $optionsNode = $doc->createElement('options');
+ $doc->appendChild($optionsNode);
+ foreach ($this->options as $flag => $value) {
+ $optionNode = $doc->createElement('option');
+ $optionNode->setAttribute('flag', utf8_encode($flag));
+ if ($value !== true) {
+ $optionNode->setAttribute('parameter', utf8_encode($value));
+ }
+ $optionsNode->appendChild($optionNode);
+ }
+ $xml = $doc->saveXML();
+ return $xml;
+ }
+
+ /**
+ * Return a list of options that have been seen in the current argv.
+ *
+ * @return array
+ */
+ public function getOptions()
+ {
+ $this->parse();
+ return array_keys($this->options);
+ }
+
+ /**
+ * Return the state of the option seen on the command line of the
+ * current application invocation.
+ *
+ * This function returns true, or the parameter value to the option, if any.
+ * If the option was not given, this function returns false.
+ *
+ * @param string $flag
+ * @return mixed
+ */
+ public function getOption($flag)
+ {
+ $this->parse();
+ if ($this->getoptConfig[self::CONFIG_IGNORECASE]) {
+ $flag = strtolower($flag);
+ }
+ if (isset($this->ruleMap[$flag])) {
+ $flag = $this->ruleMap[$flag];
+ if (isset($this->options[$flag])) {
+ return $this->options[$flag];
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return the arguments from the command-line following all options found.
+ *
+ * @return array
+ */
+ public function getRemainingArgs()
+ {
+ $this->parse();
+ return $this->remainingArgs;
+ }
+
+ public function getArguments()
+ {
+ $result = $this->getRemainingArgs();
+ foreach ($this->getOptions() as $option) {
+ $result[$option] = $this->getOption($option);
+ }
+ return $result;
+ }
+
+ /**
+ * Return a useful option reference, formatted for display in an
+ * error message.
+ *
+ * Note that this usage information is provided in most Exceptions
+ * generated by this class.
+ *
+ * @return string
+ */
+ public function getUsageMessage()
+ {
+ $usage = "Usage: {$this->progname} [ options ]\n";
+ $maxLen = 20;
+ $lines = array();
+ foreach ($this->rules as $rule) {
+ if (isset($rule['isFreeformFlag'])) {
+ continue;
+ }
+ $flags = array();
+ if (is_array($rule['alias'])) {
+ foreach ($rule['alias'] as $flag) {
+ $flags[] = (strlen($flag) == 1 ? '-' : '--') . $flag;
+ }
+ }
+ $linepart['name'] = implode('|', $flags);
+ if (isset($rule['param']) && $rule['param'] != 'none') {
+ $linepart['name'] .= ' ';
+ switch ($rule['param']) {
+ case 'optional':
+ $linepart['name'] .= "[ <{$rule['paramType']}> ]";
+ break;
+ case 'required':
+ $linepart['name'] .= "<{$rule['paramType']}>";
+ break;
+ }
+ }
+ if (strlen($linepart['name']) > $maxLen) {
+ $maxLen = strlen($linepart['name']);
+ }
+ $linepart['help'] = '';
+ if (isset($rule['help'])) {
+ $linepart['help'] .= $rule['help'];
+ }
+ $lines[] = $linepart;
+ }
+ foreach ($lines as $linepart) {
+ $usage .= sprintf(
+ "%s %s\n",
+ str_pad($linepart['name'], $maxLen),
+ $linepart['help']
+ );
+ }
+ return $usage;
+ }
+
+ /**
+ * Define aliases for options.
+ *
+ * The parameter $aliasMap is an associative array
+ * mapping option name (short or long) to an alias.
+ *
+ * @param array $aliasMap
+ * @throws Exception\ExceptionInterface
+ * @return self
+ */
+ public function setAliases($aliasMap)
+ {
+ foreach ($aliasMap as $flag => $alias) {
+ if ($this->getoptConfig[self::CONFIG_IGNORECASE]) {
+ $flag = strtolower($flag);
+ $alias = strtolower($alias);
+ }
+ if (!isset($this->ruleMap[$flag])) {
+ continue;
+ }
+ $flag = $this->ruleMap[$flag];
+ if (isset($this->rules[$alias]) || isset($this->ruleMap[$alias])) {
+ $o = (strlen($alias) == 1 ? '-' : '--') . $alias;
+ throw new Exception\InvalidArgumentException("Option \"$o\" is being defined more than once.");
+ }
+ $this->rules[$flag]['alias'][] = $alias;
+ $this->ruleMap[$alias] = $flag;
+ }
+ return $this;
+ }
+
+ /**
+ * Define help messages for options.
+ *
+ * The parameter $helpMap is an associative array
+ * mapping option name (short or long) to the help string.
+ *
+ * @param array $helpMap
+ * @return self
+ */
+ public function setHelp($helpMap)
+ {
+ foreach ($helpMap as $flag => $help) {
+ if (!isset($this->ruleMap[$flag])) {
+ continue;
+ }
+ $flag = $this->ruleMap[$flag];
+ $this->rules[$flag]['help'] = $help;
+ }
+ return $this;
+ }
+
+ /**
+ * Parse command-line arguments and find both long and short
+ * options.
+ *
+ * Also find option parameters, and remaining arguments after
+ * all options have been parsed.
+ *
+ * @return self
+ */
+ public function parse()
+ {
+ if ($this->parsed === true) {
+ return $this;
+ }
+
+ $argv = $this->argv;
+ $this->options = array();
+ $this->remainingArgs = array();
+ while (count($argv) > 0) {
+ if ($argv[0] == '--') {
+ array_shift($argv);
+ if ($this->getoptConfig[self::CONFIG_DASHDASH]) {
+ $this->remainingArgs = array_merge($this->remainingArgs, $argv);
+ break;
+ }
+ }
+ if (substr($argv[0], 0, 2) == '--') {
+ $this->_parseLongOption($argv);
+ } elseif (substr($argv[0], 0, 1) == '-' && ('-' != $argv[0] || count($argv) >1)) {
+ $this->_parseShortOptionCluster($argv);
+ } elseif ($this->getoptConfig[self::CONFIG_PARSEALL]) {
+ $this->remainingArgs[] = array_shift($argv);
+ } else {
+ /*
+ * We should put all other arguments in remainingArgs and stop parsing
+ * since CONFIG_PARSEALL is false.
+ */
+ $this->remainingArgs = array_merge($this->remainingArgs, $argv);
+ break;
+ }
+ }
+ $this->parsed = true;
+
+ //go through parsed args and process callbacks
+ $this->triggerCallbacks();
+
+ return $this;
+ }
+
+ /**
+ * @param string $option The name of the property which, if present, will call the passed
+ * callback with the value of this parameter.
+ * @param callable $callback The callback that will be called for this option. The first
+ * parameter will be the value of getOption($option), the second
+ * parameter will be a reference to $this object. If the callback returns
+ * false then an Exception\RuntimeException will be thrown indicating that
+ * there is a parse issue with this option.
+ *
+ * @return self
+ */
+ public function setOptionCallback($option, \Closure $callback)
+ {
+ $this->optionCallbacks[$option] = $callback;
+
+ return $this;
+ }
+
+ /**
+ * Triggers all the registered callbacks.
+ */
+ protected function triggerCallbacks()
+ {
+ foreach ($this->optionCallbacks as $option => $callback) {
+ if (null === $this->getOption($option)) {
+ continue;
+ }
+ //make sure we've resolved the alias, if using one
+ if (isset($this->ruleMap[$option]) && $option = $this->ruleMap[$option]) {
+ if (false === $callback($this->getOption($option), $this)) {
+ throw new Exception\RuntimeException(
+ "The option $option is invalid. See usage.",
+ $this->getUsageMessage()
+ );
+ }
+ }
+ }
+ }
+
+ /**
+ * Parse command-line arguments for a single long option.
+ * A long option is preceded by a double '--' character.
+ * Long options may not be clustered.
+ *
+ * @param mixed &$argv
+ */
+ protected function _parseLongOption(&$argv)
+ {
+ $optionWithParam = ltrim(array_shift($argv), '-');
+ $l = explode('=', $optionWithParam, 2);
+ $flag = array_shift($l);
+ $param = array_shift($l);
+ if (isset($param)) {
+ array_unshift($argv, $param);
+ }
+ $this->_parseSingleOption($flag, $argv);
+ }
+
+ /**
+ * Parse command-line arguments for short options.
+ * Short options are those preceded by a single '-' character.
+ * Short options may be clustered.
+ *
+ * @param mixed &$argv
+ */
+ protected function _parseShortOptionCluster(&$argv)
+ {
+ $flagCluster = ltrim(array_shift($argv), '-');
+ foreach (str_split($flagCluster) as $flag) {
+ $this->_parseSingleOption($flag, $argv);
+ }
+ }
+
+ /**
+ * Parse command-line arguments for a single option.
+ *
+ * @param string $flag
+ * @param mixed $argv
+ * @throws Exception\ExceptionInterface
+ */
+ protected function _parseSingleOption($flag, &$argv)
+ {
+ if ($this->getoptConfig[self::CONFIG_IGNORECASE]) {
+ $flag = strtolower($flag);
+ }
+
+ // Check if this option is numeric one
+ if (preg_match('/^\d+$/', $flag)) {
+ return $this->_setNumericOptionValue($flag);
+ }
+
+ if (!isset($this->ruleMap[$flag])) {
+ // Don't throw Exception for flag-like param in case when freeform flags are allowed
+ if (!$this->getoptConfig[self::CONFIG_FREEFORM_FLAGS]) {
+ throw new Exception\RuntimeException(
+ "Option \"$flag\" is not recognized.",
+ $this->getUsageMessage()
+ );
+ }
+
+ // Magic methods in future will use this mark as real flag value
+ $this->ruleMap[$flag] = $flag;
+ $realFlag = $flag;
+ $this->rules[$realFlag] = array(
+ 'param' => 'optional',
+ 'isFreeformFlag' => true
+ );
+ } else {
+ $realFlag = $this->ruleMap[$flag];
+ }
+
+ switch ($this->rules[$realFlag]['param']) {
+ case 'required':
+ if (count($argv) > 0) {
+ $param = array_shift($argv);
+ $this->_checkParameterType($realFlag, $param);
+ } else {
+ throw new Exception\RuntimeException(
+ "Option \"$flag\" requires a parameter.",
+ $this->getUsageMessage()
+ );
+ }
+ break;
+ case 'optional':
+ if (count($argv) > 0 && substr($argv[0], 0, 1) != '-') {
+ $param = array_shift($argv);
+ $this->_checkParameterType($realFlag, $param);
+ } else {
+ $param = true;
+ }
+ break;
+ default:
+ $param = true;
+ }
+
+ $this->_setSingleOptionValue($realFlag, $param);
+ }
+
+ /**
+ * Set given value as value of numeric option
+ *
+ * Throw runtime exception if this action is deny by configuration
+ * or no one numeric option handlers is defined
+ *
+ * @param int $value
+ * @throws Exception\RuntimeException
+ * @return void
+ */
+ protected function _setNumericOptionValue($value)
+ {
+ if (!$this->getoptConfig[self::CONFIG_NUMERIC_FLAGS]) {
+ throw new Exception\RuntimeException("Using of numeric flags are deny by configuration");
+ }
+
+ if (empty($this->getoptConfig['numericFlagsOption'])) {
+ throw new Exception\RuntimeException("Any option for handling numeric flags are specified");
+ }
+
+ return $this->_setSingleOptionValue($this->getoptConfig['numericFlagsOption'], $value);
+ }
+
+ /**
+ * Add relative to options' flag value
+ *
+ * If options list already has current flag as key
+ * and parser should follow cumulative params by configuration,
+ * we should to add new param to array, not to overwrite
+ *
+ * @param string $flag
+ * @param string $value
+ */
+ protected function _setSingleOptionValue($flag, $value)
+ {
+ if (true === $value && $this->getoptConfig[self::CONFIG_CUMULATIVE_FLAGS]) {
+ // For boolean values we have to create new flag, or increase number of flags' usage count
+ return $this->_setBooleanFlagValue($flag);
+ }
+
+ // Split multiple values, if necessary
+ // Filter empty values from splited array
+ $separator = $this->getoptConfig[self::CONFIG_PARAMETER_SEPARATOR];
+ if (is_string($value) && !empty($separator) && is_string($separator) && substr_count($value, $separator)) {
+ $value = array_filter(explode($separator, $value));
+ }
+
+ if (!array_key_exists($flag, $this->options)) {
+ $this->options[$flag] = $value;
+ } elseif ($this->getoptConfig[self::CONFIG_CUMULATIVE_PARAMETERS]) {
+ $this->options[$flag] = (array) $this->options[$flag];
+ array_push($this->options[$flag], $value);
+ } else {
+ $this->options[$flag] = $value;
+ }
+ }
+
+ /**
+ * Set TRUE value to given flag, if this option does not exist yet
+ * In other case increase value to show count of flags' usage
+ *
+ * @param string $flag
+ */
+ protected function _setBooleanFlagValue($flag)
+ {
+ $this->options[$flag] = array_key_exists($flag, $this->options)
+ ? (int) $this->options[$flag] + 1
+ : true;
+ }
+
+ /**
+ * Return true if the parameter is in a valid format for
+ * the option $flag.
+ * Throw an exception in most other cases.
+ *
+ * @param string $flag
+ * @param string $param
+ * @throws Exception\ExceptionInterface
+ * @return bool
+ */
+ protected function _checkParameterType($flag, $param)
+ {
+ $type = 'string';
+ if (isset($this->rules[$flag]['paramType'])) {
+ $type = $this->rules[$flag]['paramType'];
+ }
+ switch ($type) {
+ case 'word':
+ if (preg_match('/\W/', $param)) {
+ throw new Exception\RuntimeException(
+ "Option \"$flag\" requires a single-word parameter, but was given \"$param\".",
+ $this->getUsageMessage()
+ );
+ }
+ break;
+ case 'integer':
+ if (preg_match('/\D/', $param)) {
+ throw new Exception\RuntimeException(
+ "Option \"$flag\" requires an integer parameter, but was given \"$param\".",
+ $this->getUsageMessage()
+ );
+ }
+ break;
+ case 'string':
+ default:
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * Define legal options using the gnu-style format.
+ *
+ * @param string $rules
+ */
+ protected function _addRulesModeGnu($rules)
+ {
+ $ruleArray = array();
+
+ /**
+ * Options may be single alphanumeric characters.
+ * Options may have a ':' which indicates a required string parameter.
+ * No long options or option aliases are supported in GNU style.
+ */
+ preg_match_all('/([a-zA-Z0-9]:?)/', $rules, $ruleArray);
+ foreach ($ruleArray[1] as $rule) {
+ $r = array();
+ $flag = substr($rule, 0, 1);
+ if ($this->getoptConfig[self::CONFIG_IGNORECASE]) {
+ $flag = strtolower($flag);
+ }
+ $r['alias'][] = $flag;
+ if (substr($rule, 1, 1) == ':') {
+ $r['param'] = 'required';
+ $r['paramType'] = 'string';
+ } else {
+ $r['param'] = 'none';
+ }
+ $this->rules[$flag] = $r;
+ $this->ruleMap[$flag] = $flag;
+ }
+ }
+
+ /**
+ * Define legal options using the Zend-style format.
+ *
+ * @param array $rules
+ * @throws Exception\ExceptionInterface
+ */
+ protected function _addRulesModeZend($rules)
+ {
+ foreach ($rules as $ruleCode => $helpMessage) {
+ // this may have to translate the long parm type if there
+ // are any complaints that =string will not work (even though that use
+ // case is not documented)
+ if (in_array(substr($ruleCode, -2, 1), array('-', '='))) {
+ $flagList = substr($ruleCode, 0, -2);
+ $delimiter = substr($ruleCode, -2, 1);
+ $paramType = substr($ruleCode, -1);
+ } else {
+ $flagList = $ruleCode;
+ $delimiter = $paramType = null;
+ }
+ if ($this->getoptConfig[self::CONFIG_IGNORECASE]) {
+ $flagList = strtolower($flagList);
+ }
+ $flags = explode('|', $flagList);
+ $rule = array();
+ $mainFlag = $flags[0];
+ foreach ($flags as $flag) {
+ if (empty($flag)) {
+ throw new Exception\InvalidArgumentException("Blank flag not allowed in rule \"$ruleCode\".");
+ }
+ if (strlen($flag) == 1) {
+ if (isset($this->ruleMap[$flag])) {
+ throw new Exception\InvalidArgumentException(
+ "Option \"-$flag\" is being defined more than once."
+ );
+ }
+ $this->ruleMap[$flag] = $mainFlag;
+ $rule['alias'][] = $flag;
+ } else {
+ if (isset($this->rules[$flag]) || isset($this->ruleMap[$flag])) {
+ throw new Exception\InvalidArgumentException(
+ "Option \"--$flag\" is being defined more than once."
+ );
+ }
+ $this->ruleMap[$flag] = $mainFlag;
+ $rule['alias'][] = $flag;
+ }
+ }
+ if (isset($delimiter)) {
+ switch ($delimiter) {
+ case self::PARAM_REQUIRED:
+ $rule['param'] = 'required';
+ break;
+ case self::PARAM_OPTIONAL:
+ default:
+ $rule['param'] = 'optional';
+ }
+ switch (substr($paramType, 0, 1)) {
+ case self::TYPE_WORD:
+ $rule['paramType'] = 'word';
+ break;
+ case self::TYPE_INTEGER:
+ $rule['paramType'] = 'integer';
+ break;
+ case self::TYPE_NUMERIC_FLAG:
+ $rule['paramType'] = 'numericFlag';
+ $this->getoptConfig['numericFlagsOption'] = $mainFlag;
+ break;
+ case self::TYPE_STRING:
+ default:
+ $rule['paramType'] = 'string';
+ }
+ } else {
+ $rule['param'] = 'none';
+ }
+ $rule['help'] = $helpMessage;
+ $this->rules[$mainFlag] = $rule;
+ }
+ }
+}
diff --git a/library/Zend/Console/Prompt/AbstractPrompt.php b/library/Zend/Console/Prompt/AbstractPrompt.php
new file mode 100755
index 0000000000..cd717cf35c
--- /dev/null
+++ b/library/Zend/Console/Prompt/AbstractPrompt.php
@@ -0,0 +1,85 @@
+lastResponse;
+ }
+
+ /**
+ * Return console adapter to use when showing prompt.
+ *
+ * @return ConsoleAdapter
+ */
+ public function getConsole()
+ {
+ if (!$this->console) {
+ $this->console = Console::getInstance();
+ }
+
+ return $this->console;
+ }
+
+ /**
+ * Set console adapter to use when showing prompt.
+ *
+ * @param ConsoleAdapter $adapter
+ */
+ public function setConsole(ConsoleAdapter $adapter)
+ {
+ $this->console = $adapter;
+ }
+
+ /**
+ * Create an instance of this prompt, show it and return response.
+ *
+ * This is a convenience method for creating statically creating prompts, i.e.:
+ *
+ * $name = Zend\Console\Prompt\Line::prompt("Enter your name: ");
+ *
+ * @return mixed
+ * @throws Exception\BadMethodCallException
+ */
+ public static function prompt()
+ {
+ if (get_called_class() === __CLASS__) {
+ throw new Exception\BadMethodCallException(
+ 'Cannot call prompt() on AbstractPrompt class. Use one of the Zend\Console\Prompt\ subclasses.'
+ );
+ }
+
+ $refl = new ReflectionClass(get_called_class());
+ $instance = $refl->newInstanceArgs(func_get_args());
+ return $instance->show();
+ }
+}
diff --git a/library/Zend/Console/Prompt/Char.php b/library/Zend/Console/Prompt/Char.php
new file mode 100755
index 0000000000..b6c3051453
--- /dev/null
+++ b/library/Zend/Console/Prompt/Char.php
@@ -0,0 +1,186 @@
+setPromptText($promptText);
+ $this->setAllowEmpty($allowEmpty);
+ $this->setIgnoreCase($ignoreCase);
+
+ if (null != $allowedChars) {
+ if ($this->ignoreCase) {
+ $this->setAllowedChars(strtolower($allowedChars));
+ } else {
+ $this->setAllowedChars($allowedChars);
+ }
+ }
+
+ $this->setEcho($echo);
+ }
+
+ /**
+ * Show the prompt to user and return a single char.
+ *
+ * @return string
+ */
+ public function show()
+ {
+ $this->getConsole()->write($this->promptText);
+ $mask = $this->getAllowedChars();
+
+ /**
+ * Normalize the mask if case is irrelevant
+ */
+ if ($this->ignoreCase) {
+ $mask = strtolower($mask); // lowercase all
+ $mask .= strtoupper($mask); // uppercase and append
+ $mask = str_split($mask); // convert to array
+ $mask = array_unique($mask); // remove duplicates
+ $mask = implode("", $mask); // convert back to string
+ }
+
+ /**
+ * Read char from console
+ */
+ $char = $this->getConsole()->readChar($mask);
+
+ if ($this->echo) {
+ echo trim($char)."\n";
+ } else {
+ if ($this->promptText) {
+ echo "\n"; // skip to next line but only if we had any prompt text
+ }
+ }
+
+ return $this->lastResponse = $char;
+ }
+
+ /**
+ * @param bool $allowEmpty
+ */
+ public function setAllowEmpty($allowEmpty)
+ {
+ $this->allowEmpty = (bool) $allowEmpty;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getAllowEmpty()
+ {
+ return $this->allowEmpty;
+ }
+
+ /**
+ * @param string $promptText
+ */
+ public function setPromptText($promptText)
+ {
+ $this->promptText = $promptText;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPromptText()
+ {
+ return $this->promptText;
+ }
+
+ /**
+ * @param string $allowedChars
+ */
+ public function setAllowedChars($allowedChars)
+ {
+ $this->allowedChars = $allowedChars;
+ }
+
+ /**
+ * @return string
+ */
+ public function getAllowedChars()
+ {
+ return $this->allowedChars;
+ }
+
+ /**
+ * @param bool $ignoreCase
+ */
+ public function setIgnoreCase($ignoreCase)
+ {
+ $this->ignoreCase = (bool) $ignoreCase;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getIgnoreCase()
+ {
+ return $this->ignoreCase;
+ }
+
+ /**
+ * @param bool $echo
+ */
+ public function setEcho($echo)
+ {
+ $this->echo = (bool) $echo;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getEcho()
+ {
+ return $this->echo;
+ }
+}
diff --git a/library/Zend/Console/Prompt/Confirm.php b/library/Zend/Console/Prompt/Confirm.php
new file mode 100755
index 0000000000..f660452be7
--- /dev/null
+++ b/library/Zend/Console/Prompt/Confirm.php
@@ -0,0 +1,113 @@
+setPromptText($promptText);
+ }
+
+ if ($yesChar !== null) {
+ $this->setYesChar($yesChar);
+ }
+
+ if ($noChar !== null) {
+ $this->setNoChar($noChar);
+ }
+ }
+
+ /**
+ * Show the confirmation message and return result.
+ *
+ * @return bool
+ */
+ public function show()
+ {
+ $char = parent::show();
+ if ($this->ignoreCase) {
+ $response = strtolower($char) === strtolower($this->yesChar);
+ } else {
+ $response = $char === $this->yesChar;
+ }
+ return $this->lastResponse = $response;
+ }
+
+ /**
+ * @param string $noChar
+ */
+ public function setNoChar($noChar)
+ {
+ $this->noChar = $noChar;
+ $this->setAllowedChars($this->yesChar . $this->noChar);
+ }
+
+ /**
+ * @return string
+ */
+ public function getNoChar()
+ {
+ return $this->noChar;
+ }
+
+ /**
+ * @param string $yesChar
+ */
+ public function setYesChar($yesChar)
+ {
+ $this->yesChar = $yesChar;
+ $this->setAllowedChars($this->yesChar . $this->noChar);
+ }
+
+ /**
+ * @return string
+ */
+ public function getYesChar()
+ {
+ return $this->yesChar;
+ }
+}
diff --git a/library/Zend/Console/Prompt/Line.php b/library/Zend/Console/Prompt/Line.php
new file mode 100755
index 0000000000..7a7427d7a5
--- /dev/null
+++ b/library/Zend/Console/Prompt/Line.php
@@ -0,0 +1,113 @@
+setPromptText($promptText);
+ }
+
+ if ($allowEmpty !== null) {
+ $this->setAllowEmpty($allowEmpty);
+ }
+
+ if ($maxLength !== null) {
+ $this->setMaxLength($maxLength);
+ }
+ }
+
+ /**
+ * Show the prompt to user and return the answer.
+ *
+ * @return string
+ */
+ public function show()
+ {
+ do {
+ $this->getConsole()->write($this->promptText);
+ $line = $this->getConsole()->readLine($this->maxLength);
+ } while (!$this->allowEmpty && !$line);
+
+ return $this->lastResponse = $line;
+ }
+
+ /**
+ * @param bool $allowEmpty
+ */
+ public function setAllowEmpty($allowEmpty)
+ {
+ $this->allowEmpty = $allowEmpty;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getAllowEmpty()
+ {
+ return $this->allowEmpty;
+ }
+
+ /**
+ * @param int $maxLength
+ */
+ public function setMaxLength($maxLength)
+ {
+ $this->maxLength = $maxLength;
+ }
+
+ /**
+ * @return int
+ */
+ public function getMaxLength()
+ {
+ return $this->maxLength;
+ }
+
+ /**
+ * @param string $promptText
+ */
+ public function setPromptText($promptText)
+ {
+ $this->promptText = $promptText;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPromptText()
+ {
+ return $this->promptText;
+ }
+}
diff --git a/library/Zend/Console/Prompt/Number.php b/library/Zend/Console/Prompt/Number.php
new file mode 100755
index 0000000000..8ce5330053
--- /dev/null
+++ b/library/Zend/Console/Prompt/Number.php
@@ -0,0 +1,208 @@
+setPromptText($promptText);
+ }
+
+ if ($allowEmpty !== null) {
+ $this->setAllowEmpty($allowEmpty);
+ }
+
+ if ($min !== null) {
+ $this->setMin($min);
+ }
+
+ if ($max !== null) {
+ $this->setMax($max);
+ }
+
+ if ($allowFloat !== null) {
+ $this->setAllowFloat($allowFloat);
+ }
+ }
+
+ /**
+ * Show the prompt to user and return the answer.
+ *
+ * @return mixed
+ */
+ public function show()
+ {
+ /**
+ * Ask for a number and validate it.
+ */
+ do {
+ $valid = true;
+ $number = parent::show();
+ if ($number === "" && !$this->allowEmpty) {
+ $valid = false;
+ } elseif ($number === "") {
+ $number = null;
+ } elseif (!is_numeric($number)) {
+ $this->getConsole()->writeLine("$number is not a number\n");
+ $valid = false;
+ } elseif (!$this->allowFloat && (round($number) != $number)) {
+ $this->getConsole()->writeLine("Please enter a non-floating number, i.e. " . round($number) . "\n");
+ $valid = false;
+ } elseif ($this->max !== null && $number > $this->max) {
+ $this->getConsole()->writeLine("Please enter a number not greater than " . $this->max . "\n");
+ $valid = false;
+ } elseif ($this->min !== null && $number < $this->min) {
+ $this->getConsole()->writeLine("Please enter a number not smaller than " . $this->min . "\n");
+ $valid = false;
+ }
+ } while (!$valid);
+
+ /**
+ * Cast proper type
+ */
+ if ($number !== null) {
+ $number = $this->allowFloat ? (double) $number : (int) $number;
+ }
+
+ return $this->lastResponse = $number;
+ }
+
+ /**
+ * @param bool $allowEmpty
+ */
+ public function setAllowEmpty($allowEmpty)
+ {
+ $this->allowEmpty = $allowEmpty;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getAllowEmpty()
+ {
+ return $this->allowEmpty;
+ }
+
+ /**
+ * @param int $maxLength
+ */
+ public function setMaxLength($maxLength)
+ {
+ $this->maxLength = $maxLength;
+ }
+
+ /**
+ * @return int
+ */
+ public function getMaxLength()
+ {
+ return $this->maxLength;
+ }
+
+ /**
+ * @param string $promptText
+ */
+ public function setPromptText($promptText)
+ {
+ $this->promptText = $promptText;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPromptText()
+ {
+ return $this->promptText;
+ }
+
+ /**
+ * @param int $max
+ */
+ public function setMax($max)
+ {
+ $this->max = $max;
+ }
+
+ /**
+ * @return int
+ */
+ public function getMax()
+ {
+ return $this->max;
+ }
+
+ /**
+ * @param int $min
+ */
+ public function setMin($min)
+ {
+ $this->min = $min;
+ }
+
+ /**
+ * @return int
+ */
+ public function getMin()
+ {
+ return $this->min;
+ }
+
+ /**
+ * @param bool $allowFloat
+ */
+ public function setAllowFloat($allowFloat)
+ {
+ $this->allowFloat = $allowFloat;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getAllowFloat()
+ {
+ return $this->allowFloat;
+ }
+}
diff --git a/library/Zend/Console/Prompt/PromptInterface.php b/library/Zend/Console/Prompt/PromptInterface.php
new file mode 100755
index 0000000000..da1b6215c1
--- /dev/null
+++ b/library/Zend/Console/Prompt/PromptInterface.php
@@ -0,0 +1,44 @@
+setPromptText($promptText);
+ }
+
+ if (!count($options)) {
+ throw new Exception\BadMethodCallException(
+ 'Cannot construct a "select" prompt without any options'
+ );
+ }
+
+ $this->setOptions($options);
+
+ if ($allowEmpty !== null) {
+ $this->setAllowEmpty($allowEmpty);
+ }
+
+ if ($echo !== null) {
+ $this->setEcho($echo);
+ }
+ }
+
+ /**
+ * Show a list of options and prompt the user to select one of them.
+ *
+ * @return string Selected option
+ */
+ public function show()
+ {
+ // Show prompt text and available options
+ $console = $this->getConsole();
+ $console->writeLine($this->promptText);
+ foreach ($this->options as $k => $v) {
+ $console->writeLine(' ' . $k . ') ' . $v);
+ }
+
+ // Prepare mask
+ $mask = implode("", array_keys($this->options));
+ if ($this->allowEmpty) {
+ $mask .= "\r\n";
+ }
+
+ // Prepare other params for parent class
+ $this->setAllowedChars($mask);
+ $oldPrompt = $this->promptText;
+ $oldEcho = $this->echo;
+ $this->echo = false;
+ $this->promptText = null;
+
+ // Retrieve a single character
+ $response = parent::show();
+
+ // Restore old params
+ $this->promptText = $oldPrompt;
+ $this->echo = $oldEcho;
+
+ // Display selected option if echo is enabled
+ if ($this->echo) {
+ if (isset($this->options[$response])) {
+ $console->writeLine($this->options[$response]);
+ } else {
+ $console->writeLine();
+ }
+ }
+
+ $this->lastResponse = $response;
+ return $response;
+ }
+
+ /**
+ * Set allowed options
+ *
+ * @param array|\Traversable $options
+ * @throws Exception\BadMethodCallException
+ */
+ public function setOptions($options)
+ {
+ if (!is_array($options) && !$options instanceof \Traversable) {
+ throw new Exception\BadMethodCallException(
+ 'Please specify an array or Traversable object as options'
+ );
+ }
+
+ if (!is_array($options)) {
+ $this->options = array();
+ foreach ($options as $k => $v) {
+ $this->options[$k] = $v;
+ }
+ } else {
+ $this->options = $options;
+ }
+ }
+
+ /**
+ * @return array
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+}
diff --git a/library/Zend/Console/README.md b/library/Zend/Console/README.md
new file mode 100755
index 0000000000..eb8566c0f2
--- /dev/null
+++ b/library/Zend/Console/README.md
@@ -0,0 +1,15 @@
+Console Component from ZF2
+==========================
+
+This is the Console component for ZF2.
+
+- File issues at https://github.com/zendframework/zf2/issues
+- Create pull requests against https://github.com/zendframework/zf2
+- Documentation is at http://framework.zend.com/docs
+
+LICENSE
+-------
+
+The files in this archive are released under the [Zend Framework
+license](http://framework.zend.com/license), which is a 3-clause BSD license.
+
diff --git a/library/Zend/Console/Request.php b/library/Zend/Console/Request.php
new file mode 100755
index 0000000000..57b48bb2cb
--- /dev/null
+++ b/library/Zend/Console/Request.php
@@ -0,0 +1,197 @@
+ 0) {
+ $this->setScriptName(array_shift($args));
+ }
+
+ /**
+ * Store runtime params
+ */
+ $this->params()->fromArray($args);
+ $this->setContent($args);
+
+ /**
+ * Store environment data
+ */
+ $this->env()->fromArray($env);
+ }
+
+ /**
+ * Exchange parameters object
+ *
+ * @param \Zend\Stdlib\Parameters $params
+ * @return Request
+ */
+ public function setParams(Parameters $params)
+ {
+ $this->params = $params;
+ $this->setContent($params);
+ return $this;
+ }
+
+ /**
+ * Return the container responsible for parameters
+ *
+ * @return \Zend\Stdlib\Parameters
+ */
+ public function getParams()
+ {
+ if ($this->params === null) {
+ $this->params = new Parameters();
+ }
+
+ return $this->params;
+ }
+
+ /**
+ * Return a single parameter.
+ * Shortcut for $request->params()->get()
+ *
+ * @param string $name Parameter name
+ * @param string $default (optional) default value in case the parameter does not exist
+ * @return mixed
+ */
+ public function getParam($name, $default = null)
+ {
+ return $this->params()->get($name, $default);
+ }
+
+ /**
+ * Return the container responsible for parameters
+ *
+ * @return \Zend\Stdlib\Parameters
+ */
+ public function params()
+ {
+ return $this->getParams();
+ }
+
+ /**
+ * Provide an alternate Parameter Container implementation for env parameters in this object, (this is NOT the
+ * primary API for value setting, for that see env())
+ *
+ * @param \Zend\Stdlib\Parameters $env
+ * @return \Zend\Console\Request
+ */
+ public function setEnv(Parameters $env)
+ {
+ $this->envParams = $env;
+ return $this;
+ }
+
+ /**
+ * Return a single parameter container responsible for env parameters
+ *
+ * @param string $name Parameter name
+ * @param string $default (optional) default value in case the parameter does not exist
+ * @return \Zend\Stdlib\Parameters
+ */
+ public function getEnv($name, $default = null)
+ {
+ return $this->env()->get($name, $default);
+ }
+
+ /**
+ * Return the parameter container responsible for env parameters
+ *
+ * @return \Zend\Stdlib\Parameters
+ */
+ public function env()
+ {
+ if ($this->envParams === null) {
+ $this->envParams = new Parameters();
+ }
+
+ return $this->envParams;
+ }
+
+ /**
+ * @return string
+ */
+ public function toString()
+ {
+ return trim(implode(' ', $this->params()->toArray()));
+ }
+
+ /**
+ * Allow PHP casting of this object
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * @param string $scriptName
+ */
+ public function setScriptName($scriptName)
+ {
+ $this->scriptName = $scriptName;
+ }
+
+ /**
+ * @return string
+ */
+ public function getScriptName()
+ {
+ return $this->scriptName;
+ }
+}
diff --git a/library/Zend/Console/Response.php b/library/Zend/Console/Response.php
new file mode 100755
index 0000000000..75a068614c
--- /dev/null
+++ b/library/Zend/Console/Response.php
@@ -0,0 +1,80 @@
+contentSent;
+ }
+
+ /**
+ * Set the error level that will be returned to shell.
+ *
+ * @param int $errorLevel
+ * @return Response
+ */
+ public function setErrorLevel($errorLevel)
+ {
+ $this->setMetadata('errorLevel', $errorLevel);
+ return $this;
+ }
+
+ /**
+ * Get response error level that will be returned to shell.
+ *
+ * @return int|0
+ */
+ public function getErrorLevel()
+ {
+ return $this->getMetadata('errorLevel', 0);
+ }
+
+ /**
+ * Send content
+ *
+ * @return Response
+ * @deprecated
+ */
+ public function sendContent()
+ {
+ if ($this->contentSent()) {
+ return $this;
+ }
+ echo $this->getContent();
+ $this->contentSent = true;
+ return $this;
+ }
+
+ /**
+ * @deprecated
+ */
+ public function send()
+ {
+ $this->sendContent();
+ $errorLevel = (int) $this->getMetadata('errorLevel', 0);
+ exit($errorLevel);
+ }
+}
diff --git a/library/Zend/Console/RouteMatcher/DefaultRouteMatcher.php b/library/Zend/Console/RouteMatcher/DefaultRouteMatcher.php
new file mode 100755
index 0000000000..a5f8fae575
--- /dev/null
+++ b/library/Zend/Console/RouteMatcher/DefaultRouteMatcher.php
@@ -0,0 +1,780 @@
+defaults = $defaults;
+ $this->constraints = $constraints;
+ $this->aliases = $aliases;
+
+ if ($filters !== null) {
+ foreach ($filters as $name => $filter) {
+ if (!$filter instanceof FilterInterface) {
+ throw new Exception\InvalidArgumentException('Cannot use ' . gettype($filters) . ' as filter for ' . __CLASS__);
+ }
+ $this->filters[$name] = $filter;
+ }
+ }
+
+ if ($validators !== null) {
+ foreach ($validators as $name => $validator) {
+ if (!$validator instanceof ValidatorInterface) {
+ throw new Exception\InvalidArgumentException('Cannot use ' . gettype($validator) . ' as validator for ' . __CLASS__);
+ }
+ $this->validators[$name] = $validator;
+ }
+ }
+
+ $this->parts = $this->parseDefinition($route);
+ }
+
+ /**
+ * Parse a route definition.
+ *
+ * @param string $def
+ * @return array
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function parseDefinition($def)
+ {
+ $def = trim($def);
+ $pos = 0;
+ $length = strlen($def);
+ $parts = array();
+ $unnamedGroupCounter = 1;
+
+ while ($pos < $length) {
+ /**
+ * Optional value param, i.e.
+ * [SOMETHING]
+ */
+ if (preg_match('/\G\[(?P[A-Z][A-Z0-9\_\-]*?)\](?: +|$)/s', $def, $m, 0, $pos)) {
+ $item = array(
+ 'name' => strtolower($m['name']),
+ 'literal' => false,
+ 'required' => false,
+ 'positional' => true,
+ 'hasValue' => true,
+ );
+ }
+ /**
+ * Mandatory value param, i.e.
+ * SOMETHING
+ */
+ elseif (preg_match('/\G(?P[A-Z][A-Z0-9\_\-]*?)(?: +|$)/s', $def, $m, 0, $pos)) {
+ $item = array(
+ 'name' => strtolower($m['name']),
+ 'literal' => false,
+ 'required' => true,
+ 'positional' => true,
+ 'hasValue' => true,
+ );
+ }
+ /**
+ * Optional literal param, i.e.
+ * [something]
+ */
+ elseif (preg_match('/\G\[ *?(?P[a-zA-Z][a-zA-Z0-9\_\-]*?) *?\](?: +|$)/s', $def, $m, 0, $pos)) {
+ $item = array(
+ 'name' => $m['name'],
+ 'literal' => true,
+ 'required' => false,
+ 'positional' => true,
+ 'hasValue' => false,
+ );
+ }
+ /**
+ * Optional value param, syntax 2, i.e.
+ * []
+ */
+ elseif (preg_match('/\G\[ *\<(?P[a-zA-Z][a-zA-Z0-9\_\-]*?)\> *\](?: +|$)/s', $def, $m, 0, $pos)) {
+ $item = array(
+ 'name' => $m['name'],
+ 'literal' => false,
+ 'required' => false,
+ 'positional' => true,
+ 'hasValue' => true,
+ );
+ }
+ /**
+ * Mandatory value param, i.e.
+ *
+ */
+ elseif (preg_match('/\G\< *(?P[a-zA-Z][a-zA-Z0-9\_\-]*?) *\>(?: +|$)/s', $def, $m, 0, $pos)) {
+ $item = array(
+ 'name' => $m['name'],
+ 'literal' => false,
+ 'required' => true,
+ 'positional' => true,
+ 'hasValue' => true,
+ );
+ }
+ /**
+ * Mandatory literal param, i.e.
+ * something
+ */
+ elseif (preg_match('/\G(?P[a-zA-Z][a-zA-Z0-9\_\-]*?)(?: +|$)/s', $def, $m, 0, $pos)) {
+ $item = array(
+ 'name' => $m['name'],
+ 'literal' => true,
+ 'required' => true,
+ 'positional' => true,
+ 'hasValue' => false,
+ );
+ }
+ /**
+ * Mandatory long param
+ * --param=
+ * --param=whatever
+ */
+ elseif (preg_match('/\G--(?P[a-zA-Z0-9][a-zA-Z0-9\_\-]+)(?P=\S*?)?(?: +|$)/s', $def, $m, 0, $pos)) {
+ $item = array(
+ 'name' => $m['name'],
+ 'short' => false,
+ 'literal' => false,
+ 'required' => true,
+ 'positional' => false,
+ 'hasValue' => !empty($m['hasValue']),
+ );
+ }
+ /**
+ * Optional long flag
+ * [--param]
+ */
+ elseif (preg_match(
+ '/\G\[ *?--(?P[a-zA-Z0-9][a-zA-Z0-9\_\-]+) *?\](?: +|$)/s', $def, $m, 0, $pos
+ )) {
+ $item = array(
+ 'name' => $m['name'],
+ 'short' => false,
+ 'literal' => false,
+ 'required' => false,
+ 'positional' => false,
+ 'hasValue' => false,
+ );
+ }
+ /**
+ * Optional long param
+ * [--param=]
+ * [--param=whatever]
+ */
+ elseif (preg_match(
+ '/\G\[ *?--(?P[a-zA-Z0-9][a-zA-Z0-9\_\-]+)(?P=\S*?)? *?\](?: +|$)/s', $def, $m, 0, $pos
+ )) {
+ $item = array(
+ 'name' => $m['name'],
+ 'short' => false,
+ 'literal' => false,
+ 'required' => false,
+ 'positional' => false,
+ 'hasValue' => !empty($m['hasValue']),
+ );
+ }
+ /**
+ * Mandatory short param
+ * -a
+ * -a=i
+ * -a=s
+ * -a=w
+ */
+ elseif (preg_match('/\G-(?P[a-zA-Z0-9])(?:=(?P[ns]))?(?: +|$)/s', $def, $m, 0, $pos)) {
+ $item = array(
+ 'name' => $m['name'],
+ 'short' => true,
+ 'literal' => false,
+ 'required' => true,
+ 'positional' => false,
+ 'hasValue' => !empty($m['type']) ? $m['type'] : null,
+ );
+ }
+ /**
+ * Optional short param
+ * [-a]
+ * [-a=n]
+ * [-a=s]
+ */
+ elseif (preg_match('/\G\[ *?-(?P[a-zA-Z0-9])(?:=(?P[ns]))? *?\](?: +|$)/s', $def, $m, 0, $pos)) {
+ $item = array(
+ 'name' => $m['name'],
+ 'short' => true,
+ 'literal' => false,
+ 'required' => false,
+ 'positional' => false,
+ 'hasValue' => !empty($m['type']) ? $m['type'] : null,
+ );
+ }
+ /**
+ * Optional literal param alternative
+ * [ something | somethingElse | anotherOne ]
+ * [ something | somethingElse | anotherOne ]:namedGroup
+ */
+ elseif (preg_match('/
+ \G
+ \[
+ (?P
+ (?:
+ \ *?
+ (?P[a-zA-Z][a-zA-Z0-9_\-]*?)
+ \ *?
+ (?:\||(?=\]))
+ \ *?
+ )+
+ )
+ \]
+ (?:\:(?P[a-zA-Z0-9]+))?
+ (?:\ +|$)
+ /sx', $def, $m, 0, $pos
+ )
+ ) {
+ // extract available options
+ $options = preg_split('/ *\| */', trim($m['options']), 0, PREG_SPLIT_NO_EMPTY);
+
+ // remove dupes
+ array_unique($options);
+
+ // prepare item
+ $item = array(
+ 'name' => isset($m['groupName']) ? $m['groupName'] : 'unnamedGroup' . $unnamedGroupCounter++,
+ 'literal' => true,
+ 'required' => false,
+ 'positional' => true,
+ 'alternatives' => $options,
+ 'hasValue' => false,
+ );
+ }
+
+ /**
+ * Required literal param alternative
+ * ( something | somethingElse | anotherOne )
+ * ( something | somethingElse | anotherOne ):namedGroup
+ */
+ elseif (preg_match('/
+ \G
+ \(
+ (?P
+ (?:
+ \ *?
+ (?P[a-zA-Z][a-zA-Z0-9_\-]+)
+ \ *?
+ (?:\||(?=\)))
+ \ *?
+ )+
+ )
+ \)
+ (?:\:(?P[a-zA-Z0-9]+))?
+ (?:\ +|$)
+ /sx', $def, $m, 0, $pos
+ )) {
+ // extract available options
+ $options = preg_split('/ *\| */', trim($m['options']), 0, PREG_SPLIT_NO_EMPTY);
+
+ // remove dupes
+ array_unique($options);
+
+ // prepare item
+ $item = array(
+ 'name' => isset($m['groupName']) ? $m['groupName']:'unnamedGroupAt' . $unnamedGroupCounter++,
+ 'literal' => true,
+ 'required' => true,
+ 'positional' => true,
+ 'alternatives' => $options,
+ 'hasValue' => false,
+ );
+ }
+ /**
+ * Required long/short flag alternative
+ * ( --something | --somethingElse | --anotherOne | -s | -a )
+ * ( --something | --somethingElse | --anotherOne | -s | -a ):namedGroup
+ */
+ elseif (preg_match('/
+ \G
+ \(
+ (?P
+ (?:
+ \ *?
+ \-+(?P[a-zA-Z0-9][a-zA-Z0-9_\-]*?)
+ \ *?
+ (?:\||(?=\)))
+ \ *?
+ )+
+ )
+ \)
+ (?:\:(?P[a-zA-Z0-9]+))?
+ (?:\ +|$)
+ /sx', $def, $m, 0, $pos
+ )) {
+ // extract available options
+ $options = preg_split('/ *\| */', trim($m['options']), 0, PREG_SPLIT_NO_EMPTY);
+
+ // remove dupes
+ array_unique($options);
+
+ // remove prefix
+ array_walk($options, function (&$val, $key) {
+ $val = ltrim($val, '-');
+ });
+
+ // prepare item
+ $item = array(
+ 'name' => isset($m['groupName']) ? $m['groupName']:'unnamedGroupAt' . $unnamedGroupCounter++,
+ 'literal' => false,
+ 'required' => true,
+ 'positional' => false,
+ 'alternatives' => $options,
+ 'hasValue' => false,
+ );
+ }
+ /**
+ * Optional flag alternative
+ * [ --something | --somethingElse | --anotherOne | -s | -a ]
+ * [ --something | --somethingElse | --anotherOne | -s | -a ]:namedGroup
+ */
+ elseif (preg_match('/
+ \G
+ \[
+ (?P
+ (?:
+ \ *?
+ \-+(?P[a-zA-Z0-9][a-zA-Z0-9_\-]*?)
+ \ *?
+ (?:\||(?=\]))
+ \ *?
+ )+
+ )
+ \]
+ (?:\:(?P[a-zA-Z0-9]+))?
+ (?:\ +|$)
+ /sx', $def, $m, 0, $pos
+ )) {
+ // extract available options
+ $options = preg_split('/ *\| */', trim($m['options']), 0, PREG_SPLIT_NO_EMPTY);
+
+ // remove dupes
+ array_unique($options);
+
+ // remove prefix
+ array_walk($options, function (&$val, $key) {
+ $val = ltrim($val, '-');
+ });
+
+ // prepare item
+ $item = array(
+ 'name' => isset($m['groupName']) ? $m['groupName']:'unnamedGroupAt' . $unnamedGroupCounter++,
+ 'literal' => false,
+ 'required' => false,
+ 'positional' => false,
+ 'alternatives' => $options,
+ 'hasValue' => false,
+ );
+ } else {
+ throw new Exception\InvalidArgumentException(
+ 'Cannot understand Console route at "' . substr($def, $pos) . '"'
+ );
+ }
+
+ $pos += strlen($m[0]);
+ $parts[] = $item;
+ }
+
+ return $parts;
+ }
+
+ /**
+ * Returns list of names representing single parameter
+ *
+ * @param string $name
+ * @return string
+ */
+ private function getAliases($name)
+ {
+ $namesToMatch = array($name);
+ foreach ($this->aliases as $alias => $canonical) {
+ if ($name == $canonical) {
+ $namesToMatch[] = $alias;
+ }
+ }
+ return $namesToMatch;
+ }
+
+ /**
+ * Returns canonical name of a parameter
+ *
+ * @param string $name
+ * @return string
+ */
+ private function getCanonicalName($name)
+ {
+ if (isset($this->aliases[$name])) {
+ return $this->aliases[$name];
+ }
+ return $name;
+ }
+
+ /**
+ * Match parameters against route passed to constructor
+ *
+ * @param array $params
+ * @return array|null
+ */
+ public function match($params)
+ {
+ $matches = array();
+
+ /*
+ * Extract positional and named parts
+ */
+ $positional = $named = array();
+ foreach ($this->parts as &$part) {
+ if ($part['positional']) {
+ $positional[] = &$part;
+ } else {
+ $named[] = &$part;
+ }
+ }
+
+ /*
+ * Scan for named parts inside Console params
+ */
+ foreach ($named as &$part) {
+ /*
+ * Prepare match regex
+ */
+ if (isset($part['alternatives'])) {
+ // an alternative of flags
+ $regex = '/^\-+(?P';
+
+ $alternativeAliases = array();
+ foreach ($part['alternatives'] as $alternative) {
+ $alternativeAliases[] = '(?:' . implode('|', $this->getAliases($alternative)) . ')';
+ }
+
+ $regex .= join('|', $alternativeAliases);
+
+ if ($part['hasValue']) {
+ $regex .= ')(?:\=(?P.*?)$)?$/';
+ } else {
+ $regex .= ')$/i';
+ }
+ } else {
+ // a single named flag
+ $name = '(?:' . implode('|', $this->getAliases($part['name'])) . ')';
+
+ if ($part['short'] === true) {
+ // short variant
+ if ($part['hasValue']) {
+ $regex = '/^\-' . $name . '(?:\=(?P.*?)$)?$/i';
+ } else {
+ $regex = '/^\-' . $name . '$/i';
+ }
+ } elseif ($part['short'] === false) {
+ // long variant
+ if ($part['hasValue']) {
+ $regex = '/^\-{2,}' . $name . '(?:\=(?P.*?)$)?$/i';
+ } else {
+ $regex = '/^\-{2,}' . $name . '$/i';
+ }
+ }
+ }
+
+ /*
+ * Look for param
+ */
+ $value = $param = null;
+ for ($x = 0, $count = count($params); $x < $count; $x++) {
+ if (preg_match($regex, $params[$x], $m)) {
+ // found param
+ $param = $params[$x];
+
+ // prevent further scanning of this param
+ array_splice($params, $x, 1);
+
+ if (isset($m['value'])) {
+ $value = $m['value'];
+ }
+
+ if (isset($m['name'])) {
+ $matchedName = $this->getCanonicalName($m['name']);
+ }
+
+ break;
+ }
+ }
+
+
+ if (!$param) {
+ /*
+ * Drop out if that was a mandatory param
+ */
+ if ($part['required']) {
+ return null;
+ }
+
+ /*
+ * Continue to next positional param
+ */
+ else {
+ continue;
+ }
+ }
+
+
+ /*
+ * Value for flags is always boolean
+ */
+ if ($param && !$part['hasValue']) {
+ $value = true;
+ }
+
+ /*
+ * Try to retrieve value if it is expected
+ */
+ if ((null === $value || "" === $value) && $part['hasValue']) {
+ if ($x < count($params)+1 && isset($params[$x])) {
+ // retrieve value from adjacent param
+ $value = $params[$x];
+
+ // prevent further scanning of this param
+ array_splice($params, $x, 1);
+ } else {
+ // there are no more params available
+ return null;
+ }
+ }
+
+ /*
+ * Validate the value against constraints
+ */
+ if ($part['hasValue'] && isset($this->constraints[$part['name']])) {
+ if (
+ !preg_match($this->constraints[$part['name']], $value)
+ ) {
+ // constraint failed
+ return null;
+ }
+ }
+
+ /*
+ * Store the value
+ */
+ if ($part['hasValue']) {
+ $matches[$part['name']] = $value;
+ } else {
+ $matches[$part['name']] = true;
+ }
+
+ /*
+ * If there are alternatives, fill them
+ */
+ if (isset($part['alternatives'])) {
+ if ($part['hasValue']) {
+ foreach ($part['alternatives'] as $alt) {
+ if ($alt === $matchedName && !isset($matches[$alt])) {
+ $matches[$alt] = $value;
+ } elseif (!isset($matches[$alt])) {
+ $matches[$alt] = null;
+ }
+ }
+ } else {
+ foreach ($part['alternatives'] as $alt) {
+ if ($alt === $matchedName && !isset($matches[$alt])) {
+ $matches[$alt] = isset($this->defaults[$alt])? $this->defaults[$alt] : true;
+ } elseif (!isset($matches[$alt])) {
+ $matches[$alt] = false;
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * Scan for left-out flags that should result in a mismatch
+ */
+ foreach ($params as $param) {
+ if (preg_match('#^\-+#', $param)) {
+ return null; // there is an unrecognized flag
+ }
+ }
+
+ /*
+ * Go through all positional params
+ */
+ $argPos = 0;
+ foreach ($positional as &$part) {
+ /*
+ * Check if param exists
+ */
+ if (!isset($params[$argPos])) {
+ if ($part['required']) {
+ // cannot find required positional param
+ return null;
+ } else {
+ // stop matching
+ break;
+ }
+ }
+
+ $value = $params[$argPos];
+
+ /*
+ * Check if literal param matches
+ */
+ if ($part['literal']) {
+ if (
+ (isset($part['alternatives']) && !in_array($value, $part['alternatives'])) ||
+ (!isset($part['alternatives']) && $value != $part['name'])
+ ) {
+ return null;
+ }
+ }
+
+ /*
+ * Validate the value against constraints
+ */
+ if ($part['hasValue'] && isset($this->constraints[$part['name']])) {
+ if (
+ !preg_match($this->constraints[$part['name']], $value)
+ ) {
+ // constraint failed
+ return null;
+ }
+ }
+
+ /*
+ * Store the value
+ */
+ if ($part['hasValue']) {
+ $matches[$part['name']] = $value;
+ } elseif (isset($part['alternatives'])) {
+ // from all alternatives set matching parameter to TRUE and the rest to FALSE
+ foreach ($part['alternatives'] as $alt) {
+ if ($alt == $value) {
+ $matches[$alt] = isset($this->defaults[$alt])? $this->defaults[$alt] : true;
+ } else {
+ $matches[$alt] = false;
+ }
+ }
+
+ // set alternatives group value
+ $matches[$part['name']] = $value;
+ } elseif (!$part['required']) {
+ // set optional parameter flag
+ $name = $part['name'];
+ $matches[$name] = isset($this->defaults[$name])? $this->defaults[$name] : true;
+ }
+
+ /*
+ * Advance to next argument
+ */
+ $argPos++;
+ }
+
+ /*
+ * Check if we have consumed all positional parameters
+ */
+ if ($argPos < count($params)) {
+ return null; // there are extraneous params that were not consumed
+ }
+
+ /*
+ * Any optional flags that were not entered have value false
+ */
+ foreach ($this->parts as &$part) {
+ if (!$part['required'] && !$part['hasValue']) {
+ if (!isset($matches[$part['name']])) {
+ $matches[$part['name']] = false;
+ }
+ // unset alternatives also should be false
+ if (isset($part['alternatives'])) {
+ foreach ($part['alternatives'] as $alt) {
+ if (!isset($matches[$alt])) {
+ $matches[$alt] = false;
+ }
+ }
+ }
+ }
+ }
+
+ // run filters
+ foreach ($matches as $name => $value) {
+ if (isset($this->filters[$name])) {
+ $matches[$name] = $this->filters[$name]->filter($value);
+ }
+ }
+
+ // run validators
+ $valid = true;
+ foreach ($matches as $name => $value) {
+ if (isset($this->validators[$name])) {
+ $valid &= $this->validators[$name]->isValid($value);
+ }
+ }
+
+ if (!$valid) {
+ return null;
+ }
+
+ return array_replace($this->defaults, $matches);
+ }
+}
diff --git a/library/Zend/Console/RouteMatcher/RouteMatcherInterface.php b/library/Zend/Console/RouteMatcher/RouteMatcherInterface.php
new file mode 100755
index 0000000000..62f463c335
--- /dev/null
+++ b/library/Zend/Console/RouteMatcher/RouteMatcherInterface.php
@@ -0,0 +1,21 @@
+=5.3.23",
+ "zendframework/zend-stdlib": "self.version"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev",
+ "dev-develop": "2.4-dev"
+ }
+ }
+}
diff --git a/library/Zend/Crypt/BlockCipher.php b/library/Zend/Crypt/BlockCipher.php
new file mode 100755
index 0000000000..9b2a713ecd
--- /dev/null
+++ b/library/Zend/Crypt/BlockCipher.php
@@ -0,0 +1,489 @@
+cipher = $cipher;
+ }
+
+ /**
+ * Factory.
+ *
+ * @param string $adapter
+ * @param array $options
+ * @return BlockCipher
+ */
+ public static function factory($adapter, $options = array())
+ {
+ $plugins = static::getSymmetricPluginManager();
+ $adapter = $plugins->get($adapter, (array) $options);
+
+ return new static($adapter);
+ }
+
+ /**
+ * Returns the symmetric cipher plugin manager. If it doesn't exist it's created.
+ *
+ * @return SymmetricPluginManager
+ */
+ public static function getSymmetricPluginManager()
+ {
+ if (static::$symmetricPlugins === null) {
+ static::setSymmetricPluginManager(new SymmetricPluginManager());
+ }
+
+ return static::$symmetricPlugins;
+ }
+
+ /**
+ * Set the symmetric cipher plugin manager
+ *
+ * @param string|SymmetricPluginManager $plugins
+ * @throws Exception\InvalidArgumentException
+ */
+ public static function setSymmetricPluginManager($plugins)
+ {
+ if (is_string($plugins)) {
+ if (!class_exists($plugins)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Unable to locate symmetric cipher plugins using class "%s"; class does not exist',
+ $plugins
+ ));
+ }
+ $plugins = new $plugins();
+ }
+ if (!$plugins instanceof SymmetricPluginManager) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Expected an instance or extension of %s\SymmetricPluginManager; received "%s"',
+ __NAMESPACE__,
+ (is_object($plugins) ? get_class($plugins) : gettype($plugins))
+ ));
+ }
+ static::$symmetricPlugins = $plugins;
+ }
+
+ /**
+ * Set the symmetric cipher
+ *
+ * @param SymmetricInterface $cipher
+ * @return BlockCipher
+ */
+ public function setCipher(SymmetricInterface $cipher)
+ {
+ $this->cipher = $cipher;
+ return $this;
+ }
+
+ /**
+ * Get symmetric cipher
+ *
+ * @return SymmetricInterface
+ */
+ public function getCipher()
+ {
+ return $this->cipher;
+ }
+
+ /**
+ * Set the number of iterations for Pbkdf2
+ *
+ * @param int $num
+ * @return BlockCipher
+ */
+ public function setKeyIteration($num)
+ {
+ $this->keyIteration = (int) $num;
+
+ return $this;
+ }
+
+ /**
+ * Get the number of iterations for Pbkdf2
+ *
+ * @return int
+ */
+ public function getKeyIteration()
+ {
+ return $this->keyIteration;
+ }
+
+ /**
+ * Set the salt (IV)
+ *
+ * @param string $salt
+ * @return BlockCipher
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setSalt($salt)
+ {
+ try {
+ $this->cipher->setSalt($salt);
+ } catch (Symmetric\Exception\InvalidArgumentException $e) {
+ throw new Exception\InvalidArgumentException("The salt is not valid: " . $e->getMessage());
+ }
+ $this->saltSetted = true;
+
+ return $this;
+ }
+
+ /**
+ * Get the salt (IV) according to the size requested by the algorithm
+ *
+ * @return string
+ */
+ public function getSalt()
+ {
+ return $this->cipher->getSalt();
+ }
+
+ /**
+ * Get the original salt value
+ *
+ * @return string
+ */
+ public function getOriginalSalt()
+ {
+ return $this->cipher->getOriginalSalt();
+ }
+
+ /**
+ * Enable/disable the binary output
+ *
+ * @param bool $value
+ * @return BlockCipher
+ */
+ public function setBinaryOutput($value)
+ {
+ $this->binaryOutput = (bool) $value;
+
+ return $this;
+ }
+
+ /**
+ * Get the value of binary output
+ *
+ * @return bool
+ */
+ public function getBinaryOutput()
+ {
+ return $this->binaryOutput;
+ }
+
+ /**
+ * Set the encryption/decryption key
+ *
+ * @param string $key
+ * @return BlockCipher
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setKey($key)
+ {
+ if (empty($key)) {
+ throw new Exception\InvalidArgumentException('The key cannot be empty');
+ }
+ $this->key = $key;
+
+ return $this;
+ }
+
+ /**
+ * Get the key
+ *
+ * @return string
+ */
+ public function getKey()
+ {
+ return $this->key;
+ }
+
+ /**
+ * Set algorithm of the symmetric cipher
+ *
+ * @param string $algo
+ * @return BlockCipher
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setCipherAlgorithm($algo)
+ {
+ if (empty($this->cipher)) {
+ throw new Exception\InvalidArgumentException('No symmetric cipher specified');
+ }
+ try {
+ $this->cipher->setAlgorithm($algo);
+ } catch (Symmetric\Exception\InvalidArgumentException $e) {
+ throw new Exception\InvalidArgumentException($e->getMessage());
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the cipher algorithm
+ *
+ * @return string|bool
+ */
+ public function getCipherAlgorithm()
+ {
+ if (!empty($this->cipher)) {
+ return $this->cipher->getAlgorithm();
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the supported algorithms of the symmetric cipher
+ *
+ * @return array
+ */
+ public function getCipherSupportedAlgorithms()
+ {
+ if (!empty($this->cipher)) {
+ return $this->cipher->getSupportedAlgorithms();
+ }
+
+ return array();
+ }
+
+ /**
+ * Set the hash algorithm for HMAC authentication
+ *
+ * @param string $hash
+ * @return BlockCipher
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setHashAlgorithm($hash)
+ {
+ if (!Hash::isSupported($hash)) {
+ throw new Exception\InvalidArgumentException(
+ "The specified hash algorithm '{$hash}' is not supported by Zend\Crypt\Hash"
+ );
+ }
+ $this->hash = $hash;
+
+ return $this;
+ }
+
+ /**
+ * Get the hash algorithm for HMAC authentication
+ *
+ * @return string
+ */
+ public function getHashAlgorithm()
+ {
+ return $this->hash;
+ }
+
+ /**
+ * Set the hash algorithm for the Pbkdf2
+ *
+ * @param string $hash
+ * @return BlockCipher
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setPbkdf2HashAlgorithm($hash)
+ {
+ if (!Hash::isSupported($hash)) {
+ throw new Exception\InvalidArgumentException(
+ "The specified hash algorithm '{$hash}' is not supported by Zend\Crypt\Hash"
+ );
+ }
+ $this->pbkdf2Hash = $hash;
+
+ return $this;
+ }
+
+ /**
+ * Get the Pbkdf2 hash algorithm
+ *
+ * @return string
+ */
+ public function getPbkdf2HashAlgorithm()
+ {
+ return $this->pbkdf2Hash;
+ }
+
+ /**
+ * Encrypt then authenticate using HMAC
+ *
+ * @param string $data
+ * @return string
+ * @throws Exception\InvalidArgumentException
+ */
+ public function encrypt($data)
+ {
+ // 0 (as integer), 0.0 (as float) & '0' (as string) will return false, though these should be allowed
+ // Must be a string, integer, or float in order to encrypt
+ if ((is_string($data) && $data === '')
+ || is_array($data)
+ || is_object($data)
+ ) {
+ throw new Exception\InvalidArgumentException('The data to encrypt cannot be empty');
+ }
+
+ // Cast to string prior to encrypting
+ if (!is_string($data)) {
+ $data = (string) $data;
+ }
+
+ if (empty($this->cipher)) {
+ throw new Exception\InvalidArgumentException('No symmetric cipher specified');
+ }
+ if (empty($this->key)) {
+ throw new Exception\InvalidArgumentException('No key specified for the encryption');
+ }
+ $keySize = $this->cipher->getKeySize();
+ // generate a random salt (IV) if the salt has not been set
+ if (!$this->saltSetted) {
+ $this->cipher->setSalt(Rand::getBytes($this->cipher->getSaltSize(), true));
+ }
+ // generate the encryption key and the HMAC key for the authentication
+ $hash = Pbkdf2::calc(
+ $this->getPbkdf2HashAlgorithm(),
+ $this->getKey(),
+ $this->getSalt(),
+ $this->keyIteration,
+ $keySize * 2
+ );
+ // set the encryption key
+ $this->cipher->setKey(substr($hash, 0, $keySize));
+ // set the key for HMAC
+ $keyHmac = substr($hash, $keySize);
+ // encryption
+ $ciphertext = $this->cipher->encrypt($data);
+ // HMAC
+ $hmac = Hmac::compute($keyHmac, $this->hash, $this->cipher->getAlgorithm() . $ciphertext);
+ if (!$this->binaryOutput) {
+ $ciphertext = base64_encode($ciphertext);
+ }
+
+ return $hmac . $ciphertext;
+ }
+
+ /**
+ * Decrypt
+ *
+ * @param string $data
+ * @return string|bool
+ * @throws Exception\InvalidArgumentException
+ */
+ public function decrypt($data)
+ {
+ if (!is_string($data)) {
+ throw new Exception\InvalidArgumentException('The data to decrypt must be a string');
+ }
+ if ('' === $data) {
+ throw new Exception\InvalidArgumentException('The data to decrypt cannot be empty');
+ }
+ if (empty($this->key)) {
+ throw new Exception\InvalidArgumentException('No key specified for the decryption');
+ }
+ if (empty($this->cipher)) {
+ throw new Exception\InvalidArgumentException('No symmetric cipher specified');
+ }
+ $hmacSize = Hmac::getOutputSize($this->hash);
+ $hmac = substr($data, 0, $hmacSize);
+ $ciphertext = substr($data, $hmacSize);
+ if (!$this->binaryOutput) {
+ $ciphertext = base64_decode($ciphertext);
+ }
+ $iv = substr($ciphertext, 0, $this->cipher->getSaltSize());
+ $keySize = $this->cipher->getKeySize();
+ // generate the encryption key and the HMAC key for the authentication
+ $hash = Pbkdf2::calc(
+ $this->getPbkdf2HashAlgorithm(),
+ $this->getKey(),
+ $iv,
+ $this->keyIteration,
+ $keySize * 2
+ );
+ // set the decryption key
+ $this->cipher->setKey(substr($hash, 0, $keySize));
+ // set the key for HMAC
+ $keyHmac = substr($hash, $keySize);
+ $hmacNew = Hmac::compute($keyHmac, $this->hash, $this->cipher->getAlgorithm() . $ciphertext);
+ if (!Utils::compareStrings($hmacNew, $hmac)) {
+ return false;
+ }
+
+ return $this->cipher->decrypt($ciphertext);
+ }
+}
diff --git a/library/Zend/Crypt/CONTRIBUTING.md b/library/Zend/Crypt/CONTRIBUTING.md
new file mode 100755
index 0000000000..e77f5d2d5b
--- /dev/null
+++ b/library/Zend/Crypt/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# CONTRIBUTING
+
+Please don't open pull requests against this repository, please use https://github.com/zendframework/zf2.
\ No newline at end of file
diff --git a/library/Zend/Crypt/Exception/ExceptionInterface.php b/library/Zend/Crypt/Exception/ExceptionInterface.php
new file mode 100755
index 0000000000..ef93743614
--- /dev/null
+++ b/library/Zend/Crypt/Exception/ExceptionInterface.php
@@ -0,0 +1,14 @@
+ MHASH_MD2,
+ 'md4' => MHASH_MD4,
+ 'md5' => MHASH_MD5,
+ 'sha1' => MHASH_SHA1,
+ 'sha224' => MHASH_SHA224,
+ 'sha256' => MHASH_SHA256,
+ 'sha384' => MHASH_SHA384,
+ 'sha512' => MHASH_SHA512,
+ 'ripemd128' => MHASH_RIPEMD128,
+ 'ripemd256' => MHASH_RIPEMD256,
+ 'ripemd320' => MHASH_RIPEMD320,
+ 'haval128,3' => MHASH_HAVAL128,
+ 'haval160,3' => MHASH_HAVAL160,
+ 'haval192,3' => MHASH_HAVAL192,
+ 'haval224,3' => MHASH_HAVAL224,
+ 'haval256,3' => MHASH_HAVAL256,
+ 'tiger128,3' => MHASH_TIGER128,
+ 'riger160,3' => MHASH_TIGER160,
+ 'whirpool' => MHASH_WHIRLPOOL,
+ 'snefru256' => MHASH_SNEFRU256,
+ 'gost' => MHASH_GOST,
+ 'crc32' => MHASH_CRC32,
+ 'crc32b' => MHASH_CRC32B
+ );
+
+ /**
+ * Generate the new key
+ *
+ * @param string $hash The hash algorithm to be used by HMAC
+ * @param string $password The source password/key
+ * @param int $bytes The output size in bytes
+ * @param string $salt The salt of the algorithm
+ * @throws Exception\InvalidArgumentException
+ * @return string
+ */
+ public static function calc($hash, $password, $salt, $bytes)
+ {
+ if (!in_array($hash, array_keys(static::$supportedMhashAlgos))) {
+ throw new Exception\InvalidArgumentException("The hash algorithm $hash is not supported by " . __CLASS__);
+ }
+ if (strlen($salt)<8) {
+ throw new Exception\InvalidArgumentException('The salt size must be at least of 8 bytes');
+ }
+ return mhash_keygen_s2k(static::$supportedMhashAlgos[$hash], $password, $salt, $bytes);
+ }
+}
diff --git a/library/Zend/Crypt/Key/Derivation/Scrypt.php b/library/Zend/Crypt/Key/Derivation/Scrypt.php
new file mode 100755
index 0000000000..da2a783677
--- /dev/null
+++ b/library/Zend/Crypt/Key/Derivation/Scrypt.php
@@ -0,0 +1,340 @@
+ 0 and a power of 2");
+ }
+ if ($n > PHP_INT_MAX / 128 / $r) {
+ throw new Exception\InvalidArgumentException("Parameter n is too large");
+ }
+ if ($r > PHP_INT_MAX / 128 / $p) {
+ throw new Exception\InvalidArgumentException("Parameter r is too large");
+ }
+
+ if (extension_loaded('Scrypt')) {
+ if ($length < 16) {
+ throw new Exception\InvalidArgumentException("Key length is too low, must be greater or equal to 16");
+ }
+ return self::hex2bin(scrypt($password, $salt, $n, $r, $p, $length));
+ }
+
+ $b = Pbkdf2::calc('sha256', $password, $salt, 1, $p * 128 * $r);
+
+ $s = '';
+ for ($i = 0; $i < $p; $i++) {
+ $s .= self::scryptROMix(substr($b, $i * 128 * $r, 128 * $r), $n, $r);
+ }
+
+ return Pbkdf2::calc('sha256', $password, $s, 1, $length);
+ }
+
+ /**
+ * scryptROMix
+ *
+ * @param string $b
+ * @param int $n
+ * @param int $r
+ * @return string
+ * @see https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01#section-4
+ */
+ protected static function scryptROMix($b, $n, $r)
+ {
+ $x = $b;
+ $v = array();
+ for ($i = 0; $i < $n; $i++) {
+ $v[$i] = $x;
+ $x = self::scryptBlockMix($x, $r);
+ }
+ for ($i = 0; $i < $n; $i++) {
+ $j = self::integerify($x) % $n;
+ $t = $x ^ $v[$j];
+ $x = self::scryptBlockMix($t, $r);
+ }
+ return $x;
+ }
+
+ /**
+ * scryptBlockMix
+ *
+ * @param string $b
+ * @param int $r
+ * @return string
+ * @see https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01#section-3
+ */
+ protected static function scryptBlockMix($b, $r)
+ {
+ $x = substr($b, -64);
+ $even = '';
+ $odd = '';
+ $len = 2 * $r;
+
+ for ($i = 0; $i < $len; $i++) {
+ if (PHP_INT_SIZE === 4) {
+ $x = self::salsa208Core32($x ^ substr($b, 64 * $i, 64));
+ } else {
+ $x = self::salsa208Core64($x ^ substr($b, 64 * $i, 64));
+ }
+ if ($i % 2 == 0) {
+ $even .= $x;
+ } else {
+ $odd .= $x;
+ }
+ }
+ return $even . $odd;
+ }
+
+ /**
+ * Salsa 20/8 core (32 bit version)
+ *
+ * @param string $b
+ * @return string
+ * @see https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01#section-2
+ * @see http://cr.yp.to/salsa20.html
+ */
+ protected static function salsa208Core32($b)
+ {
+ $b32 = array();
+ for ($i = 0; $i < 16; $i++) {
+ list(, $b32[$i]) = unpack("V", substr($b, $i * 4, 4));
+ }
+
+ $x = $b32;
+ for ($i = 0; $i < 8; $i += 2) {
+ $a = ($x[ 0] + $x[12]);
+ $x[ 4] ^= ($a << 7) | ($a >> 25) & 0x7f;
+ $a = ($x[ 4] + $x[ 0]);
+ $x[ 8] ^= ($a << 9) | ($a >> 23) & 0x1ff;
+ $a = ($x[ 8] + $x[ 4]);
+ $x[12] ^= ($a << 13) | ($a >> 19) & 0x1fff;
+ $a = ($x[12] + $x[ 8]);
+ $x[ 0] ^= ($a << 18) | ($a >> 14) & 0x3ffff;
+ $a = ($x[ 5] + $x[ 1]);
+ $x[ 9] ^= ($a << 7) | ($a >> 25) & 0x7f;
+ $a = ($x[ 9] + $x[ 5]);
+ $x[13] ^= ($a << 9) | ($a >> 23) & 0x1ff;
+ $a = ($x[13] + $x[ 9]);
+ $x[ 1] ^= ($a << 13) | ($a >> 19) & 0x1fff;
+ $a = ($x[ 1] + $x[13]);
+ $x[ 5] ^= ($a << 18) | ($a >> 14) & 0x3ffff;
+ $a = ($x[10] + $x[ 6]);
+ $x[14] ^= ($a << 7) | ($a >> 25) & 0x7f;
+ $a = ($x[14] + $x[10]);
+ $x[ 2] ^= ($a << 9) | ($a >> 23) & 0x1ff;
+ $a = ($x[ 2] + $x[14]);
+ $x[ 6] ^= ($a << 13) | ($a >> 19) & 0x1fff;
+ $a = ($x[ 6] + $x[ 2]);
+ $x[10] ^= ($a << 18) | ($a >> 14) & 0x3ffff;
+ $a = ($x[15] + $x[11]);
+ $x[ 3] ^= ($a << 7) | ($a >> 25) & 0x7f;
+ $a = ($x[ 3] + $x[15]);
+ $x[ 7] ^= ($a << 9) | ($a >> 23) & 0x1ff;
+ $a = ($x[ 7] + $x[ 3]);
+ $x[11] ^= ($a << 13) | ($a >> 19) & 0x1fff;
+ $a = ($x[11] + $x[ 7]);
+ $x[15] ^= ($a << 18) | ($a >> 14) & 0x3ffff;
+ $a = ($x[ 0] + $x[ 3]);
+ $x[ 1] ^= ($a << 7) | ($a >> 25) & 0x7f;
+ $a = ($x[ 1] + $x[ 0]);
+ $x[ 2] ^= ($a << 9) | ($a >> 23) & 0x1ff;
+ $a = ($x[ 2] + $x[ 1]);
+ $x[ 3] ^= ($a << 13) | ($a >> 19) & 0x1fff;
+ $a = ($x[ 3] + $x[ 2]);
+ $x[ 0] ^= ($a << 18) | ($a >> 14) & 0x3ffff;
+ $a = ($x[ 5] + $x[ 4]);
+ $x[ 6] ^= ($a << 7) | ($a >> 25) & 0x7f;
+ $a = ($x[ 6] + $x[ 5]);
+ $x[ 7] ^= ($a << 9) | ($a >> 23) & 0x1ff;
+ $a = ($x[ 7] + $x[ 6]);
+ $x[ 4] ^= ($a << 13) | ($a >> 19) & 0x1fff;
+ $a = ($x[ 4] + $x[ 7]);
+ $x[ 5] ^= ($a << 18) | ($a >> 14) & 0x3ffff;
+ $a = ($x[10] + $x[ 9]);
+ $x[11] ^= ($a << 7) | ($a >> 25) & 0x7f;
+ $a = ($x[11] + $x[10]);
+ $x[ 8] ^= ($a << 9) | ($a >> 23) & 0x1ff;
+ $a = ($x[ 8] + $x[11]);
+ $x[ 9] ^= ($a << 13) | ($a >> 19) & 0x1fff;
+ $a = ($x[ 9] + $x[ 8]);
+ $x[10] ^= ($a << 18) | ($a >> 14) & 0x3ffff;
+ $a = ($x[15] + $x[14]);
+ $x[12] ^= ($a << 7) | ($a >> 25) & 0x7f;
+ $a = ($x[12] + $x[15]);
+ $x[13] ^= ($a << 9) | ($a >> 23) & 0x1ff;
+ $a = ($x[13] + $x[12]);
+ $x[14] ^= ($a << 13) | ($a >> 19) & 0x1fff;
+ $a = ($x[14] + $x[13]);
+ $x[15] ^= ($a << 18) | ($a >> 14) & 0x3ffff;
+ }
+ for ($i = 0; $i < 16; $i++) {
+ $b32[$i] = $b32[$i] + $x[$i];
+ }
+ $result = '';
+ for ($i = 0; $i < 16; $i++) {
+ $result .= pack("V", $b32[$i]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Salsa 20/8 core (64 bit version)
+ *
+ * @param string $b
+ * @return string
+ * @see https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01#section-2
+ * @see http://cr.yp.to/salsa20.html
+ */
+ protected static function salsa208Core64($b)
+ {
+ $b32 = array();
+ for ($i = 0; $i < 16; $i++) {
+ list(, $b32[$i]) = unpack("V", substr($b, $i * 4, 4));
+ }
+
+ $x = $b32;
+ for ($i = 0; $i < 8; $i += 2) {
+ $a = ($x[ 0] + $x[12]) & 0xffffffff;
+ $x[ 4] ^= ($a << 7) | ($a >> 25);
+ $a = ($x[ 4] + $x[ 0]) & 0xffffffff;
+ $x[ 8] ^= ($a << 9) | ($a >> 23);
+ $a = ($x[ 8] + $x[ 4]) & 0xffffffff;
+ $x[12] ^= ($a << 13) | ($a >> 19);
+ $a = ($x[12] + $x[ 8]) & 0xffffffff;
+ $x[ 0] ^= ($a << 18) | ($a >> 14);
+ $a = ($x[ 5] + $x[ 1]) & 0xffffffff;
+ $x[ 9] ^= ($a << 7) | ($a >> 25);
+ $a = ($x[ 9] + $x[ 5]) & 0xffffffff;
+ $x[13] ^= ($a << 9) | ($a >> 23);
+ $a = ($x[13] + $x[ 9]) & 0xffffffff;
+ $x[ 1] ^= ($a << 13) | ($a >> 19);
+ $a = ($x[ 1] + $x[13]) & 0xffffffff;
+ $x[ 5] ^= ($a << 18) | ($a >> 14);
+ $a = ($x[10] + $x[ 6]) & 0xffffffff;
+ $x[14] ^= ($a << 7) | ($a >> 25);
+ $a = ($x[14] + $x[10]) & 0xffffffff;
+ $x[ 2] ^= ($a << 9) | ($a >> 23);
+ $a = ($x[ 2] + $x[14]) & 0xffffffff;
+ $x[ 6] ^= ($a << 13) | ($a >> 19);
+ $a = ($x[ 6] + $x[ 2]) & 0xffffffff;
+ $x[10] ^= ($a << 18) | ($a >> 14);
+ $a = ($x[15] + $x[11]) & 0xffffffff;
+ $x[ 3] ^= ($a << 7) | ($a >> 25);
+ $a = ($x[ 3] + $x[15]) & 0xffffffff;
+ $x[ 7] ^= ($a << 9) | ($a >> 23);
+ $a = ($x[ 7] + $x[ 3]) & 0xffffffff;
+ $x[11] ^= ($a << 13) | ($a >> 19);
+ $a = ($x[11] + $x[ 7]) & 0xffffffff;
+ $x[15] ^= ($a << 18) | ($a >> 14);
+ $a = ($x[ 0] + $x[ 3]) & 0xffffffff;
+ $x[ 1] ^= ($a << 7) | ($a >> 25);
+ $a = ($x[ 1] + $x[ 0]) & 0xffffffff;
+ $x[ 2] ^= ($a << 9) | ($a >> 23);
+ $a = ($x[ 2] + $x[ 1]) & 0xffffffff;
+ $x[ 3] ^= ($a << 13) | ($a >> 19);
+ $a = ($x[ 3] + $x[ 2]) & 0xffffffff;
+ $x[ 0] ^= ($a << 18) | ($a >> 14);
+ $a = ($x[ 5] + $x[ 4]) & 0xffffffff;
+ $x[ 6] ^= ($a << 7) | ($a >> 25);
+ $a = ($x[ 6] + $x[ 5]) & 0xffffffff;
+ $x[ 7] ^= ($a << 9) | ($a >> 23);
+ $a = ($x[ 7] + $x[ 6]) & 0xffffffff;
+ $x[ 4] ^= ($a << 13) | ($a >> 19);
+ $a = ($x[ 4] + $x[ 7]) & 0xffffffff;
+ $x[ 5] ^= ($a << 18) | ($a >> 14);
+ $a = ($x[10] + $x[ 9]) & 0xffffffff;
+ $x[11] ^= ($a << 7) | ($a >> 25);
+ $a = ($x[11] + $x[10]) & 0xffffffff;
+ $x[ 8] ^= ($a << 9) | ($a >> 23);
+ $a = ($x[ 8] + $x[11]) & 0xffffffff;
+ $x[ 9] ^= ($a << 13) | ($a >> 19);
+ $a = ($x[ 9] + $x[ 8]) & 0xffffffff;
+ $x[10] ^= ($a << 18) | ($a >> 14);
+ $a = ($x[15] + $x[14]) & 0xffffffff;
+ $x[12] ^= ($a << 7) | ($a >> 25);
+ $a = ($x[12] + $x[15]) & 0xffffffff;
+ $x[13] ^= ($a << 9) | ($a >> 23);
+ $a = ($x[13] + $x[12]) & 0xffffffff;
+ $x[14] ^= ($a << 13) | ($a >> 19);
+ $a = ($x[14] + $x[13]) & 0xffffffff;
+ $x[15] ^= ($a << 18) | ($a >> 14);
+ }
+ for ($i = 0; $i < 16; $i++) {
+ $b32[$i] = ($b32[$i] + $x[$i]) & 0xffffffff;
+ }
+ $result = '';
+ for ($i = 0; $i < 16; $i++) {
+ $result .= pack("V", $b32[$i]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Integerify
+ *
+ * Integerify (B[0] ... B[2 * r - 1]) is defined as the result
+ * of interpreting B[2 * r - 1] as a little-endian integer.
+ * Each block B is a string of 64 bytes.
+ *
+ * @param string $b
+ * @return int
+ * @see https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01#section-4
+ */
+ protected static function integerify($b)
+ {
+ $v = 'v';
+ if (PHP_INT_SIZE === 8) {
+ $v = 'V';
+ }
+ list(,$n) = unpack($v, substr($b, -64));
+ return $n;
+ }
+
+ /**
+ * Convert hex string in a binary string
+ *
+ * @param string $hex
+ * @return string
+ */
+ protected static function hex2bin($hex)
+ {
+ if (PHP_VERSION_ID >= 50400) {
+ return hex2bin($hex);
+ }
+ $len = strlen($hex);
+ $result = '';
+ for ($i = 0; $i < $len; $i+=2) {
+ $result .= chr(hexdec($hex[$i] . $hex[$i+1]));
+ }
+ return $result;
+ }
+}
diff --git a/library/Zend/Crypt/Password/Apache.php b/library/Zend/Crypt/Password/Apache.php
new file mode 100755
index 0000000000..d9359827a1
--- /dev/null
+++ b/library/Zend/Crypt/Password/Apache.php
@@ -0,0 +1,301 @@
+ $value) {
+ switch (strtolower($key)) {
+ case 'format':
+ $this->setFormat($value);
+ break;
+ case 'authname':
+ $this->setAuthName($value);
+ break;
+ case 'username':
+ $this->setUserName($value);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Generate the hash of a password
+ *
+ * @param string $password
+ * @throws Exception\RuntimeException
+ * @return string
+ */
+ public function create($password)
+ {
+ if (empty($this->format)) {
+ throw new Exception\RuntimeException(
+ 'You must specify a password format'
+ );
+ }
+ switch ($this->format) {
+ case 'crypt':
+ $hash = crypt($password, Rand::getString(2, self::ALPHA64));
+ break;
+ case 'sha1':
+ $hash = '{SHA}' . base64_encode(sha1($password, true));
+ break;
+ case 'md5':
+ $hash = $this->apr1Md5($password);
+ break;
+ case 'digest':
+ if (empty($this->userName) || empty($this->authName)) {
+ throw new Exception\RuntimeException(
+ 'You must specify UserName and AuthName (realm) to generate the digest'
+ );
+ }
+ $hash = md5($this->userName . ':' . $this->authName . ':' .$password);
+ break;
+ }
+
+ return $hash;
+ }
+
+ /**
+ * Verify if a password is correct against a hash value
+ *
+ * @param string $password
+ * @param string $hash
+ * @return bool
+ */
+ public function verify($password, $hash)
+ {
+ if (substr($hash, 0, 5) === '{SHA}') {
+ $hash2 = '{SHA}' . base64_encode(sha1($password, true));
+ return ($hash === $hash2);
+ }
+ if (substr($hash, 0, 6) === '$apr1$') {
+ $token = explode('$', $hash);
+ if (empty($token[2])) {
+ throw new Exception\InvalidArgumentException(
+ 'The APR1 password format is not valid'
+ );
+ }
+ $hash2 = $this->apr1Md5($password, $token[2]);
+ return ($hash === $hash2);
+ }
+ if (strlen($hash) > 13) { // digest
+ if (empty($this->userName) || empty($this->authName)) {
+ throw new Exception\RuntimeException(
+ 'You must specify UserName and AuthName (realm) to verify the digest'
+ );
+ }
+ $hash2 = md5($this->userName . ':' . $this->authName . ':' .$password);
+ return ($hash === $hash2);
+ }
+ return (crypt($password, $hash) === $hash);
+ }
+
+ /**
+ * Set the format of the password
+ *
+ * @param string $format
+ * @throws Exception\InvalidArgumentException
+ * @return Apache
+ */
+ public function setFormat($format)
+ {
+ $format = strtolower($format);
+ if (!in_array($format, $this->supportedFormat)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'The format %s specified is not valid. The supported formats are: %s',
+ $format,
+ implode(',', $this->supportedFormat)
+ ));
+ }
+ $this->format = $format;
+
+ return $this;
+ }
+
+ /**
+ * Get the format of the password
+ *
+ * @return string
+ */
+ public function getFormat()
+ {
+ return $this->format;
+ }
+
+ /**
+ * Set the AuthName (for digest authentication)
+ *
+ * @param string $name
+ * @return Apache
+ */
+ public function setAuthName($name)
+ {
+ $this->authName = $name;
+
+ return $this;
+ }
+
+ /**
+ * Get the AuthName (for digest authentication)
+ *
+ * @return string
+ */
+ public function getAuthName()
+ {
+ return $this->authName;
+ }
+
+ /**
+ * Set the username
+ *
+ * @param string $name
+ * @return Apache
+ */
+ public function setUserName($name)
+ {
+ $this->userName = $name;
+
+ return $this;
+ }
+
+ /**
+ * Get the username
+ *
+ * @return string
+ */
+ public function getUserName()
+ {
+ return $this->userName;
+ }
+
+ /**
+ * Convert a binary string using the alphabet "./0-9A-Za-z"
+ *
+ * @param string $value
+ * @return string
+ */
+ protected function toAlphabet64($value)
+ {
+ return strtr(strrev(substr(base64_encode($value), 2)), self::BASE64, self::ALPHA64);
+ }
+
+ /**
+ * APR1 MD5 algorithm
+ *
+ * @param string $password
+ * @param null|string $salt
+ * @return string
+ */
+ protected function apr1Md5($password, $salt = null)
+ {
+ if (null === $salt) {
+ $salt = Rand::getString(8, self::ALPHA64);
+ } else {
+ if (strlen($salt) !== 8) {
+ throw new Exception\InvalidArgumentException(
+ 'The salt value for APR1 algorithm must be 8 characters long'
+ );
+ }
+ for ($i = 0; $i < 8; $i++) {
+ if (strpos(self::ALPHA64, $salt[$i]) === false) {
+ throw new Exception\InvalidArgumentException(
+ 'The salt value must be a string in the alphabet "./0-9A-Za-z"'
+ );
+ }
+ }
+ }
+ $len = strlen($password);
+ $text = $password . '$apr1$' . $salt;
+ $bin = pack("H32", md5($password . $salt . $password));
+ for ($i = $len; $i > 0; $i -= 16) {
+ $text .= substr($bin, 0, min(16, $i));
+ }
+ for ($i = $len; $i > 0; $i >>= 1) {
+ $text .= ($i & 1) ? chr(0) : $password[0];
+ }
+ $bin = pack("H32", md5($text));
+ for ($i = 0; $i < 1000; $i++) {
+ $new = ($i & 1) ? $password : $bin;
+ if ($i % 3) {
+ $new .= $salt;
+ }
+ if ($i % 7) {
+ $new .= $password;
+ }
+ $new .= ($i & 1) ? $bin : $password;
+ $bin = pack("H32", md5($new));
+ }
+ $tmp = '';
+ for ($i = 0; $i < 5; $i++) {
+ $k = $i + 6;
+ $j = $i + 12;
+ if ($j == 16) {
+ $j = 5;
+ }
+ $tmp = $bin[$i] . $bin[$k] . $bin[$j] . $tmp;
+ }
+ $tmp = chr(0) . chr(0) . $bin[11] . $tmp;
+
+ return '$apr1$' . $salt . '$' . $this->toAlphabet64($tmp);
+ }
+}
diff --git a/library/Zend/Crypt/Password/Bcrypt.php b/library/Zend/Crypt/Password/Bcrypt.php
new file mode 100755
index 0000000000..b489ddd48f
--- /dev/null
+++ b/library/Zend/Crypt/Password/Bcrypt.php
@@ -0,0 +1,207 @@
+ $value) {
+ switch (strtolower($key)) {
+ case 'salt':
+ $this->setSalt($value);
+ break;
+ case 'cost':
+ $this->setCost($value);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Bcrypt
+ *
+ * @param string $password
+ * @throws Exception\RuntimeException
+ * @return string
+ */
+ public function create($password)
+ {
+ if (empty($this->salt)) {
+ $salt = Rand::getBytes(self::MIN_SALT_SIZE);
+ } else {
+ $salt = $this->salt;
+ }
+ $salt64 = substr(str_replace('+', '.', base64_encode($salt)), 0, 22);
+ /**
+ * Check for security flaw in the bcrypt implementation used by crypt()
+ * @see http://php.net/security/crypt_blowfish.php
+ */
+ if ((PHP_VERSION_ID >= 50307) && !$this->backwardCompatibility) {
+ $prefix = '$2y$';
+ } else {
+ $prefix = '$2a$';
+ // check if the password contains 8-bit character
+ if (preg_match('/[\x80-\xFF]/', $password)) {
+ throw new Exception\RuntimeException(
+ 'The bcrypt implementation used by PHP can contain a security flaw ' .
+ 'using password with 8-bit character. ' .
+ 'We suggest to upgrade to PHP 5.3.7+ or use passwords with only 7-bit characters'
+ );
+ }
+ }
+ $hash = crypt($password, $prefix . $this->cost . '$' . $salt64);
+ if (strlen($hash) < 13) {
+ throw new Exception\RuntimeException('Error during the bcrypt generation');
+ }
+ return $hash;
+ }
+
+ /**
+ * Verify if a password is correct against a hash value
+ *
+ * @param string $password
+ * @param string $hash
+ * @throws Exception\RuntimeException when the hash is unable to be processed
+ * @return bool
+ */
+ public function verify($password, $hash)
+ {
+ $result = crypt($password, $hash);
+ if ($result === $hash) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Set the cost parameter
+ *
+ * @param int|string $cost
+ * @throws Exception\InvalidArgumentException
+ * @return Bcrypt
+ */
+ public function setCost($cost)
+ {
+ if (!empty($cost)) {
+ $cost = (int) $cost;
+ if ($cost < 4 || $cost > 31) {
+ throw new Exception\InvalidArgumentException(
+ 'The cost parameter of bcrypt must be in range 04-31'
+ );
+ }
+ $this->cost = sprintf('%1$02d', $cost);
+ }
+ return $this;
+ }
+
+ /**
+ * Get the cost parameter
+ *
+ * @return string
+ */
+ public function getCost()
+ {
+ return $this->cost;
+ }
+
+ /**
+ * Set the salt value
+ *
+ * @param string $salt
+ * @throws Exception\InvalidArgumentException
+ * @return Bcrypt
+ */
+ public function setSalt($salt)
+ {
+ if (strlen($salt) < self::MIN_SALT_SIZE) {
+ throw new Exception\InvalidArgumentException(
+ 'The length of the salt must be at least ' . self::MIN_SALT_SIZE . ' bytes'
+ );
+ }
+ $this->salt = $salt;
+ return $this;
+ }
+
+ /**
+ * Get the salt value
+ *
+ * @return string
+ */
+ public function getSalt()
+ {
+ return $this->salt;
+ }
+
+ /**
+ * Set the backward compatibility $2a$ instead of $2y$ for PHP 5.3.7+
+ *
+ * @param bool $value
+ * @return Bcrypt
+ */
+ public function setBackwardCompatibility($value)
+ {
+ $this->backwardCompatibility = (bool) $value;
+ return $this;
+ }
+
+ /**
+ * Get the backward compatibility
+ *
+ * @return bool
+ */
+ public function getBackwardCompatibility()
+ {
+ return $this->backwardCompatibility;
+ }
+}
diff --git a/library/Zend/Crypt/Password/Exception/ExceptionInterface.php b/library/Zend/Crypt/Password/Exception/ExceptionInterface.php
new file mode 100755
index 0000000000..5c65fb7811
--- /dev/null
+++ b/library/Zend/Crypt/Password/Exception/ExceptionInterface.php
@@ -0,0 +1,16 @@
+setPrime($prime);
+ $this->setGenerator($generator);
+ if ($privateKey !== null) {
+ $this->setPrivateKey($privateKey, $privateKeyFormat);
+ }
+
+ // set up BigInteger adapter
+ $this->math = Math\BigInteger\BigInteger::factory();
+ }
+
+ /**
+ * Set whether to use openssl extension
+ *
+ * @static
+ * @param bool $flag
+ */
+ public static function useOpensslExtension($flag = true)
+ {
+ static::$useOpenssl = (bool) $flag;
+ }
+
+ /**
+ * Generate own public key. If a private number has not already been set,
+ * one will be generated at this stage.
+ *
+ * @return DiffieHellman
+ * @throws \Zend\Crypt\Exception\RuntimeException
+ */
+ public function generateKeys()
+ {
+ if (function_exists('openssl_dh_compute_key') && static::$useOpenssl !== false) {
+ $details = array(
+ 'p' => $this->convert($this->getPrime(), self::FORMAT_NUMBER, self::FORMAT_BINARY),
+ 'g' => $this->convert($this->getGenerator(), self::FORMAT_NUMBER, self::FORMAT_BINARY)
+ );
+ if ($this->hasPrivateKey()) {
+ $details['priv_key'] = $this->convert(
+ $this->privateKey,
+ self::FORMAT_NUMBER,
+ self::FORMAT_BINARY
+ );
+ $opensslKeyResource = openssl_pkey_new(array('dh' => $details));
+ } else {
+ $opensslKeyResource = openssl_pkey_new(array(
+ 'dh' => $details,
+ 'private_key_bits' => self::DEFAULT_KEY_SIZE,
+ 'private_key_type' => OPENSSL_KEYTYPE_DH
+ ));
+ }
+
+ if (false === $opensslKeyResource) {
+ throw new Exception\RuntimeException(
+ 'Can not generate new key; openssl ' . openssl_error_string()
+ );
+ }
+
+ $data = openssl_pkey_get_details($opensslKeyResource);
+
+ $this->setPrivateKey($data['dh']['priv_key'], self::FORMAT_BINARY);
+ $this->setPublicKey($data['dh']['pub_key'], self::FORMAT_BINARY);
+
+ $this->opensslKeyResource = $opensslKeyResource;
+ } else {
+ // Private key is lazy generated in the absence of ext/openssl
+ $publicKey = $this->math->powmod($this->getGenerator(), $this->getPrivateKey(), $this->getPrime());
+ $this->setPublicKey($publicKey);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Setter for the value of the public number
+ *
+ * @param string $number
+ * @param string $format
+ * @return DiffieHellman
+ * @throws \Zend\Crypt\Exception\InvalidArgumentException
+ */
+ public function setPublicKey($number, $format = self::FORMAT_NUMBER)
+ {
+ $number = $this->convert($number, $format, self::FORMAT_NUMBER);
+ if (!preg_match('/^\d+$/', $number)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter; not a positive natural number');
+ }
+ $this->publicKey = (string) $number;
+
+ return $this;
+ }
+
+ /**
+ * Returns own public key for communication to the second party to this transaction
+ *
+ * @param string $format
+ * @return string
+ * @throws \Zend\Crypt\Exception\InvalidArgumentException
+ */
+ public function getPublicKey($format = self::FORMAT_NUMBER)
+ {
+ if ($this->publicKey === null) {
+ throw new Exception\InvalidArgumentException(
+ 'A public key has not yet been generated using a prior call to generateKeys()'
+ );
+ }
+
+ return $this->convert($this->publicKey, self::FORMAT_NUMBER, $format);
+ }
+
+ /**
+ * Compute the shared secret key based on the public key received from the
+ * the second party to this transaction. This should agree to the secret
+ * key the second party computes on our own public key.
+ * Once in agreement, the key is known to only to both parties.
+ * By default, the function expects the public key to be in binary form
+ * which is the typical format when being transmitted.
+ *
+ * If you need the binary form of the shared secret key, call
+ * getSharedSecretKey() with the optional parameter for Binary output.
+ *
+ * @param string $publicKey
+ * @param string $publicKeyFormat
+ * @param string $secretKeyFormat
+ * @return string
+ * @throws \Zend\Crypt\Exception\InvalidArgumentException
+ * @throws \Zend\Crypt\Exception\RuntimeException
+ */
+ public function computeSecretKey(
+ $publicKey,
+ $publicKeyFormat = self::FORMAT_NUMBER,
+ $secretKeyFormat = self::FORMAT_NUMBER
+ ) {
+ if (function_exists('openssl_dh_compute_key') && static::$useOpenssl !== false) {
+ $publicKey = $this->convert($publicKey, $publicKeyFormat, self::FORMAT_BINARY);
+ $secretKey = openssl_dh_compute_key($publicKey, $this->opensslKeyResource);
+ if (false === $secretKey) {
+ throw new Exception\RuntimeException(
+ 'Can not compute key; openssl ' . openssl_error_string()
+ );
+ }
+ $this->secretKey = $this->convert($secretKey, self::FORMAT_BINARY, self::FORMAT_NUMBER);
+ } else {
+ $publicKey = $this->convert($publicKey, $publicKeyFormat, self::FORMAT_NUMBER);
+ if (!preg_match('/^\d+$/', $publicKey)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid parameter; not a positive natural number'
+ );
+ }
+ $this->secretKey = $this->math->powmod($publicKey, $this->getPrivateKey(), $this->getPrime());
+ }
+
+ return $this->getSharedSecretKey($secretKeyFormat);
+ }
+
+ /**
+ * Return the computed shared secret key from the DiffieHellman transaction
+ *
+ * @param string $format
+ * @return string
+ * @throws \Zend\Crypt\Exception\InvalidArgumentException
+ */
+ public function getSharedSecretKey($format = self::FORMAT_NUMBER)
+ {
+ if (!isset($this->secretKey)) {
+ throw new Exception\InvalidArgumentException(
+ 'A secret key has not yet been computed; call computeSecretKey() first'
+ );
+ }
+
+ return $this->convert($this->secretKey, self::FORMAT_NUMBER, $format);
+ }
+
+ /**
+ * Setter for the value of the prime number
+ *
+ * @param string $number
+ * @return DiffieHellman
+ * @throws \Zend\Crypt\Exception\InvalidArgumentException
+ */
+ public function setPrime($number)
+ {
+ if (!preg_match('/^\d+$/', $number) || $number < 11) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid parameter; not a positive natural number or too small: ' .
+ 'should be a large natural number prime'
+ );
+ }
+ $this->prime = (string) $number;
+
+ return $this;
+ }
+
+ /**
+ * Getter for the value of the prime number
+ *
+ * @param string $format
+ * @return string
+ * @throws \Zend\Crypt\Exception\InvalidArgumentException
+ */
+ public function getPrime($format = self::FORMAT_NUMBER)
+ {
+ if (!isset($this->prime)) {
+ throw new Exception\InvalidArgumentException('No prime number has been set');
+ }
+
+ return $this->convert($this->prime, self::FORMAT_NUMBER, $format);
+ }
+
+ /**
+ * Setter for the value of the generator number
+ *
+ * @param string $number
+ * @return DiffieHellman
+ * @throws \Zend\Crypt\Exception\InvalidArgumentException
+ */
+ public function setGenerator($number)
+ {
+ if (!preg_match('/^\d+$/', $number) || $number < 2) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid parameter; not a positive natural number greater than 1'
+ );
+ }
+ $this->generator = (string) $number;
+
+ return $this;
+ }
+
+ /**
+ * Getter for the value of the generator number
+ *
+ * @param string $format
+ * @return string
+ * @throws \Zend\Crypt\Exception\InvalidArgumentException
+ */
+ public function getGenerator($format = self::FORMAT_NUMBER)
+ {
+ if (!isset($this->generator)) {
+ throw new Exception\InvalidArgumentException('No generator number has been set');
+ }
+
+ return $this->convert($this->generator, self::FORMAT_NUMBER, $format);
+ }
+
+ /**
+ * Setter for the value of the private number
+ *
+ * @param string $number
+ * @param string $format
+ * @return DiffieHellman
+ * @throws \Zend\Crypt\Exception\InvalidArgumentException
+ */
+ public function setPrivateKey($number, $format = self::FORMAT_NUMBER)
+ {
+ $number = $this->convert($number, $format, self::FORMAT_NUMBER);
+ if (!preg_match('/^\d+$/', $number)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter; not a positive natural number');
+ }
+ $this->privateKey = (string) $number;
+
+ return $this;
+ }
+
+ /**
+ * Getter for the value of the private number
+ *
+ * @param string $format
+ * @return string
+ */
+ public function getPrivateKey($format = self::FORMAT_NUMBER)
+ {
+ if (!$this->hasPrivateKey()) {
+ $this->setPrivateKey($this->generatePrivateKey(), self::FORMAT_BINARY);
+ }
+
+ return $this->convert($this->privateKey, self::FORMAT_NUMBER, $format);
+ }
+
+ /**
+ * Check whether a private key currently exists.
+ *
+ * @return bool
+ */
+ public function hasPrivateKey()
+ {
+ return isset($this->privateKey);
+ }
+
+ /**
+ * Convert number between formats
+ *
+ * @param string $number
+ * @param string $inputFormat
+ * @param string $outputFormat
+ * @return string
+ */
+ protected function convert($number, $inputFormat = self::FORMAT_NUMBER, $outputFormat = self::FORMAT_BINARY)
+ {
+ if ($inputFormat == $outputFormat) {
+ return $number;
+ }
+
+ // convert to number
+ switch ($inputFormat) {
+ case self::FORMAT_BINARY:
+ case self::FORMAT_BTWOC:
+ $number = $this->math->binToInt($number);
+ break;
+ case self::FORMAT_NUMBER:
+ default:
+ // do nothing
+ break;
+ }
+
+ // convert to output format
+ switch ($outputFormat) {
+ case self::FORMAT_BINARY:
+ return $this->math->intToBin($number);
+ break;
+ case self::FORMAT_BTWOC:
+ return $this->math->intToBin($number, true);
+ break;
+ case self::FORMAT_NUMBER:
+ default:
+ return $number;
+ break;
+ }
+ }
+
+ /**
+ * In the event a private number/key has not been set by the user,
+ * or generated by ext/openssl, a best attempt will be made to
+ * generate a random key. Having a random number generator installed
+ * on linux/bsd is highly recommended! The alternative is not recommended
+ * for production unless without any other option.
+ *
+ * @return string
+ */
+ protected function generatePrivateKey()
+ {
+ return Math\Rand::getBytes(strlen($this->getPrime()), true);
+ }
+}
diff --git a/library/Zend/Crypt/PublicKey/Rsa.php b/library/Zend/Crypt/PublicKey/Rsa.php
new file mode 100755
index 0000000000..44faaa7d78
--- /dev/null
+++ b/library/Zend/Crypt/PublicKey/Rsa.php
@@ -0,0 +1,333 @@
+setPrivateKey($privateKey);
+ }
+ if ($publicKey instanceof Rsa\PublicKey) {
+ $options->setPublicKey($publicKey);
+ }
+
+ return new Rsa($options);
+ }
+
+ /**
+ * Class constructor
+ *
+ * @param RsaOptions $options
+ * @throws Rsa\Exception\RuntimeException
+ */
+ public function __construct(RsaOptions $options = null)
+ {
+ if (!extension_loaded('openssl')) {
+ throw new Exception\RuntimeException(
+ 'Zend\Crypt\PublicKey\Rsa requires openssl extension to be loaded'
+ );
+ }
+
+ if ($options === null) {
+ $this->options = new RsaOptions();
+ } else {
+ $this->options = $options;
+ }
+ }
+
+ /**
+ * Set options
+ *
+ * @param RsaOptions $options
+ * @return Rsa
+ */
+ public function setOptions(RsaOptions $options)
+ {
+ $this->options = $options;
+ return $this;
+ }
+
+ /**
+ * Get options
+ *
+ * @return RsaOptions
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * Return last openssl error(s)
+ *
+ * @return string
+ */
+ public function getOpensslErrorString()
+ {
+ $message = '';
+ while (false !== ($error = openssl_error_string())) {
+ $message .= $error . "\n";
+ }
+ return trim($message);
+ }
+
+ /**
+ * Sign with private key
+ *
+ * @param string $data
+ * @param Rsa\PrivateKey $privateKey
+ * @return string
+ * @throws Rsa\Exception\RuntimeException
+ */
+ public function sign($data, Rsa\PrivateKey $privateKey = null)
+ {
+ $signature = '';
+ if (null === $privateKey) {
+ $privateKey = $this->options->getPrivateKey();
+ }
+
+ $result = openssl_sign(
+ $data,
+ $signature,
+ $privateKey->getOpensslKeyResource(),
+ $this->options->getOpensslSignatureAlgorithm()
+ );
+ if (false === $result) {
+ throw new Exception\RuntimeException(
+ 'Can not generate signature; openssl ' . $this->getOpensslErrorString()
+ );
+ }
+
+ if ($this->options->getBinaryOutput()) {
+ return $signature;
+ }
+
+ return base64_encode($signature);
+ }
+
+ /**
+ * Verify signature with public key
+ *
+ * $signature can be encoded in base64 or not. $mode sets how the input must be processed:
+ * - MODE_AUTO: Check if the $signature is encoded in base64. Not recommended for performance.
+ * - MODE_BASE64: Decode $signature using base64 algorithm.
+ * - MODE_RAW: $signature is not encoded.
+ *
+ * @param string $data
+ * @param string $signature
+ * @param null|Rsa\PublicKey $publicKey
+ * @param int $mode Input encoding
+ * @return bool
+ * @throws Rsa\Exception\RuntimeException
+ * @see Rsa::MODE_AUTO
+ * @see Rsa::MODE_BASE64
+ * @see Rsa::MODE_RAW
+ */
+ public function verify(
+ $data,
+ $signature,
+ Rsa\PublicKey $publicKey = null,
+ $mode = self::MODE_AUTO
+ ) {
+ if (null === $publicKey) {
+ $publicKey = $this->options->getPublicKey();
+ }
+
+ switch ($mode) {
+ case self::MODE_AUTO:
+ // check if data is encoded in Base64
+ $output = base64_decode($signature, true);
+ if ((false !== $output) && ($signature === base64_encode($output))) {
+ $signature = $output;
+ }
+ break;
+ case self::MODE_BASE64:
+ $signature = base64_decode($signature);
+ break;
+ case self::MODE_RAW:
+ default:
+ break;
+ }
+
+ $result = openssl_verify(
+ $data,
+ $signature,
+ $publicKey->getOpensslKeyResource(),
+ $this->options->getOpensslSignatureAlgorithm()
+ );
+ if (-1 === $result) {
+ throw new Exception\RuntimeException(
+ 'Can not verify signature; openssl ' . $this->getOpensslErrorString()
+ );
+ }
+
+ return ($result === 1);
+ }
+
+ /**
+ * Encrypt with private/public key
+ *
+ * @param string $data
+ * @param Rsa\AbstractKey $key
+ * @return string
+ * @throws Rsa\Exception\InvalidArgumentException
+ */
+ public function encrypt($data, Rsa\AbstractKey $key = null)
+ {
+ if (null === $key) {
+ $key = $this->options->getPublicKey();
+ }
+
+ if (null === $key) {
+ throw new Exception\InvalidArgumentException('No key specified for the decryption');
+ }
+
+ $encrypted = $key->encrypt($data);
+
+ if ($this->options->getBinaryOutput()) {
+ return $encrypted;
+ }
+
+ return base64_encode($encrypted);
+ }
+
+ /**
+ * Decrypt with private/public key
+ *
+ * $data can be encoded in base64 or not. $mode sets how the input must be processed:
+ * - MODE_AUTO: Check if the $signature is encoded in base64. Not recommended for performance.
+ * - MODE_BASE64: Decode $data using base64 algorithm.
+ * - MODE_RAW: $data is not encoded.
+ *
+ * @param string $data
+ * @param Rsa\AbstractKey $key
+ * @param int $mode Input encoding
+ * @return string
+ * @throws Rsa\Exception\InvalidArgumentException
+ * @see Rsa::MODE_AUTO
+ * @see Rsa::MODE_BASE64
+ * @see Rsa::MODE_RAW
+ */
+ public function decrypt(
+ $data,
+ Rsa\AbstractKey $key = null,
+ $mode = self::MODE_AUTO
+ ) {
+ if (null === $key) {
+ $key = $this->options->getPrivateKey();
+ }
+
+ if (null === $key) {
+ throw new Exception\InvalidArgumentException('No key specified for the decryption');
+ }
+
+ switch ($mode) {
+ case self::MODE_AUTO:
+ // check if data is encoded in Base64
+ $output = base64_decode($data, true);
+ if ((false !== $output) && ($data === base64_encode($output))) {
+ $data = $output;
+ }
+ break;
+ case self::MODE_BASE64:
+ $data = base64_decode($data);
+ break;
+ case self::MODE_RAW:
+ default:
+ break;
+ }
+
+ return $key->decrypt($data);
+ }
+
+ /**
+ * Generate new private/public key pair
+ * @see RsaOptions::generateKeys()
+ *
+ * @param array $opensslConfig
+ * @return Rsa
+ * @throws Rsa\Exception\RuntimeException
+ */
+ public function generateKeys(array $opensslConfig = array())
+ {
+ $this->options->generateKeys($opensslConfig);
+ return $this;
+ }
+}
diff --git a/library/Zend/Crypt/PublicKey/Rsa/AbstractKey.php b/library/Zend/Crypt/PublicKey/Rsa/AbstractKey.php
new file mode 100755
index 0000000000..5051c77164
--- /dev/null
+++ b/library/Zend/Crypt/PublicKey/Rsa/AbstractKey.php
@@ -0,0 +1,90 @@
+details['bits'];
+ }
+
+ /**
+ * Retrieve openssl key resource
+ *
+ * @return resource
+ */
+ public function getOpensslKeyResource()
+ {
+ return $this->opensslKeyResource;
+ }
+
+ /**
+ * Encrypt using this key
+ *
+ * @abstract
+ * @param string $data
+ * @return string
+ */
+ abstract public function encrypt($data);
+
+ /**
+ * Decrypt using this key
+ *
+ * @abstract
+ * @param string $data
+ * @return string
+ */
+ abstract public function decrypt($data);
+
+ /**
+ * Get string representation of this key
+ *
+ * @abstract
+ * @return string
+ */
+ abstract public function toString();
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+}
diff --git a/library/Zend/Crypt/PublicKey/Rsa/Exception/ExceptionInterface.php b/library/Zend/Crypt/PublicKey/Rsa/Exception/ExceptionInterface.php
new file mode 100755
index 0000000000..ccaad1b39d
--- /dev/null
+++ b/library/Zend/Crypt/PublicKey/Rsa/Exception/ExceptionInterface.php
@@ -0,0 +1,16 @@
+pemString = $pemString;
+ $this->opensslKeyResource = $result;
+ $this->details = openssl_pkey_get_details($this->opensslKeyResource);
+ }
+
+ /**
+ * Get the public key
+ *
+ * @return PublicKey
+ */
+ public function getPublicKey()
+ {
+ if ($this->publicKey === null) {
+ $this->publicKey = new PublicKey($this->details['key']);
+ }
+
+ return $this->publicKey;
+ }
+
+ /**
+ * Encrypt using this key
+ *
+ * @param string $data
+ * @return string
+ * @throws Exception\RuntimeException
+ * @throws Exception\InvalidArgumentException
+ */
+ public function encrypt($data)
+ {
+ if (empty($data)) {
+ throw new Exception\InvalidArgumentException('The data to encrypt cannot be empty');
+ }
+
+ $encrypted = '';
+ $result = openssl_private_encrypt($data, $encrypted, $this->getOpensslKeyResource());
+ if (false === $result) {
+ throw new Exception\RuntimeException(
+ 'Can not encrypt; openssl ' . openssl_error_string()
+ );
+ }
+
+ return $encrypted;
+ }
+
+ /**
+ * Decrypt using this key
+ *
+ * @param string $data
+ * @return string
+ * @throws Exception\RuntimeException
+ * @throws Exception\InvalidArgumentException
+ */
+ public function decrypt($data)
+ {
+ if (!is_string($data)) {
+ throw new Exception\InvalidArgumentException('The data to decrypt must be a string');
+ }
+ if ('' === $data) {
+ throw new Exception\InvalidArgumentException('The data to decrypt cannot be empty');
+ }
+
+ $decrypted = '';
+ $result = openssl_private_decrypt($data, $decrypted, $this->getOpensslKeyResource());
+ if (false === $result) {
+ throw new Exception\RuntimeException(
+ 'Can not decrypt; openssl ' . openssl_error_string()
+ );
+ }
+
+ return $decrypted;
+ }
+
+ /**
+ * @return string
+ */
+ public function toString()
+ {
+ return $this->pemString;
+ }
+}
diff --git a/library/Zend/Crypt/PublicKey/Rsa/PublicKey.php b/library/Zend/Crypt/PublicKey/Rsa/PublicKey.php
new file mode 100755
index 0000000000..d73078d28a
--- /dev/null
+++ b/library/Zend/Crypt/PublicKey/Rsa/PublicKey.php
@@ -0,0 +1,146 @@
+certificateString = $pemStringOrCertificate;
+ } else {
+ $this->pemString = $pemStringOrCertificate;
+ }
+
+ $this->opensslKeyResource = $result;
+ $this->details = openssl_pkey_get_details($this->opensslKeyResource);
+ }
+
+ /**
+ * Encrypt using this key
+ *
+ * @param string $data
+ * @throws Exception\InvalidArgumentException
+ * @throws Exception\RuntimeException
+ * @return string
+ */
+ public function encrypt($data)
+ {
+ if (empty($data)) {
+ throw new Exception\InvalidArgumentException('The data to encrypt cannot be empty');
+ }
+
+ $encrypted = '';
+ $result = openssl_public_encrypt($data, $encrypted, $this->getOpensslKeyResource());
+ if (false === $result) {
+ throw new Exception\RuntimeException(
+ 'Can not encrypt; openssl ' . openssl_error_string()
+ );
+ }
+
+ return $encrypted;
+ }
+
+ /**
+ * Decrypt using this key
+ *
+ * @param string $data
+ * @throws Exception\InvalidArgumentException
+ * @throws Exception\RuntimeException
+ * @return string
+ */
+ public function decrypt($data)
+ {
+ if (!is_string($data)) {
+ throw new Exception\InvalidArgumentException('The data to decrypt must be a string');
+ }
+ if ('' === $data) {
+ throw new Exception\InvalidArgumentException('The data to decrypt cannot be empty');
+ }
+
+ $decrypted = '';
+ $result = openssl_public_decrypt($data, $decrypted, $this->getOpensslKeyResource());
+ if (false === $result) {
+ throw new Exception\RuntimeException(
+ 'Can not decrypt; openssl ' . openssl_error_string()
+ );
+ }
+
+ return $decrypted;
+ }
+
+ /**
+ * Get certificate string
+ *
+ * @return string
+ */
+ public function getCertificate()
+ {
+ return $this->certificateString;
+ }
+
+ /**
+ * To string
+ *
+ * @return string
+ * @throws Exception\RuntimeException
+ */
+ public function toString()
+ {
+ if (!empty($this->certificateString)) {
+ return $this->certificateString;
+ } elseif (!empty($this->pemString)) {
+ return $this->pemString;
+ }
+ throw new Exception\RuntimeException('No public key string representation is available');
+ }
+}
diff --git a/library/Zend/Crypt/PublicKey/RsaOptions.php b/library/Zend/Crypt/PublicKey/RsaOptions.php
new file mode 100755
index 0000000000..54cd7c55b1
--- /dev/null
+++ b/library/Zend/Crypt/PublicKey/RsaOptions.php
@@ -0,0 +1,224 @@
+privateKey = $key;
+ $this->publicKey = $this->privateKey->getPublicKey();
+ return $this;
+ }
+
+ /**
+ * Get private key
+ *
+ * @return null|Rsa\PrivateKey
+ */
+ public function getPrivateKey()
+ {
+ return $this->privateKey;
+ }
+
+ /**
+ * Set public key
+ *
+ * @param Rsa\PublicKey $key
+ * @return RsaOptions
+ */
+ public function setPublicKey(Rsa\PublicKey $key)
+ {
+ $this->publicKey = $key;
+ return $this;
+ }
+
+ /**
+ * Get public key
+ *
+ * @return null|Rsa\PublicKey
+ */
+ public function getPublicKey()
+ {
+ return $this->publicKey;
+ }
+
+ /**
+ * Set pass phrase
+ *
+ * @param string $phrase
+ * @return RsaOptions
+ */
+ public function setPassPhrase($phrase)
+ {
+ $this->passPhrase = (string) $phrase;
+ return $this;
+ }
+
+ /**
+ * Get pass phrase
+ *
+ * @return string
+ */
+ public function getPassPhrase()
+ {
+ return $this->passPhrase;
+ }
+
+ /**
+ * Set hash algorithm
+ *
+ * @param string $hash
+ * @return RsaOptions
+ * @throws Rsa\Exception\RuntimeException
+ * @throws Rsa\Exception\InvalidArgumentException
+ */
+ public function setHashAlgorithm($hash)
+ {
+ $hashUpper = strtoupper($hash);
+ if (!defined('OPENSSL_ALGO_' . $hashUpper)) {
+ throw new Exception\InvalidArgumentException(
+ "Hash algorithm '{$hash}' is not supported"
+ );
+ }
+
+ $this->hashAlgorithm = strtolower($hash);
+ $this->opensslSignatureAlgorithm = constant('OPENSSL_ALGO_' . $hashUpper);
+ return $this;
+ }
+
+ /**
+ * Get hash algorithm
+ *
+ * @return string
+ */
+ public function getHashAlgorithm()
+ {
+ return $this->hashAlgorithm;
+ }
+
+ public function getOpensslSignatureAlgorithm()
+ {
+ if (!isset($this->opensslSignatureAlgorithm)) {
+ $this->opensslSignatureAlgorithm = constant('OPENSSL_ALGO_' . strtoupper($this->hashAlgorithm));
+ }
+ return $this->opensslSignatureAlgorithm;
+ }
+
+ /**
+ * Enable/disable the binary output
+ *
+ * @param bool $value
+ * @return RsaOptions
+ */
+ public function setBinaryOutput($value)
+ {
+ $this->binaryOutput = (bool) $value;
+ return $this;
+ }
+
+ /**
+ * Get the value of binary output
+ *
+ * @return bool
+ */
+ public function getBinaryOutput()
+ {
+ return $this->binaryOutput;
+ }
+
+ /**
+ * Generate new private/public key pair
+ *
+ * @param array $opensslConfig
+ * @return RsaOptions
+ * @throws Rsa\Exception\RuntimeException
+ */
+ public function generateKeys(array $opensslConfig = array())
+ {
+ $opensslConfig = array_replace(
+ array(
+ 'private_key_type' => OPENSSL_KEYTYPE_RSA,
+ 'private_key_bits' => Rsa\PrivateKey::DEFAULT_KEY_SIZE,
+ 'digest_alg' => $this->getHashAlgorithm()
+ ),
+ $opensslConfig
+ );
+
+ // generate
+ $resource = openssl_pkey_new($opensslConfig);
+ if (false === $resource) {
+ throw new Exception\RuntimeException(
+ 'Can not generate keys; openssl ' . openssl_error_string()
+ );
+ }
+
+ // export key
+ $passPhrase = $this->getPassPhrase();
+ $result = openssl_pkey_export($resource, $private, $passPhrase, $opensslConfig);
+ if (false === $result) {
+ throw new Exception\RuntimeException(
+ 'Can not export key; openssl ' . openssl_error_string()
+ );
+ }
+
+ $details = openssl_pkey_get_details($resource);
+ $this->privateKey = new Rsa\PrivateKey($private, $passPhrase);
+ $this->publicKey = new Rsa\PublicKey($details['key']);
+
+ return $this;
+ }
+}
diff --git a/library/Zend/Crypt/README.md b/library/Zend/Crypt/README.md
new file mode 100755
index 0000000000..1e2e5af919
--- /dev/null
+++ b/library/Zend/Crypt/README.md
@@ -0,0 +1,15 @@
+Crypt Component from ZF2
+========================
+
+This is the Crypt component for ZF2.
+
+- File issues at https://github.com/zendframework/zf2/issues
+- Create pull requests against https://github.com/zendframework/zf2
+- Documentation is at http://framework.zend.com/docs
+
+LICENSE
+-------
+
+The files in this archive are released under the [Zend Framework
+license](http://framework.zend.com/license), which is a 3-clause BSD license.
+
diff --git a/library/Zend/Crypt/Symmetric/Exception/ExceptionInterface.php b/library/Zend/Crypt/Symmetric/Exception/ExceptionInterface.php
new file mode 100755
index 0000000000..7e62c51b20
--- /dev/null
+++ b/library/Zend/Crypt/Symmetric/Exception/ExceptionInterface.php
@@ -0,0 +1,16 @@
+ 'rijndael-128',
+ 'blowfish' => 'blowfish',
+ 'des' => 'des',
+ '3des' => 'tripledes',
+ 'tripledes' => 'tripledes',
+ 'cast-128' => 'cast-128',
+ 'cast-256' => 'cast-256',
+ 'rijndael-128' => 'rijndael-128',
+ 'rijndael-192' => 'rijndael-192',
+ 'rijndael-256' => 'rijndael-256',
+ 'saferplus' => 'saferplus',
+ 'serpent' => 'serpent',
+ 'twofish' => 'twofish'
+ );
+
+ /**
+ * Supported encryption modes
+ *
+ * @var array
+ */
+ protected $supportedModes = array(
+ 'cbc' => 'cbc',
+ 'cfb' => 'cfb',
+ 'ctr' => 'ctr',
+ 'ofb' => 'ofb',
+ 'nofb' => 'nofb',
+ 'ncfb' => 'ncfb'
+ );
+
+ /**
+ * Constructor
+ *
+ * @param array|Traversable $options
+ * @throws Exception\RuntimeException
+ * @throws Exception\InvalidArgumentException
+ */
+ public function __construct($options = array())
+ {
+ if (!extension_loaded('mcrypt')) {
+ throw new Exception\RuntimeException(
+ 'You cannot use ' . __CLASS__ . ' without the Mcrypt extension'
+ );
+ }
+ if (!empty($options)) {
+ if ($options instanceof Traversable) {
+ $options = ArrayUtils::iteratorToArray($options);
+ } elseif (!is_array($options)) {
+ throw new Exception\InvalidArgumentException(
+ 'The options parameter must be an array, a Zend\Config\Config object or a Traversable'
+ );
+ }
+ foreach ($options as $key => $value) {
+ switch (strtolower($key)) {
+ case 'algo':
+ case 'algorithm':
+ $this->setAlgorithm($value);
+ break;
+ case 'mode':
+ $this->setMode($value);
+ break;
+ case 'key':
+ $this->setKey($value);
+ break;
+ case 'iv':
+ case 'salt':
+ $this->setSalt($value);
+ break;
+ case 'padding':
+ $plugins = static::getPaddingPluginManager();
+ $padding = $plugins->get($value);
+ $this->padding = $padding;
+ break;
+ }
+ }
+ }
+ $this->setDefaultOptions($options);
+ }
+
+ /**
+ * Set default options
+ *
+ * @param array $options
+ * @return void
+ */
+ protected function setDefaultOptions($options = array())
+ {
+ if (!isset($options['padding'])) {
+ $plugins = static::getPaddingPluginManager();
+ $padding = $plugins->get(self::DEFAULT_PADDING);
+ $this->padding = $padding;
+ }
+ }
+
+ /**
+ * Returns the padding plugin manager. If it doesn't exist it's created.
+ *
+ * @return PaddingPluginManager
+ */
+ public static function getPaddingPluginManager()
+ {
+ if (static::$paddingPlugins === null) {
+ self::setPaddingPluginManager(new PaddingPluginManager());
+ }
+
+ return static::$paddingPlugins;
+ }
+
+ /**
+ * Set the padding plugin manager
+ *
+ * @param string|PaddingPluginManager $plugins
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public static function setPaddingPluginManager($plugins)
+ {
+ if (is_string($plugins)) {
+ if (!class_exists($plugins)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Unable to locate padding plugin manager via class "%s"; class does not exist',
+ $plugins
+ ));
+ }
+ $plugins = new $plugins();
+ }
+ if (!$plugins instanceof PaddingPluginManager) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Padding plugins must extend %s\PaddingPluginManager; received "%s"',
+ __NAMESPACE__,
+ (is_object($plugins) ? get_class($plugins) : gettype($plugins))
+ ));
+ }
+ static::$paddingPlugins = $plugins;
+ }
+
+ /**
+ * Get the maximum key size for the selected cipher and mode of operation
+ *
+ * @return int
+ */
+ public function getKeySize()
+ {
+ return mcrypt_get_key_size($this->supportedAlgos[$this->algo], $this->supportedModes[$this->mode]);
+ }
+
+ /**
+ * Set the encryption key
+ * If the key is longer than maximum supported, it will be truncated by getKey().
+ *
+ * @param string $key
+ * @throws Exception\InvalidArgumentException
+ * @return Mcrypt
+ */
+ public function setKey($key)
+ {
+ $keyLen = strlen($key);
+
+ if (!$keyLen) {
+ throw new Exception\InvalidArgumentException('The key cannot be empty');
+ }
+ $keySizes = mcrypt_module_get_supported_key_sizes($this->supportedAlgos[$this->algo]);
+ $maxKey = $this->getKeySize();
+
+ /*
+ * blowfish has $keySizes empty, meaning it can have arbitrary key length.
+ * the others are more picky.
+ */
+ if (!empty($keySizes) && $keyLen < $maxKey) {
+ if (!in_array($keyLen, $keySizes)) {
+ throw new Exception\InvalidArgumentException(
+ "The size of the key must be one of " . implode(", ", $keySizes) . " bytes or longer"
+ );
+ }
+ }
+ $this->key = $key;
+
+ return $this;
+ }
+
+ /**
+ * Get the encryption key
+ *
+ * @return string
+ */
+ public function getKey()
+ {
+ if (empty($this->key)) {
+ return null;
+ }
+ return substr($this->key, 0, $this->getKeySize());
+ }
+
+ /**
+ * Set the encryption algorithm (cipher)
+ *
+ * @param string $algo
+ * @throws Exception\InvalidArgumentException
+ * @return Mcrypt
+ */
+ public function setAlgorithm($algo)
+ {
+ if (!array_key_exists($algo, $this->supportedAlgos)) {
+ throw new Exception\InvalidArgumentException(
+ "The algorithm $algo is not supported by " . __CLASS__
+ );
+ }
+ $this->algo = $algo;
+
+ return $this;
+ }
+
+ /**
+ * Get the encryption algorithm
+ *
+ * @return string
+ */
+ public function getAlgorithm()
+ {
+ return $this->algo;
+ }
+
+ /**
+ * Set the padding object
+ *
+ * @param Padding\PaddingInterface $padding
+ * @return Mcrypt
+ */
+ public function setPadding(Padding\PaddingInterface $padding)
+ {
+ $this->padding = $padding;
+
+ return $this;
+ }
+
+ /**
+ * Get the padding object
+ *
+ * @return Padding\PaddingInterface
+ */
+ public function getPadding()
+ {
+ return $this->padding;
+ }
+
+ /**
+ * Encrypt
+ *
+ * @param string $data
+ * @throws Exception\InvalidArgumentException
+ * @return string
+ */
+ public function encrypt($data)
+ {
+ // Cannot encrypt empty string
+ if (!is_string($data) || $data === '') {
+ throw new Exception\InvalidArgumentException('The data to encrypt cannot be empty');
+ }
+ if (null === $this->getKey()) {
+ throw new Exception\InvalidArgumentException('No key specified for the encryption');
+ }
+ if (null === $this->getSalt()) {
+ throw new Exception\InvalidArgumentException('The salt (IV) cannot be empty');
+ }
+ if (null === $this->getPadding()) {
+ throw new Exception\InvalidArgumentException('You have to specify a padding method');
+ }
+ // padding
+ $data = $this->padding->pad($data, $this->getBlockSize());
+ $iv = $this->getSalt();
+ // encryption
+ $result = mcrypt_encrypt(
+ $this->supportedAlgos[$this->algo],
+ $this->getKey(),
+ $data,
+ $this->supportedModes[$this->mode],
+ $iv
+ );
+
+ return $iv . $result;
+ }
+
+ /**
+ * Decrypt
+ *
+ * @param string $data
+ * @throws Exception\InvalidArgumentException
+ * @return string
+ */
+ public function decrypt($data)
+ {
+ if (empty($data)) {
+ throw new Exception\InvalidArgumentException('The data to decrypt cannot be empty');
+ }
+ if (null === $this->getKey()) {
+ throw new Exception\InvalidArgumentException('No key specified for the decryption');
+ }
+ if (null === $this->getPadding()) {
+ throw new Exception\InvalidArgumentException('You have to specify a padding method');
+ }
+ $iv = substr($data, 0, $this->getSaltSize());
+ $ciphertext = substr($data, $this->getSaltSize());
+ $result = mcrypt_decrypt(
+ $this->supportedAlgos[$this->algo],
+ $this->getKey(),
+ $ciphertext,
+ $this->supportedModes[$this->mode],
+ $iv
+ );
+ // unpadding
+ return $this->padding->strip($result);
+ }
+
+ /**
+ * Get the salt (IV) size
+ *
+ * @return int
+ */
+ public function getSaltSize()
+ {
+ return mcrypt_get_iv_size($this->supportedAlgos[$this->algo], $this->supportedModes[$this->mode]);
+ }
+
+ /**
+ * Get the supported algorithms
+ *
+ * @return array
+ */
+ public function getSupportedAlgorithms()
+ {
+ return array_keys($this->supportedAlgos);
+ }
+
+ /**
+ * Set the salt (IV)
+ *
+ * @param string $salt
+ * @throws Exception\InvalidArgumentException
+ * @return Mcrypt
+ */
+ public function setSalt($salt)
+ {
+ if (empty($salt)) {
+ throw new Exception\InvalidArgumentException('The salt (IV) cannot be empty');
+ }
+ if (strlen($salt) < $this->getSaltSize()) {
+ throw new Exception\InvalidArgumentException(
+ 'The size of the salt (IV) must be at least ' . $this->getSaltSize() . ' bytes'
+ );
+ }
+ $this->iv = $salt;
+
+ return $this;
+ }
+
+ /**
+ * Get the salt (IV) according to the size requested by the algorithm
+ *
+ * @return string
+ */
+ public function getSalt()
+ {
+ if (empty($this->iv)) {
+ return null;
+ }
+ if (strlen($this->iv) < $this->getSaltSize()) {
+ throw new Exception\RuntimeException(
+ 'The size of the salt (IV) must be at least ' . $this->getSaltSize() . ' bytes'
+ );
+ }
+
+ return substr($this->iv, 0, $this->getSaltSize());
+ }
+
+ /**
+ * Get the original salt value
+ *
+ * @return string
+ */
+ public function getOriginalSalt()
+ {
+ return $this->iv;
+ }
+
+ /**
+ * Set the cipher mode
+ *
+ * @param string $mode
+ * @throws Exception\InvalidArgumentException
+ * @return Mcrypt
+ */
+ public function setMode($mode)
+ {
+ if (!empty($mode)) {
+ $mode = strtolower($mode);
+ if (!array_key_exists($mode, $this->supportedModes)) {
+ throw new Exception\InvalidArgumentException(
+ "The mode $mode is not supported by " . __CLASS__
+ );
+ }
+ $this->mode = $mode;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the cipher mode
+ *
+ * @return string
+ */
+ public function getMode()
+ {
+ return $this->mode;
+ }
+
+ /**
+ * Get all supported encryption modes
+ *
+ * @return array
+ */
+ public function getSupportedModes()
+ {
+ return array_keys($this->supportedModes);
+ }
+
+ /**
+ * Get the block size
+ *
+ * @return int
+ */
+ public function getBlockSize()
+ {
+ return mcrypt_get_block_size($this->supportedAlgos[$this->algo], $this->supportedModes[$this->mode]);
+ }
+}
diff --git a/library/Zend/Crypt/Symmetric/Padding/PaddingInterface.php b/library/Zend/Crypt/Symmetric/Padding/PaddingInterface.php
new file mode 100755
index 0000000000..24050c311c
--- /dev/null
+++ b/library/Zend/Crypt/Symmetric/Padding/PaddingInterface.php
@@ -0,0 +1,30 @@
+ 'Zend\Crypt\Symmetric\Padding\Pkcs7'
+ );
+
+ /**
+ * Do not share by default
+ *
+ * @var bool
+ */
+ protected $shareByDefault = false;
+
+ /**
+ * Validate the plugin
+ *
+ * Checks that the padding adapter loaded is an instance of Padding\PaddingInterface.
+ *
+ * @param mixed $plugin
+ * @return void
+ * @throws Exception\InvalidArgumentException if invalid
+ */
+ public function validatePlugin($plugin)
+ {
+ if ($plugin instanceof Padding\PaddingInterface) {
+ // we're okay
+ return;
+ }
+
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Plugin of type %s is invalid; must implement %s\Padding\PaddingInterface',
+ (is_object($plugin) ? get_class($plugin) : gettype($plugin)),
+ __NAMESPACE__
+ ));
+ }
+}
diff --git a/library/Zend/Crypt/Symmetric/SymmetricInterface.php b/library/Zend/Crypt/Symmetric/SymmetricInterface.php
new file mode 100755
index 0000000000..acf160e966
--- /dev/null
+++ b/library/Zend/Crypt/Symmetric/SymmetricInterface.php
@@ -0,0 +1,61 @@
+ 'Zend\Crypt\Symmetric\Mcrypt',
+ );
+
+ /**
+ * Do not share by default
+ *
+ * @var bool
+ */
+ protected $shareByDefault = false;
+
+ /**
+ * Validate the plugin
+ *
+ * Checks that the adapter loaded is an instance
+ * of Symmetric\SymmetricInterface.
+ *
+ * @param mixed $plugin
+ * @return void
+ * @throws Exception\InvalidArgumentException if invalid
+ */
+ public function validatePlugin($plugin)
+ {
+ if ($plugin instanceof Symmetric\SymmetricInterface) {
+ // we're okay
+ return;
+ }
+
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Plugin of type %s is invalid; must implement %s\Symmetric\SymmetricInterface',
+ (is_object($plugin) ? get_class($plugin) : gettype($plugin)),
+ __NAMESPACE__
+ ));
+ }
+}
diff --git a/library/Zend/Crypt/Utils.php b/library/Zend/Crypt/Utils.php
new file mode 100755
index 0000000000..ff013dcbca
--- /dev/null
+++ b/library/Zend/Crypt/Utils.php
@@ -0,0 +1,45 @@
+=5.3.23",
+ "zendframework/zend-math": "self.version",
+ "zendframework/zend-stdlib": "self.version",
+ "zendframework/zend-servicemanager": "self.version"
+ },
+ "suggest": {
+ "ext-mcrypt": "Required for most features of Zend\\Crypt"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev",
+ "dev-develop": "2.4-dev"
+ }
+ }
+}
diff --git a/library/Zend/Db/Adapter/Adapter.php b/library/Zend/Db/Adapter/Adapter.php
new file mode 100755
index 0000000000..b4c7edffff
--- /dev/null
+++ b/library/Zend/Db/Adapter/Adapter.php
@@ -0,0 +1,387 @@
+createProfiler($parameters);
+ }
+ $driver = $this->createDriver($parameters);
+ } elseif (!$driver instanceof Driver\DriverInterface) {
+ throw new Exception\InvalidArgumentException(
+ 'The supplied or instantiated driver object does not implement Zend\Db\Adapter\Driver\DriverInterface'
+ );
+ }
+
+ $driver->checkEnvironment();
+ $this->driver = $driver;
+
+ if ($platform == null) {
+ $platform = $this->createPlatform($parameters);
+ }
+
+ $this->platform = $platform;
+ $this->queryResultSetPrototype = ($queryResultPrototype) ?: new ResultSet\ResultSet();
+
+ if ($profiler) {
+ $this->setProfiler($profiler);
+ }
+ }
+
+ /**
+ * @param Profiler\ProfilerInterface $profiler
+ * @return Adapter
+ */
+ public function setProfiler(Profiler\ProfilerInterface $profiler)
+ {
+ $this->profiler = $profiler;
+ if ($this->driver instanceof Profiler\ProfilerAwareInterface) {
+ $this->driver->setProfiler($profiler);
+ }
+ return $this;
+ }
+
+ /**
+ * @return null|Profiler\ProfilerInterface
+ */
+ public function getProfiler()
+ {
+ return $this->profiler;
+ }
+
+ /**
+ * getDriver()
+ *
+ * @throws Exception\RuntimeException
+ * @return Driver\DriverInterface
+ */
+ public function getDriver()
+ {
+ if ($this->driver == null) {
+ throw new Exception\RuntimeException('Driver has not been set or configured for this adapter.');
+ }
+ return $this->driver;
+ }
+
+ /**
+ * @return Platform\PlatformInterface
+ */
+ public function getPlatform()
+ {
+ return $this->platform;
+ }
+
+ /**
+ * @return ResultSet\ResultSetInterface
+ */
+ public function getQueryResultSetPrototype()
+ {
+ return $this->queryResultSetPrototype;
+ }
+
+ public function getCurrentSchema()
+ {
+ return $this->driver->getConnection()->getCurrentSchema();
+ }
+
+ /**
+ * query() is a convenience function
+ *
+ * @param string $sql
+ * @param string|array|ParameterContainer $parametersOrQueryMode
+ * @throws Exception\InvalidArgumentException
+ * @return Driver\StatementInterface|ResultSet\ResultSet
+ */
+ public function query($sql, $parametersOrQueryMode = self::QUERY_MODE_PREPARE, ResultSet\ResultSetInterface $resultPrototype = null)
+ {
+ if (is_string($parametersOrQueryMode) && in_array($parametersOrQueryMode, array(self::QUERY_MODE_PREPARE, self::QUERY_MODE_EXECUTE))) {
+ $mode = $parametersOrQueryMode;
+ $parameters = null;
+ } elseif (is_array($parametersOrQueryMode) || $parametersOrQueryMode instanceof ParameterContainer) {
+ $mode = self::QUERY_MODE_PREPARE;
+ $parameters = $parametersOrQueryMode;
+ } else {
+ throw new Exception\InvalidArgumentException('Parameter 2 to this method must be a flag, an array, or ParameterContainer');
+ }
+
+ if ($mode == self::QUERY_MODE_PREPARE) {
+ $this->lastPreparedStatement = null;
+ $this->lastPreparedStatement = $this->driver->createStatement($sql);
+ $this->lastPreparedStatement->prepare();
+ if (is_array($parameters) || $parameters instanceof ParameterContainer) {
+ $this->lastPreparedStatement->setParameterContainer((is_array($parameters)) ? new ParameterContainer($parameters) : $parameters);
+ $result = $this->lastPreparedStatement->execute();
+ } else {
+ return $this->lastPreparedStatement;
+ }
+ } else {
+ $result = $this->driver->getConnection()->execute($sql);
+ }
+
+ if ($result instanceof Driver\ResultInterface && $result->isQueryResult()) {
+ $resultSet = clone ($resultPrototype ?: $this->queryResultSetPrototype);
+ $resultSet->initialize($result);
+ return $resultSet;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Create statement
+ *
+ * @param string $initialSql
+ * @param ParameterContainer $initialParameters
+ * @return Driver\StatementInterface
+ */
+ public function createStatement($initialSql = null, $initialParameters = null)
+ {
+ $statement = $this->driver->createStatement($initialSql);
+ if ($initialParameters == null || !$initialParameters instanceof ParameterContainer && is_array($initialParameters)) {
+ $initialParameters = new ParameterContainer((is_array($initialParameters) ? $initialParameters : array()));
+ }
+ $statement->setParameterContainer($initialParameters);
+ return $statement;
+ }
+
+ public function getHelpers(/* $functions */)
+ {
+ $functions = array();
+ $platform = $this->platform;
+ foreach (func_get_args() as $arg) {
+ switch ($arg) {
+ case self::FUNCTION_QUOTE_IDENTIFIER:
+ $functions[] = function ($value) use ($platform) { return $platform->quoteIdentifier($value); };
+ break;
+ case self::FUNCTION_QUOTE_VALUE:
+ $functions[] = function ($value) use ($platform) { return $platform->quoteValue($value); };
+ break;
+
+ }
+ }
+ }
+
+ /**
+ * @param $name
+ * @throws Exception\InvalidArgumentException
+ * @return Driver\DriverInterface|Platform\PlatformInterface
+ */
+ public function __get($name)
+ {
+ switch (strtolower($name)) {
+ case 'driver':
+ return $this->driver;
+ case 'platform':
+ return $this->platform;
+ default:
+ throw new Exception\InvalidArgumentException('Invalid magic property on adapter');
+ }
+ }
+
+ /**
+ * @param array $parameters
+ * @return Driver\DriverInterface
+ * @throws \InvalidArgumentException
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function createDriver($parameters)
+ {
+ if (!isset($parameters['driver'])) {
+ throw new Exception\InvalidArgumentException(__FUNCTION__ . ' expects a "driver" key to be present inside the parameters');
+ }
+
+ if ($parameters['driver'] instanceof Driver\DriverInterface) {
+ return $parameters['driver'];
+ }
+
+ if (!is_string($parameters['driver'])) {
+ throw new Exception\InvalidArgumentException(__FUNCTION__ . ' expects a "driver" to be a string or instance of DriverInterface');
+ }
+
+ $options = array();
+ if (isset($parameters['options'])) {
+ $options = (array) $parameters['options'];
+ unset($parameters['options']);
+ }
+
+ $driverName = strtolower($parameters['driver']);
+ switch ($driverName) {
+ case 'mysqli':
+ $driver = new Driver\Mysqli\Mysqli($parameters, null, null, $options);
+ break;
+ case 'sqlsrv':
+ $driver = new Driver\Sqlsrv\Sqlsrv($parameters);
+ break;
+ case 'oci8':
+ $driver = new Driver\Oci8\Oci8($parameters);
+ break;
+ case 'pgsql':
+ $driver = new Driver\Pgsql\Pgsql($parameters);
+ break;
+ case 'ibmdb2':
+ $driver = new Driver\IbmDb2\IbmDb2($parameters);
+ break;
+ case 'pdo':
+ default:
+ if ($driverName == 'pdo' || strpos($driverName, 'pdo') === 0) {
+ $driver = new Driver\Pdo\Pdo($parameters);
+ }
+ }
+
+ if (!isset($driver) || !$driver instanceof Driver\DriverInterface) {
+ throw new Exception\InvalidArgumentException('DriverInterface expected', null, null);
+ }
+
+ return $driver;
+ }
+
+ /**
+ * @param Driver\DriverInterface $driver
+ * @return Platform\PlatformInterface
+ */
+ protected function createPlatform($parameters)
+ {
+ if (isset($parameters['platform'])) {
+ $platformName = $parameters['platform'];
+ } elseif ($this->driver instanceof Driver\DriverInterface) {
+ $platformName = $this->driver->getDatabasePlatformName(Driver\DriverInterface::NAME_FORMAT_CAMELCASE);
+ } else {
+ throw new Exception\InvalidArgumentException('A platform could not be determined from the provided configuration');
+ }
+
+ // currently only supported by the IbmDb2 & Oracle concrete implementations
+ $options = (isset($parameters['platform_options'])) ? $parameters['platform_options'] : array();
+
+ switch ($platformName) {
+ case 'Mysql':
+ // mysqli or pdo_mysql driver
+ $driver = ($this->driver instanceof Driver\Mysqli\Mysqli || $this->driver instanceof Driver\Pdo\Pdo) ? $this->driver : null;
+ return new Platform\Mysql($driver);
+ case 'SqlServer':
+ // PDO is only supported driver for quoting values in this platform
+ return new Platform\SqlServer(($this->driver instanceof Driver\Pdo\Pdo) ? $this->driver : null);
+ case 'Oracle':
+ // oracle does not accept a driver as an option, no driver specific quoting available
+ return new Platform\Oracle($options);
+ case 'Sqlite':
+ // PDO is only supported driver for quoting values in this platform
+ return new Platform\Sqlite(($this->driver instanceof Driver\Pdo\Pdo) ? $this->driver : null);
+ case 'Postgresql':
+ // pgsql or pdo postgres driver
+ $driver = ($this->driver instanceof Driver\Pgsql\Pgsql || $this->driver instanceof Driver\Pdo\Pdo) ? $this->driver : null;
+ return new Platform\Postgresql($driver);
+ case 'IbmDb2':
+ // ibm_db2 driver escaping does not need an action connection
+ return new Platform\IbmDb2($options);
+ default:
+ return new Platform\Sql92();
+ }
+ }
+
+ protected function createProfiler($parameters)
+ {
+ if ($parameters['profiler'] instanceof Profiler\ProfilerInterface) {
+ $profiler = $parameters['profiler'];
+ } elseif (is_bool($parameters['profiler'])) {
+ $profiler = ($parameters['profiler'] == true) ? new Profiler\Profiler : null;
+ } else {
+ throw new Exception\InvalidArgumentException(
+ '"profiler" parameter must be an instance of ProfilerInterface or a boolean'
+ );
+ }
+ return $profiler;
+ }
+
+ /**
+ * @param array $parameters
+ * @return Driver\DriverInterface
+ * @throws \InvalidArgumentException
+ * @throws Exception\InvalidArgumentException
+ * @deprecated
+ */
+ protected function createDriverFromParameters(array $parameters)
+ {
+ return $this->createDriver($parameters);
+ }
+
+ /**
+ * @param Driver\DriverInterface $driver
+ * @return Platform\PlatformInterface
+ * @deprecated
+ */
+ protected function createPlatformFromDriver(Driver\DriverInterface $driver)
+ {
+ return $this->createPlatform($driver);
+ }
+}
diff --git a/library/Zend/Db/Adapter/AdapterAbstractServiceFactory.php b/library/Zend/Db/Adapter/AdapterAbstractServiceFactory.php
new file mode 100755
index 0000000000..42800a2ce9
--- /dev/null
+++ b/library/Zend/Db/Adapter/AdapterAbstractServiceFactory.php
@@ -0,0 +1,99 @@
+getConfig($services);
+ if (empty($config)) {
+ return false;
+ }
+
+ return (
+ isset($config[$requestedName])
+ && is_array($config[$requestedName])
+ && !empty($config[$requestedName])
+ );
+ }
+
+ /**
+ * Create a DB adapter
+ *
+ * @param ServiceLocatorInterface $services
+ * @param string $name
+ * @param string $requestedName
+ * @return Adapter
+ */
+ public function createServiceWithName(ServiceLocatorInterface $services, $name, $requestedName)
+ {
+ $config = $this->getConfig($services);
+ return new Adapter($config[$requestedName]);
+ }
+
+ /**
+ * Get db configuration, if any
+ *
+ * @param ServiceLocatorInterface $services
+ * @return array
+ */
+ protected function getConfig(ServiceLocatorInterface $services)
+ {
+ if ($this->config !== null) {
+ return $this->config;
+ }
+
+ if (!$services->has('Config')) {
+ $this->config = array();
+ return $this->config;
+ }
+
+ $config = $services->get('Config');
+ if (!isset($config['db'])
+ || !is_array($config['db'])
+ ) {
+ $this->config = array();
+ return $this->config;
+ }
+
+ $config = $config['db'];
+ if (!isset($config['adapters'])
+ || !is_array($config['adapters'])
+ ) {
+ $this->config = array();
+ return $this->config;
+ }
+
+ $this->config = $config['adapters'];
+ return $this->config;
+ }
+}
diff --git a/library/Zend/Db/Adapter/AdapterAwareInterface.php b/library/Zend/Db/Adapter/AdapterAwareInterface.php
new file mode 100755
index 0000000000..95443a9a0d
--- /dev/null
+++ b/library/Zend/Db/Adapter/AdapterAwareInterface.php
@@ -0,0 +1,21 @@
+adapter = $adapter;
+
+ return $this;
+ }
+}
diff --git a/library/Zend/Db/Adapter/AdapterInterface.php b/library/Zend/Db/Adapter/AdapterInterface.php
new file mode 100755
index 0000000000..57a6a73763
--- /dev/null
+++ b/library/Zend/Db/Adapter/AdapterInterface.php
@@ -0,0 +1,28 @@
+get('Config');
+ return new Adapter($config['db']);
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/ConnectionInterface.php b/library/Zend/Db/Adapter/Driver/ConnectionInterface.php
new file mode 100755
index 0000000000..2e27fd6889
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/ConnectionInterface.php
@@ -0,0 +1,85 @@
+driver = $driver;
+ }
+
+ /**
+ * Get name
+ *
+ * @return string
+ */
+ abstract public function getName();
+}
diff --git a/library/Zend/Db/Adapter/Driver/Feature/DriverFeatureInterface.php b/library/Zend/Db/Adapter/Driver/Feature/DriverFeatureInterface.php
new file mode 100755
index 0000000000..10c96f7731
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Feature/DriverFeatureInterface.php
@@ -0,0 +1,37 @@
+setConnectionParameters($connectionParameters);
+ } elseif (is_resource($connectionParameters)) {
+ $this->setResource($connectionParameters);
+ } elseif (null !== $connectionParameters) {
+ throw new Exception\InvalidArgumentException(
+ '$connection must be an array of parameters, a db2 connection resource or null'
+ );
+ }
+ }
+
+ /**
+ * Set driver
+ *
+ * @param IbmDb2 $driver
+ * @return Connection
+ */
+ public function setDriver(IbmDb2 $driver)
+ {
+ $this->driver = $driver;
+ return $this;
+ }
+
+ /**
+ * @param Profiler\ProfilerInterface $profiler
+ * @return Connection
+ */
+ public function setProfiler(Profiler\ProfilerInterface $profiler)
+ {
+ $this->profiler = $profiler;
+ return $this;
+ }
+
+ /**
+ * @return null|Profiler\ProfilerInterface
+ */
+ public function getProfiler()
+ {
+ return $this->profiler;
+ }
+
+ /**
+ * @param array $connectionParameters
+ * @return Connection
+ */
+ public function setConnectionParameters(array $connectionParameters)
+ {
+ $this->connectionParameters = $connectionParameters;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getConnectionParameters()
+ {
+ return $this->connectionParameters;
+ }
+
+ /**
+ * @param resource $resource DB2 resource
+ * @return Connection
+ */
+ public function setResource($resource)
+ {
+ if (!is_resource($resource) || get_resource_type($resource) !== 'DB2 Connection') {
+ throw new Exception\InvalidArgumentException('The resource provided must be of type "DB2 Connection"');
+ }
+ $this->resource = $resource;
+ return $this;
+ }
+
+ /**
+ * Get current schema
+ *
+ * @return string
+ */
+ public function getCurrentSchema()
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+
+ $info = db2_server_info($this->resource);
+ return (isset($info->DB_NAME) ? $info->DB_NAME : '');
+ }
+
+ /**
+ * Get resource
+ *
+ * @return mixed
+ */
+ public function getResource()
+ {
+ return $this->resource;
+ }
+
+ /**
+ * Connect
+ *
+ * @return self
+ */
+ public function connect()
+ {
+ if (is_resource($this->resource)) {
+ return $this;
+ }
+
+ // localize
+ $p = $this->connectionParameters;
+
+ // given a list of key names, test for existence in $p
+ $findParameterValue = function (array $names) use ($p) {
+ foreach ($names as $name) {
+ if (isset($p[$name])) {
+ return $p[$name];
+ }
+ }
+
+ return null;
+ };
+
+ $database = $findParameterValue(array('database', 'db'));
+ $username = $findParameterValue(array('username', 'uid', 'UID'));
+ $password = $findParameterValue(array('password', 'pwd', 'PWD'));
+ $isPersistent = $findParameterValue(array('persistent', 'PERSISTENT', 'Persistent'));
+ $options = (isset($p['driver_options']) ? $p['driver_options'] : array());
+ $connect = ((bool) $isPersistent) ? 'db2_pconnect' : 'db2_connect';
+
+ $this->resource = $connect($database, $username, $password, $options);
+
+ if ($this->resource === false) {
+ throw new Exception\RuntimeException(sprintf(
+ '%s: Unable to connect to database',
+ __METHOD__
+ ));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Is connected
+ *
+ * @return bool
+ */
+ public function isConnected()
+ {
+ return ($this->resource !== null);
+ }
+
+ /**
+ * Disconnect
+ *
+ * @return ConnectionInterface
+ */
+ public function disconnect()
+ {
+ if ($this->resource) {
+ db2_close($this->resource);
+ $this->resource = null;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Begin transaction
+ *
+ * @return ConnectionInterface
+ */
+ public function beginTransaction()
+ {
+ if ($this->isI5() && !ini_get('ibm_db2.i5_allow_commit')) {
+ throw new Exception\RuntimeException(
+ 'DB2 transactions are not enabled, you need to set the ibm_db2.i5_allow_commit=1 in your php.ini'
+ );
+ }
+
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+
+ $this->prevAutocommit = db2_autocommit($this->resource);
+ db2_autocommit($this->resource, DB2_AUTOCOMMIT_OFF);
+ $this->inTransaction = true;
+ return $this;
+ }
+
+ /**
+ * In transaction
+ *
+ * @return bool
+ */
+ public function inTransaction()
+ {
+ return $this->inTransaction;
+ }
+
+ /**
+ * Commit
+ *
+ * @return ConnectionInterface
+ */
+ public function commit()
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+
+ if (!db2_commit($this->resource)) {
+ throw new Exception\RuntimeException("The commit has not been successful");
+ }
+
+ if ($this->prevAutocommit) {
+ db2_autocommit($this->resource, $this->prevAutocommit);
+ }
+
+ $this->inTransaction = false;
+ return $this;
+ }
+
+ /**
+ * Rollback
+ *
+ * @return ConnectionInterface
+ */
+ public function rollback()
+ {
+ if (!$this->resource) {
+ throw new Exception\RuntimeException('Must be connected before you can rollback.');
+ }
+
+ if (!$this->inTransaction) {
+ throw new Exception\RuntimeException('Must call beginTransaction() before you can rollback.');
+ }
+
+ if (!db2_rollback($this->resource)) {
+ throw new Exception\RuntimeException('The rollback has not been successful');
+ }
+
+ if ($this->prevAutocommit) {
+ db2_autocommit($this->resource, $this->prevAutocommit);
+ }
+
+ $this->inTransaction = false;
+ return $this;
+ }
+
+ /**
+ * Execute
+ *
+ * @param string $sql
+ * @return Result
+ */
+ public function execute($sql)
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+
+ if ($this->profiler) {
+ $this->profiler->profilerStart($sql);
+ }
+
+ set_error_handler(function () {}, E_WARNING); // suppress warnings
+ $resultResource = db2_exec($this->resource, $sql);
+ restore_error_handler();
+
+ if ($this->profiler) {
+ $this->profiler->profilerFinish($sql);
+ }
+
+ // if the returnValue is something other than a pg result resource, bypass wrapping it
+ if ($resultResource === false) {
+ throw new Exception\InvalidQueryException(db2_stmt_errormsg());
+ }
+
+ return $this->driver->createResult(($resultResource === true) ? $this->resource : $resultResource);
+ }
+
+ /**
+ * Get last generated id
+ *
+ * @param null $name Ignored
+ * @return int
+ */
+ public function getLastGeneratedValue($name = null)
+ {
+ return db2_last_insert_id($this->resource);
+ }
+
+ /**
+ * Determine if the OS is OS400 (AS400, IBM i)
+ *
+ * @return bool
+ */
+ protected function isI5()
+ {
+ if (isset($this->i5)) {
+ return $this->i5;
+ }
+
+ $this->i5 = php_uname('s') == 'OS400' ? true : false;
+ return $this->i5;
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/IbmDb2/IbmDb2.php b/library/Zend/Db/Adapter/Driver/IbmDb2/IbmDb2.php
new file mode 100755
index 0000000000..d129b49b38
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/IbmDb2/IbmDb2.php
@@ -0,0 +1,214 @@
+registerConnection($connection);
+ $this->registerStatementPrototype(($statementPrototype) ?: new Statement());
+ $this->registerResultPrototype(($resultPrototype) ?: new Result());
+ }
+
+ /**
+ * @param Profiler\ProfilerInterface $profiler
+ * @return IbmDb2
+ */
+ public function setProfiler(Profiler\ProfilerInterface $profiler)
+ {
+ $this->profiler = $profiler;
+ if ($this->connection instanceof Profiler\ProfilerAwareInterface) {
+ $this->connection->setProfiler($profiler);
+ }
+ if ($this->statementPrototype instanceof Profiler\ProfilerAwareInterface) {
+ $this->statementPrototype->setProfiler($profiler);
+ }
+ return $this;
+ }
+
+ /**
+ * @return null|Profiler\ProfilerInterface
+ */
+ public function getProfiler()
+ {
+ return $this->profiler;
+ }
+
+ /**
+ * @param Connection $connection
+ * @return IbmDb2
+ */
+ public function registerConnection(Connection $connection)
+ {
+ $this->connection = $connection;
+ $this->connection->setDriver($this);
+ return $this;
+ }
+
+ /**
+ * @param Statement $statementPrototype
+ * @return IbmDb2
+ */
+ public function registerStatementPrototype(Statement $statementPrototype)
+ {
+ $this->statementPrototype = $statementPrototype;
+ $this->statementPrototype->setDriver($this);
+ return $this;
+ }
+
+ /**
+ * @param Result $resultPrototype
+ * @return IbmDb2
+ */
+ public function registerResultPrototype(Result $resultPrototype)
+ {
+ $this->resultPrototype = $resultPrototype;
+ return $this;
+ }
+
+ /**
+ * Get database platform name
+ *
+ * @param string $nameFormat
+ * @return string
+ */
+ public function getDatabasePlatformName($nameFormat = self::NAME_FORMAT_CAMELCASE)
+ {
+ if ($nameFormat == self::NAME_FORMAT_CAMELCASE) {
+ return 'IbmDb2';
+ } else {
+ return 'IBM DB2';
+ }
+ }
+
+ /**
+ * Check environment
+ *
+ * @return bool
+ */
+ public function checkEnvironment()
+ {
+ if (!extension_loaded('ibm_db2')) {
+ throw new Exception\RuntimeException('The ibm_db2 extension is required by this driver.');
+ }
+ }
+
+ /**
+ * Get connection
+ *
+ * @return Connection
+ */
+ public function getConnection()
+ {
+ return $this->connection;
+ }
+
+ /**
+ * Create statement
+ *
+ * @param string|resource $sqlOrResource
+ * @return Statement
+ */
+ public function createStatement($sqlOrResource = null)
+ {
+ $statement = clone $this->statementPrototype;
+ if (is_resource($sqlOrResource) && get_resource_type($sqlOrResource) == 'DB2 Statement') {
+ $statement->setResource($sqlOrResource);
+ } else {
+ if (is_string($sqlOrResource)) {
+ $statement->setSql($sqlOrResource);
+ } elseif ($sqlOrResource !== null) {
+ throw new Exception\InvalidArgumentException(
+ __FUNCTION__ . ' only accepts an SQL string or an ibm_db2 resource'
+ );
+ }
+ if (!$this->connection->isConnected()) {
+ $this->connection->connect();
+ }
+ $statement->initialize($this->connection->getResource());
+ }
+ return $statement;
+ }
+
+ /**
+ * Create result
+ *
+ * @param resource $resource
+ * @return Result
+ */
+ public function createResult($resource)
+ {
+ $result = clone $this->resultPrototype;
+ $result->initialize($resource, $this->connection->getLastGeneratedValue());
+ return $result;
+ }
+
+ /**
+ * Get prepare type
+ *
+ * @return array
+ */
+ public function getPrepareType()
+ {
+ return self::PARAMETERIZATION_POSITIONAL;
+ }
+
+ /**
+ * Format parameter name
+ *
+ * @param string $name
+ * @param mixed $type
+ * @return string
+ */
+ public function formatParameterName($name, $type = null)
+ {
+ return '?';
+ }
+
+ /**
+ * Get last generated value
+ *
+ * @return mixed
+ */
+ public function getLastGeneratedValue()
+ {
+ return $this->connection->getLastGeneratedValue();
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/IbmDb2/Result.php b/library/Zend/Db/Adapter/Driver/IbmDb2/Result.php
new file mode 100755
index 0000000000..add4e1e3f1
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/IbmDb2/Result.php
@@ -0,0 +1,192 @@
+resource = $resource;
+ $this->generatedValue = $generatedValue;
+ return $this;
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Return the current element
+ * @link http://php.net/manual/en/iterator.current.php
+ * @return mixed Can return any type.
+ */
+ public function current()
+ {
+ if ($this->currentComplete) {
+ return $this->currentData;
+ }
+
+ $this->currentData = db2_fetch_assoc($this->resource);
+ return $this->currentData;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function next()
+ {
+ $this->currentData = db2_fetch_assoc($this->resource);
+ $this->currentComplete = true;
+ $this->position++;
+ return $this->currentData;
+ }
+
+ /**
+ * @return int|string
+ */
+ public function key()
+ {
+ return $this->position;
+ }
+
+ /**
+ * @return bool
+ */
+ public function valid()
+ {
+ return ($this->currentData !== false);
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Rewind the Iterator to the first element
+ * @link http://php.net/manual/en/iterator.rewind.php
+ * @return void Any returned value is ignored.
+ */
+ public function rewind()
+ {
+ if ($this->position > 0) {
+ throw new Exception\RuntimeException(
+ 'This result is a forward only result set, calling rewind() after moving forward is not supported'
+ );
+ }
+ $this->currentData = db2_fetch_assoc($this->resource);
+ $this->currentComplete = true;
+ $this->position = 1;
+ }
+
+ /**
+ * Force buffering
+ *
+ * @return void
+ */
+ public function buffer()
+ {
+ return null;
+ }
+
+ /**
+ * Check if is buffered
+ *
+ * @return bool|null
+ */
+ public function isBuffered()
+ {
+ return false;
+ }
+
+ /**
+ * Is query result?
+ *
+ * @return bool
+ */
+ public function isQueryResult()
+ {
+ return (db2_num_fields($this->resource) > 0);
+ }
+
+ /**
+ * Get affected rows
+ *
+ * @return int
+ */
+ public function getAffectedRows()
+ {
+ return db2_num_rows($this->resource);
+ }
+
+ /**
+ * Get generated value
+ *
+ * @return mixed|null
+ */
+ public function getGeneratedValue()
+ {
+ return $this->generatedValue;
+ }
+
+ /**
+ * Get the resource
+ *
+ * @return mixed
+ */
+ public function getResource()
+ {
+ return $this->resource;
+ }
+
+ /**
+ * Get field count
+ *
+ * @return int
+ */
+ public function getFieldCount()
+ {
+ return db2_num_fields($this->resource);
+ }
+
+ /**
+ * @return null|int
+ */
+ public function count()
+ {
+ return null;
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/IbmDb2/Statement.php b/library/Zend/Db/Adapter/Driver/IbmDb2/Statement.php
new file mode 100755
index 0000000000..029a9ed276
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/IbmDb2/Statement.php
@@ -0,0 +1,240 @@
+db2 = $resource;
+ return $this;
+ }
+
+ /**
+ * @param IbmDb2 $driver
+ * @return Statement
+ */
+ public function setDriver(IbmDb2 $driver)
+ {
+ $this->driver = $driver;
+ return $this;
+ }
+
+ /**
+ * @param Profiler\ProfilerInterface $profiler
+ * @return Statement
+ */
+ public function setProfiler(Profiler\ProfilerInterface $profiler)
+ {
+ $this->profiler = $profiler;
+ return $this;
+ }
+
+ /**
+ * @return null|Profiler\ProfilerInterface
+ */
+ public function getProfiler()
+ {
+ return $this->profiler;
+ }
+
+ /**
+ * Set sql
+ *
+ * @param $sql
+ * @return mixed
+ */
+ public function setSql($sql)
+ {
+ $this->sql = $sql;
+ return $this;
+ }
+
+ /**
+ * Get sql
+ *
+ * @return mixed
+ */
+ public function getSql()
+ {
+ return $this->sql;
+ }
+
+ /**
+ * Set parameter container
+ *
+ * @param ParameterContainer $parameterContainer
+ * @return mixed
+ */
+ public function setParameterContainer(ParameterContainer $parameterContainer)
+ {
+ $this->parameterContainer = $parameterContainer;
+ return $this;
+ }
+
+ /**
+ * Get parameter container
+ *
+ * @return mixed
+ */
+ public function getParameterContainer()
+ {
+ return $this->parameterContainer;
+ }
+
+ /**
+ * @param $resource
+ * @throws \Zend\Db\Adapter\Exception\InvalidArgumentException
+ */
+ public function setResource($resource)
+ {
+ if (get_resource_type($resource) !== 'DB2 Statement') {
+ throw new Exception\InvalidArgumentException('Resource must be of type DB2 Statement');
+ }
+ $this->resource = $resource;
+ }
+
+ /**
+ * Get resource
+ *
+ * @return resource
+ */
+ public function getResource()
+ {
+ return $this->resource;
+ }
+
+ /**
+ * Prepare sql
+ *
+ * @param string|null $sql
+ * @return Statement
+ */
+ public function prepare($sql = null)
+ {
+ if ($this->isPrepared) {
+ throw new Exception\RuntimeException('This statement has been prepared already');
+ }
+
+ if ($sql == null) {
+ $sql = $this->sql;
+ }
+
+ $this->resource = db2_prepare($this->db2, $sql);
+
+ if ($this->resource === false) {
+ throw new Exception\RuntimeException(db2_stmt_errormsg(), db2_stmt_error());
+ }
+
+ $this->isPrepared = true;
+ return $this;
+ }
+
+ /**
+ * Check if is prepared
+ *
+ * @return bool
+ */
+ public function isPrepared()
+ {
+ return $this->isPrepared;
+ }
+
+ /**
+ * Execute
+ *
+ * @param null $parameters
+ * @return Result
+ */
+ public function execute($parameters = null)
+ {
+ if (!$this->isPrepared) {
+ $this->prepare();
+ }
+
+ /** START Standard ParameterContainer Merging Block */
+ if (!$this->parameterContainer instanceof ParameterContainer) {
+ if ($parameters instanceof ParameterContainer) {
+ $this->parameterContainer = $parameters;
+ $parameters = null;
+ } else {
+ $this->parameterContainer = new ParameterContainer();
+ }
+ }
+
+ if (is_array($parameters)) {
+ $this->parameterContainer->setFromArray($parameters);
+ }
+ /** END Standard ParameterContainer Merging Block */
+
+ if ($this->profiler) {
+ $this->profiler->profilerStart($this);
+ }
+
+ set_error_handler(function () {}, E_WARNING); // suppress warnings
+ $response = db2_execute($this->resource, $this->parameterContainer->getPositionalArray());
+ restore_error_handler();
+
+ if ($this->profiler) {
+ $this->profiler->profilerFinish();
+ }
+
+ if ($response === false) {
+ throw new Exception\RuntimeException(db2_stmt_errormsg($this->resource));
+ }
+
+ $result = $this->driver->createResult($this->resource);
+ return $result;
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Mysqli/Connection.php b/library/Zend/Db/Adapter/Driver/Mysqli/Connection.php
new file mode 100755
index 0000000000..d84db10fe2
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Mysqli/Connection.php
@@ -0,0 +1,346 @@
+setConnectionParameters($connectionInfo);
+ } elseif ($connectionInfo instanceof \mysqli) {
+ $this->setResource($connectionInfo);
+ } elseif (null !== $connectionInfo) {
+ throw new Exception\InvalidArgumentException('$connection must be an array of parameters, a mysqli object or null');
+ }
+ }
+
+ /**
+ * @param Mysqli $driver
+ * @return Connection
+ */
+ public function setDriver(Mysqli $driver)
+ {
+ $this->driver = $driver;
+ return $this;
+ }
+
+ /**
+ * @param Profiler\ProfilerInterface $profiler
+ * @return Connection
+ */
+ public function setProfiler(Profiler\ProfilerInterface $profiler)
+ {
+ $this->profiler = $profiler;
+ return $this;
+ }
+
+ /**
+ * @return null|Profiler\ProfilerInterface
+ */
+ public function getProfiler()
+ {
+ return $this->profiler;
+ }
+
+ /**
+ * Set connection parameters
+ *
+ * @param array $connectionParameters
+ * @return Connection
+ */
+ public function setConnectionParameters(array $connectionParameters)
+ {
+ $this->connectionParameters = $connectionParameters;
+ return $this;
+ }
+
+ /**
+ * Get connection parameters
+ *
+ * @return array
+ */
+ public function getConnectionParameters()
+ {
+ return $this->connectionParameters;
+ }
+
+ /**
+ * Get current schema
+ *
+ * @return string
+ */
+ public function getCurrentSchema()
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+
+ /** @var $result \mysqli_result */
+ $result = $this->resource->query('SELECT DATABASE()');
+ $r = $result->fetch_row();
+ return $r[0];
+ }
+
+ /**
+ * Set resource
+ *
+ * @param \mysqli $resource
+ * @return Connection
+ */
+ public function setResource(\mysqli $resource)
+ {
+ $this->resource = $resource;
+ return $this;
+ }
+
+ /**
+ * Get resource
+ *
+ * @return \mysqli
+ */
+ public function getResource()
+ {
+ $this->connect();
+ return $this->resource;
+ }
+
+ /**
+ * Connect
+ *
+ * @throws Exception\RuntimeException
+ * @return Connection
+ */
+ public function connect()
+ {
+ if ($this->resource instanceof \mysqli) {
+ return $this;
+ }
+
+ // localize
+ $p = $this->connectionParameters;
+
+ // given a list of key names, test for existence in $p
+ $findParameterValue = function (array $names) use ($p) {
+ foreach ($names as $name) {
+ if (isset($p[$name])) {
+ return $p[$name];
+ }
+ }
+ return;
+ };
+
+ $hostname = $findParameterValue(array('hostname', 'host'));
+ $username = $findParameterValue(array('username', 'user'));
+ $password = $findParameterValue(array('password', 'passwd', 'pw'));
+ $database = $findParameterValue(array('database', 'dbname', 'db', 'schema'));
+ $port = (isset($p['port'])) ? (int) $p['port'] : null;
+ $socket = (isset($p['socket'])) ? $p['socket'] : null;
+
+ $this->resource = new \mysqli();
+ $this->resource->init();
+
+ if (!empty($p['driver_options'])) {
+ foreach ($p['driver_options'] as $option => $value) {
+ if (is_string($option)) {
+ $option = strtoupper($option);
+ if (!defined($option)) {
+ continue;
+ }
+ $option = constant($option);
+ }
+ $this->resource->options($option, $value);
+ }
+ }
+
+ $this->resource->real_connect($hostname, $username, $password, $database, $port, $socket);
+
+ if ($this->resource->connect_error) {
+ throw new Exception\RuntimeException(
+ 'Connection error',
+ null,
+ new Exception\ErrorException($this->resource->connect_error, $this->resource->connect_errno)
+ );
+ }
+
+ if (!empty($p['charset'])) {
+ $this->resource->set_charset($p['charset']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Is connected
+ *
+ * @return bool
+ */
+ public function isConnected()
+ {
+ return ($this->resource instanceof \mysqli);
+ }
+
+ /**
+ * Disconnect
+ *
+ * @return void
+ */
+ public function disconnect()
+ {
+ if ($this->resource instanceof \mysqli) {
+ $this->resource->close();
+ }
+ $this->resource = null;
+ }
+
+ /**
+ * Begin transaction
+ *
+ * @return void
+ */
+ public function beginTransaction()
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+
+ $this->resource->autocommit(false);
+ $this->inTransaction = true;
+ }
+
+ /**
+ * In transaction
+ *
+ * @return bool
+ */
+ public function inTransaction()
+ {
+ return $this->inTransaction;
+ }
+
+ /**
+ * Commit
+ *
+ * @return void
+ */
+ public function commit()
+ {
+ if (!$this->resource) {
+ $this->connect();
+ }
+
+ $this->resource->commit();
+ $this->inTransaction = false;
+ $this->resource->autocommit(true);
+ }
+
+ /**
+ * Rollback
+ *
+ * @throws Exception\RuntimeException
+ * @return Connection
+ */
+ public function rollback()
+ {
+ if (!$this->resource) {
+ throw new Exception\RuntimeException('Must be connected before you can rollback.');
+ }
+
+ if (!$this->inTransaction) {
+ throw new Exception\RuntimeException('Must call beginTransaction() before you can rollback.');
+ }
+
+ $this->resource->rollback();
+ $this->resource->autocommit(true);
+ return $this;
+ }
+
+ /**
+ * Execute
+ *
+ * @param string $sql
+ * @throws Exception\InvalidQueryException
+ * @return Result
+ */
+ public function execute($sql)
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+
+ if ($this->profiler) {
+ $this->profiler->profilerStart($sql);
+ }
+
+ $resultResource = $this->resource->query($sql);
+
+ if ($this->profiler) {
+ $this->profiler->profilerFinish($sql);
+ }
+
+ // if the returnValue is something other than a mysqli_result, bypass wrapping it
+ if ($resultResource === false) {
+ throw new Exception\InvalidQueryException($this->resource->error);
+ }
+
+ $resultPrototype = $this->driver->createResult(($resultResource === true) ? $this->resource : $resultResource);
+ return $resultPrototype;
+ }
+
+ /**
+ * Get last generated id
+ *
+ * @param null $name Ignored
+ * @return int
+ */
+ public function getLastGeneratedValue($name = null)
+ {
+ return $this->resource->insert_id;
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Mysqli/Mysqli.php b/library/Zend/Db/Adapter/Driver/Mysqli/Mysqli.php
new file mode 100755
index 0000000000..443350ca10
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Mysqli/Mysqli.php
@@ -0,0 +1,256 @@
+ false
+ );
+
+ /**
+ * Constructor
+ *
+ * @param array|Connection|\mysqli $connection
+ * @param null|Statement $statementPrototype
+ * @param null|Result $resultPrototype
+ * @param array $options
+ */
+ public function __construct($connection, Statement $statementPrototype = null, Result $resultPrototype = null, array $options = array())
+ {
+ if (!$connection instanceof Connection) {
+ $connection = new Connection($connection);
+ }
+
+ $options = array_intersect_key(array_merge($this->options, $options), $this->options);
+
+ $this->registerConnection($connection);
+ $this->registerStatementPrototype(($statementPrototype) ?: new Statement($options['buffer_results']));
+ $this->registerResultPrototype(($resultPrototype) ?: new Result());
+ }
+
+ /**
+ * @param Profiler\ProfilerInterface $profiler
+ * @return Mysqli
+ */
+ public function setProfiler(Profiler\ProfilerInterface $profiler)
+ {
+ $this->profiler = $profiler;
+ if ($this->connection instanceof Profiler\ProfilerAwareInterface) {
+ $this->connection->setProfiler($profiler);
+ }
+ if ($this->statementPrototype instanceof Profiler\ProfilerAwareInterface) {
+ $this->statementPrototype->setProfiler($profiler);
+ }
+ return $this;
+ }
+
+ /**
+ * @return null|Profiler\ProfilerInterface
+ */
+ public function getProfiler()
+ {
+ return $this->profiler;
+ }
+
+ /**
+ * Register connection
+ *
+ * @param Connection $connection
+ * @return Mysqli
+ */
+ public function registerConnection(Connection $connection)
+ {
+ $this->connection = $connection;
+ $this->connection->setDriver($this); // needs access to driver to createStatement()
+ return $this;
+ }
+
+ /**
+ * Register statement prototype
+ *
+ * @param Statement $statementPrototype
+ */
+ public function registerStatementPrototype(Statement $statementPrototype)
+ {
+ $this->statementPrototype = $statementPrototype;
+ $this->statementPrototype->setDriver($this); // needs access to driver to createResult()
+ }
+
+ /**
+ * Get statement prototype
+ *
+ * @return null|Statement
+ */
+ public function getStatementPrototype()
+ {
+ return $this->statementPrototype;
+ }
+
+ /**
+ * Register result prototype
+ *
+ * @param Result $resultPrototype
+ */
+ public function registerResultPrototype(Result $resultPrototype)
+ {
+ $this->resultPrototype = $resultPrototype;
+ }
+
+ /**
+ * @return null|Result
+ */
+ public function getResultPrototype()
+ {
+ return $this->resultPrototype;
+ }
+
+ /**
+ * Get database platform name
+ *
+ * @param string $nameFormat
+ * @return string
+ */
+ public function getDatabasePlatformName($nameFormat = self::NAME_FORMAT_CAMELCASE)
+ {
+ if ($nameFormat == self::NAME_FORMAT_CAMELCASE) {
+ return 'Mysql';
+ }
+
+ return 'MySQL';
+ }
+
+ /**
+ * Check environment
+ *
+ * @throws Exception\RuntimeException
+ * @return void
+ */
+ public function checkEnvironment()
+ {
+ if (!extension_loaded('mysqli')) {
+ throw new Exception\RuntimeException('The Mysqli extension is required for this adapter but the extension is not loaded');
+ }
+ }
+
+ /**
+ * Get connection
+ *
+ * @return Connection
+ */
+ public function getConnection()
+ {
+ return $this->connection;
+ }
+
+ /**
+ * Create statement
+ *
+ * @param string $sqlOrResource
+ * @return Statement
+ */
+ public function createStatement($sqlOrResource = null)
+ {
+ /**
+ * @todo Resource tracking
+ if (is_resource($sqlOrResource) && !in_array($sqlOrResource, $this->resources, true)) {
+ $this->resources[] = $sqlOrResource;
+ }
+ */
+
+ $statement = clone $this->statementPrototype;
+ if ($sqlOrResource instanceof mysqli_stmt) {
+ $statement->setResource($sqlOrResource);
+ } else {
+ if (is_string($sqlOrResource)) {
+ $statement->setSql($sqlOrResource);
+ }
+ if (!$this->connection->isConnected()) {
+ $this->connection->connect();
+ }
+ $statement->initialize($this->connection->getResource());
+ }
+ return $statement;
+ }
+
+ /**
+ * Create result
+ *
+ * @param resource $resource
+ * @param null|bool $isBuffered
+ * @return Result
+ */
+ public function createResult($resource, $isBuffered = null)
+ {
+ $result = clone $this->resultPrototype;
+ $result->initialize($resource, $this->connection->getLastGeneratedValue(), $isBuffered);
+ return $result;
+ }
+
+ /**
+ * Get prepare type
+ *
+ * @return array
+ */
+ public function getPrepareType()
+ {
+ return self::PARAMETERIZATION_POSITIONAL;
+ }
+
+ /**
+ * Format parameter name
+ *
+ * @param string $name
+ * @param mixed $type
+ * @return string
+ */
+ public function formatParameterName($name, $type = null)
+ {
+ return '?';
+ }
+
+ /**
+ * Get last generated value
+ *
+ * @return mixed
+ */
+ public function getLastGeneratedValue()
+ {
+ return $this->getConnection()->getLastGeneratedValue();
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Mysqli/Result.php b/library/Zend/Db/Adapter/Driver/Mysqli/Result.php
new file mode 100755
index 0000000000..11622b15ac
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Mysqli/Result.php
@@ -0,0 +1,341 @@
+ null, 'values' => array());
+
+ /**
+ * @var mixed
+ */
+ protected $generatedValue = null;
+
+ /**
+ * Initialize
+ *
+ * @param mixed $resource
+ * @param mixed $generatedValue
+ * @param bool|null $isBuffered
+ * @throws Exception\InvalidArgumentException
+ * @return Result
+ */
+ public function initialize($resource, $generatedValue, $isBuffered = null)
+ {
+ if (!$resource instanceof \mysqli && !$resource instanceof \mysqli_result && !$resource instanceof \mysqli_stmt) {
+ throw new Exception\InvalidArgumentException('Invalid resource provided.');
+ }
+
+ if ($isBuffered !== null) {
+ $this->isBuffered = $isBuffered;
+ } else {
+ if ($resource instanceof \mysqli || $resource instanceof \mysqli_result
+ || $resource instanceof \mysqli_stmt && $resource->num_rows != 0) {
+ $this->isBuffered = true;
+ }
+ }
+
+ $this->resource = $resource;
+ $this->generatedValue = $generatedValue;
+ return $this;
+ }
+
+ /**
+ * Force buffering
+ *
+ * @throws Exception\RuntimeException
+ */
+ public function buffer()
+ {
+ if ($this->resource instanceof \mysqli_stmt && $this->isBuffered !== true) {
+ if ($this->position > 0) {
+ throw new Exception\RuntimeException('Cannot buffer a result set that has started iteration.');
+ }
+ $this->resource->store_result();
+ $this->isBuffered = true;
+ }
+ }
+
+ /**
+ * Check if is buffered
+ *
+ * @return bool|null
+ */
+ public function isBuffered()
+ {
+ return $this->isBuffered;
+ }
+
+ /**
+ * Return the resource
+ *
+ * @return mixed
+ */
+ public function getResource()
+ {
+ return $this->resource;
+ }
+
+ /**
+ * Is query result?
+ *
+ * @return bool
+ */
+ public function isQueryResult()
+ {
+ return ($this->resource->field_count > 0);
+ }
+
+ /**
+ * Get affected rows
+ *
+ * @return int
+ */
+ public function getAffectedRows()
+ {
+ if ($this->resource instanceof \mysqli || $this->resource instanceof \mysqli_stmt) {
+ return $this->resource->affected_rows;
+ }
+
+ return $this->resource->num_rows;
+ }
+
+ /**
+ * Current
+ *
+ * @return mixed
+ */
+ public function current()
+ {
+ if ($this->currentComplete) {
+ return $this->currentData;
+ }
+
+ if ($this->resource instanceof \mysqli_stmt) {
+ $this->loadDataFromMysqliStatement();
+ return $this->currentData;
+ } else {
+ $this->loadFromMysqliResult();
+ return $this->currentData;
+ }
+ }
+
+ /**
+ * Mysqli's binding and returning of statement values
+ *
+ * Mysqli requires you to bind variables to the extension in order to
+ * get data out. These values have to be references:
+ * @see http://php.net/manual/en/mysqli-stmt.bind-result.php
+ *
+ * @throws Exception\RuntimeException
+ * @return bool
+ */
+ protected function loadDataFromMysqliStatement()
+ {
+ $data = null;
+ // build the default reference based bind structure, if it does not already exist
+ if ($this->statementBindValues['keys'] === null) {
+ $this->statementBindValues['keys'] = array();
+ $resultResource = $this->resource->result_metadata();
+ foreach ($resultResource->fetch_fields() as $col) {
+ $this->statementBindValues['keys'][] = $col->name;
+ }
+ $this->statementBindValues['values'] = array_fill(0, count($this->statementBindValues['keys']), null);
+ $refs = array();
+ foreach ($this->statementBindValues['values'] as $i => &$f) {
+ $refs[$i] = &$f;
+ }
+ call_user_func_array(array($this->resource, 'bind_result'), $this->statementBindValues['values']);
+ }
+
+ if (($r = $this->resource->fetch()) === null) {
+ if (!$this->isBuffered) {
+ $this->resource->close();
+ }
+ return false;
+ } elseif ($r === false) {
+ throw new Exception\RuntimeException($this->resource->error);
+ }
+
+ // dereference
+ for ($i = 0, $count = count($this->statementBindValues['keys']); $i < $count; $i++) {
+ $this->currentData[$this->statementBindValues['keys'][$i]] = $this->statementBindValues['values'][$i];
+ }
+ $this->currentComplete = true;
+ $this->nextComplete = true;
+ $this->position++;
+ return true;
+ }
+
+ /**
+ * Load from mysqli result
+ *
+ * @return bool
+ */
+ protected function loadFromMysqliResult()
+ {
+ $this->currentData = null;
+
+ if (($data = $this->resource->fetch_assoc()) === null) {
+ return false;
+ }
+
+ $this->position++;
+ $this->currentData = $data;
+ $this->currentComplete = true;
+ $this->nextComplete = true;
+ $this->position++;
+ return true;
+ }
+
+ /**
+ * Next
+ *
+ * @return void
+ */
+ public function next()
+ {
+ $this->currentComplete = false;
+
+ if ($this->nextComplete == false) {
+ $this->position++;
+ }
+
+ $this->nextComplete = false;
+ }
+
+ /**
+ * Key
+ *
+ * @return mixed
+ */
+ public function key()
+ {
+ return $this->position;
+ }
+
+ /**
+ * Rewind
+ *
+ * @throws Exception\RuntimeException
+ * @return void
+ */
+ public function rewind()
+ {
+ if ($this->position !== 0) {
+ if ($this->isBuffered === false) {
+ throw new Exception\RuntimeException('Unbuffered results cannot be rewound for multiple iterations');
+ }
+ }
+ $this->resource->data_seek(0); // works for both mysqli_result & mysqli_stmt
+ $this->currentComplete = false;
+ $this->position = 0;
+ }
+
+ /**
+ * Valid
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ if ($this->currentComplete) {
+ return true;
+ }
+
+ if ($this->resource instanceof \mysqli_stmt) {
+ return $this->loadDataFromMysqliStatement();
+ }
+
+ return $this->loadFromMysqliResult();
+ }
+
+ /**
+ * Count
+ *
+ * @throws Exception\RuntimeException
+ * @return int
+ */
+ public function count()
+ {
+ if ($this->isBuffered === false) {
+ throw new Exception\RuntimeException('Row count is not available in unbuffered result sets.');
+ }
+ return $this->resource->num_rows;
+ }
+
+ /**
+ * Get field count
+ *
+ * @return int
+ */
+ public function getFieldCount()
+ {
+ return $this->resource->field_count;
+ }
+
+ /**
+ * Get generated value
+ *
+ * @return mixed|null
+ */
+ public function getGeneratedValue()
+ {
+ return $this->generatedValue;
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Mysqli/Statement.php b/library/Zend/Db/Adapter/Driver/Mysqli/Statement.php
new file mode 100755
index 0000000000..d462cd177e
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Mysqli/Statement.php
@@ -0,0 +1,315 @@
+bufferResults = (bool) $bufferResults;
+ }
+
+ /**
+ * Set driver
+ *
+ * @param Mysqli $driver
+ * @return Statement
+ */
+ public function setDriver(Mysqli $driver)
+ {
+ $this->driver = $driver;
+ return $this;
+ }
+
+ /**
+ * @param Profiler\ProfilerInterface $profiler
+ * @return Statement
+ */
+ public function setProfiler(Profiler\ProfilerInterface $profiler)
+ {
+ $this->profiler = $profiler;
+ return $this;
+ }
+
+ /**
+ * @return null|Profiler\ProfilerInterface
+ */
+ public function getProfiler()
+ {
+ return $this->profiler;
+ }
+
+ /**
+ * Initialize
+ *
+ * @param \mysqli $mysqli
+ * @return Statement
+ */
+ public function initialize(\mysqli $mysqli)
+ {
+ $this->mysqli = $mysqli;
+ return $this;
+ }
+
+ /**
+ * Set sql
+ *
+ * @param string $sql
+ * @return Statement
+ */
+ public function setSql($sql)
+ {
+ $this->sql = $sql;
+ return $this;
+ }
+
+ /**
+ * Set Parameter container
+ *
+ * @param ParameterContainer $parameterContainer
+ * @return Statement
+ */
+ public function setParameterContainer(ParameterContainer $parameterContainer)
+ {
+ $this->parameterContainer = $parameterContainer;
+ return $this;
+ }
+
+ /**
+ * Get resource
+ *
+ * @return mixed
+ */
+ public function getResource()
+ {
+ return $this->resource;
+ }
+
+ /**
+ * Set resource
+ *
+ * @param \mysqli_stmt $mysqliStatement
+ * @return Statement
+ */
+ public function setResource(\mysqli_stmt $mysqliStatement)
+ {
+ $this->resource = $mysqliStatement;
+ $this->isPrepared = true;
+ return $this;
+ }
+
+ /**
+ * Get sql
+ *
+ * @return string
+ */
+ public function getSql()
+ {
+ return $this->sql;
+ }
+
+ /**
+ * Get parameter count
+ *
+ * @return ParameterContainer
+ */
+ public function getParameterContainer()
+ {
+ return $this->parameterContainer;
+ }
+
+ /**
+ * Is prepared
+ *
+ * @return bool
+ */
+ public function isPrepared()
+ {
+ return $this->isPrepared;
+ }
+
+ /**
+ * Prepare
+ *
+ * @param string $sql
+ * @throws Exception\InvalidQueryException
+ * @throws Exception\RuntimeException
+ * @return Statement
+ */
+ public function prepare($sql = null)
+ {
+ if ($this->isPrepared) {
+ throw new Exception\RuntimeException('This statement has already been prepared');
+ }
+
+ $sql = ($sql) ?: $this->sql;
+
+ $this->resource = $this->mysqli->prepare($sql);
+ if (!$this->resource instanceof \mysqli_stmt) {
+ throw new Exception\InvalidQueryException(
+ 'Statement couldn\'t be produced with sql: ' . $sql,
+ null,
+ new Exception\ErrorException($this->mysqli->error, $this->mysqli->errno)
+ );
+ }
+
+ $this->isPrepared = true;
+ return $this;
+ }
+
+ /**
+ * Execute
+ *
+ * @param ParameterContainer|array $parameters
+ * @throws Exception\RuntimeException
+ * @return mixed
+ */
+ public function execute($parameters = null)
+ {
+ if (!$this->isPrepared) {
+ $this->prepare();
+ }
+
+ /** START Standard ParameterContainer Merging Block */
+ if (!$this->parameterContainer instanceof ParameterContainer) {
+ if ($parameters instanceof ParameterContainer) {
+ $this->parameterContainer = $parameters;
+ $parameters = null;
+ } else {
+ $this->parameterContainer = new ParameterContainer();
+ }
+ }
+
+ if (is_array($parameters)) {
+ $this->parameterContainer->setFromArray($parameters);
+ }
+
+ if ($this->parameterContainer->count() > 0) {
+ $this->bindParametersFromContainer();
+ }
+ /** END Standard ParameterContainer Merging Block */
+
+ if ($this->profiler) {
+ $this->profiler->profilerStart($this);
+ }
+
+ $return = $this->resource->execute();
+
+ if ($this->profiler) {
+ $this->profiler->profilerFinish();
+ }
+
+ if ($return === false) {
+ throw new Exception\RuntimeException($this->resource->error);
+ }
+
+ if ($this->bufferResults === true) {
+ $this->resource->store_result();
+ $this->isPrepared = false;
+ $buffered = true;
+ } else {
+ $buffered = false;
+ }
+
+ $result = $this->driver->createResult($this->resource, $buffered);
+ return $result;
+ }
+
+ /**
+ * Bind parameters from container
+ *
+ * @return void
+ */
+ protected function bindParametersFromContainer()
+ {
+ $parameters = $this->parameterContainer->getNamedArray();
+ $type = '';
+ $args = array();
+
+ foreach ($parameters as $name => &$value) {
+ if ($this->parameterContainer->offsetHasErrata($name)) {
+ switch ($this->parameterContainer->offsetGetErrata($name)) {
+ case ParameterContainer::TYPE_DOUBLE:
+ $type .= 'd';
+ break;
+ case ParameterContainer::TYPE_NULL:
+ $value = null; // as per @see http://www.php.net/manual/en/mysqli-stmt.bind-param.php#96148
+ case ParameterContainer::TYPE_INTEGER:
+ $type .= 'i';
+ break;
+ case ParameterContainer::TYPE_STRING:
+ default:
+ $type .= 's';
+ break;
+ }
+ } else {
+ $type .= 's';
+ }
+ $args[] = &$value;
+ }
+
+ if ($args) {
+ array_unshift($args, $type);
+ call_user_func_array(array($this->resource, 'bind_param'), $args);
+ }
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Oci8/Connection.php b/library/Zend/Db/Adapter/Driver/Oci8/Connection.php
new file mode 100755
index 0000000000..73376521e2
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Oci8/Connection.php
@@ -0,0 +1,346 @@
+setConnectionParameters($connectionInfo);
+ } elseif ($connectionInfo instanceof \oci8) {
+ $this->setResource($connectionInfo);
+ } elseif (null !== $connectionInfo) {
+ throw new Exception\InvalidArgumentException('$connection must be an array of parameters, an oci8 resource or null');
+ }
+ }
+
+ /**
+ * @param Oci8 $driver
+ * @return Connection
+ */
+ public function setDriver(Oci8 $driver)
+ {
+ $this->driver = $driver;
+ return $this;
+ }
+
+ /**
+ * @param Profiler\ProfilerInterface $profiler
+ * @return Connection
+ */
+ public function setProfiler(Profiler\ProfilerInterface $profiler)
+ {
+ $this->profiler = $profiler;
+ return $this;
+ }
+
+ /**
+ * @return null|Profiler\ProfilerInterface
+ */
+ public function getProfiler()
+ {
+ return $this->profiler;
+ }
+
+ /**
+ * Set connection parameters
+ *
+ * @param array $connectionParameters
+ * @return Connection
+ */
+ public function setConnectionParameters(array $connectionParameters)
+ {
+ $this->connectionParameters = $connectionParameters;
+ return $this;
+ }
+
+ /**
+ * Get connection parameters
+ *
+ * @return array
+ */
+ public function getConnectionParameters()
+ {
+ return $this->connectionParameters;
+ }
+
+ /**
+ * Get current schema
+ *
+ * @return string
+ */
+ public function getCurrentSchema()
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+
+ $query = "SELECT sys_context('USERENV', 'CURRENT_SCHEMA') as \"current_schema\" FROM DUAL";
+ $stmt = oci_parse($this->resource, $query);
+ oci_execute($stmt);
+ $dbNameArray = oci_fetch_array($stmt, OCI_ASSOC);
+ return $dbNameArray['current_schema'];
+ }
+
+ /**
+ * Set resource
+ *
+ * @param resource $resource
+ * @return Connection
+ */
+ public function setResource($resource)
+ {
+ if (!is_resource($resource) || get_resource_type($resource) !== 'oci8 connection') {
+ throw new Exception\InvalidArgumentException('A resource of type "oci8 connection" was expected');
+ }
+ $this->resource = $resource;
+ return $this;
+ }
+
+ /**
+ * Get resource
+ *
+ * @return \oci8
+ */
+ public function getResource()
+ {
+ $this->connect();
+ return $this->resource;
+ }
+
+ /**
+ * Connect
+ *
+ * @return Connection
+ */
+ public function connect()
+ {
+ if (is_resource($this->resource)) {
+ return $this;
+ }
+
+ // localize
+ $p = $this->connectionParameters;
+
+ // given a list of key names, test for existence in $p
+ $findParameterValue = function (array $names) use ($p) {
+ foreach ($names as $name) {
+ if (isset($p[$name])) {
+ return $p[$name];
+ }
+ }
+ return null;
+ };
+
+ // http://www.php.net/manual/en/function.oci-connect.php
+ $username = $findParameterValue(array('username'));
+ $password = $findParameterValue(array('password'));
+ $connectionString = $findParameterValue(array('connection_string', 'connectionstring', 'connection', 'hostname', 'instance'));
+ $characterSet = $findParameterValue(array('character_set', 'charset', 'encoding'));
+ $sessionMode = $findParameterValue(array('session_mode'));
+
+ // connection modifiers
+ $isUnique = $findParameterValue(array('unique'));
+ $isPersistent = $findParameterValue(array('persistent'));
+
+ if ($isUnique == true) {
+ $this->resource = oci_new_connect($username, $password, $connectionString, $characterSet, $sessionMode);
+ } elseif ($isPersistent == true) {
+ $this->resource = oci_pconnect($username, $password, $connectionString, $characterSet, $sessionMode);
+ } else {
+ $this->resource = oci_connect($username, $password, $connectionString, $characterSet, $sessionMode);
+ }
+
+ if (!$this->resource) {
+ $e = oci_error();
+ throw new Exception\RuntimeException(
+ 'Connection error',
+ null,
+ new Exception\ErrorException($e['message'], $e['code'])
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Is connected
+ *
+ * @return bool
+ */
+ public function isConnected()
+ {
+ return (is_resource($this->resource));
+ }
+
+ /**
+ * Disconnect
+ */
+ public function disconnect()
+ {
+ if (is_resource($this->resource)) {
+ oci_close($this->resource);
+ }
+ }
+
+ /**
+ * Begin transaction
+ */
+ public function beginTransaction()
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+
+ // A transaction begins when the first SQL statement that changes data is executed with oci_execute() using the OCI_NO_AUTO_COMMIT flag.
+ $this->inTransaction = true;
+ }
+
+ /**
+ * In transaction
+ *
+ * @return bool
+ */
+ public function inTransaction()
+ {
+ return $this->inTransaction;
+ }
+
+ /**
+ * Commit
+ */
+ public function commit()
+ {
+ if (!$this->resource) {
+ $this->connect();
+ }
+
+ if ($this->inTransaction) {
+ $valid = oci_commit($this->resource);
+ if ($valid === false) {
+ $e = oci_error($this->resource);
+ throw new Exception\InvalidQueryException($e['message'], $e['code']);
+ }
+ }
+ }
+
+ /**
+ * Rollback
+ *
+ * @return Connection
+ */
+ public function rollback()
+ {
+ if (!$this->resource) {
+ throw new Exception\RuntimeException('Must be connected before you can rollback.');
+ }
+
+ if (!$this->inTransaction) {
+ throw new Exception\RuntimeException('Must call commit() before you can rollback.');
+ }
+
+ $valid = oci_rollback($this->resource);
+ if ($valid === false) {
+ $e = oci_error($this->resource);
+ throw new Exception\InvalidQueryException($e['message'], $e['code']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Execute
+ *
+ * @param string $sql
+ * @return Result
+ */
+ public function execute($sql)
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+
+ if ($this->profiler) {
+ $this->profiler->profilerStart($sql);
+ }
+
+ $ociStmt = oci_parse($this->resource, $sql);
+
+ if ($this->inTransaction) {
+ $valid = @oci_execute($ociStmt, OCI_NO_AUTO_COMMIT);
+ } else {
+ $valid = @oci_execute($ociStmt, OCI_COMMIT_ON_SUCCESS);
+ }
+
+ if ($this->profiler) {
+ $this->profiler->profilerFinish($sql);
+ }
+
+ if ($valid === false) {
+ $e = oci_error($ociStmt);
+ throw new Exception\InvalidQueryException($e['message'], $e['code']);
+ }
+
+ $resultPrototype = $this->driver->createResult($ociStmt);
+ return $resultPrototype;
+ }
+
+ /**
+ * Get last generated id
+ *
+ * @param null $name Ignored
+ * @return int
+ */
+ public function getLastGeneratedValue($name = null)
+ {
+ // @todo Get Last Generated Value in Connection (this might not apply)
+ return null;
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Oci8/Oci8.php b/library/Zend/Db/Adapter/Driver/Oci8/Oci8.php
new file mode 100755
index 0000000000..9685f8c41f
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Oci8/Oci8.php
@@ -0,0 +1,223 @@
+registerConnection($connection);
+ $this->registerStatementPrototype(($statementPrototype) ?: new Statement());
+ $this->registerResultPrototype(($resultPrototype) ?: new Result());
+ }
+
+ /**
+ * @param Profiler\ProfilerInterface $profiler
+ * @return Oci8
+ */
+ public function setProfiler(Profiler\ProfilerInterface $profiler)
+ {
+ $this->profiler = $profiler;
+ if ($this->connection instanceof Profiler\ProfilerAwareInterface) {
+ $this->connection->setProfiler($profiler);
+ }
+ if ($this->statementPrototype instanceof Profiler\ProfilerAwareInterface) {
+ $this->statementPrototype->setProfiler($profiler);
+ }
+ return $this;
+ }
+
+ /**
+ * @return null|Profiler\ProfilerInterface
+ */
+ public function getProfiler()
+ {
+ return $this->profiler;
+ }
+
+ /**
+ * Register connection
+ *
+ * @param Connection $connection
+ * @return Oci8
+ */
+ public function registerConnection(Connection $connection)
+ {
+ $this->connection = $connection;
+ $this->connection->setDriver($this); // needs access to driver to createStatement()
+ return $this;
+ }
+
+ /**
+ * Register statement prototype
+ *
+ * @param Statement $statementPrototype
+ * @return Oci8
+ */
+ public function registerStatementPrototype(Statement $statementPrototype)
+ {
+ $this->statementPrototype = $statementPrototype;
+ $this->statementPrototype->setDriver($this); // needs access to driver to createResult()
+ return $this;
+ }
+
+ /**
+ * @return null|Statement
+ */
+ public function getStatementPrototype()
+ {
+ return $this->statementPrototype;
+ }
+
+ /**
+ * Register result prototype
+ *
+ * @param Result $resultPrototype
+ * @return Oci8
+ */
+ public function registerResultPrototype(Result $resultPrototype)
+ {
+ $this->resultPrototype = $resultPrototype;
+ return $this;
+ }
+
+ /**
+ * @return null|Result
+ */
+ public function getResultPrototype()
+ {
+ return $this->resultPrototype;
+ }
+
+ /**
+ * Get database platform name
+ *
+ * @param string $nameFormat
+ * @return string
+ */
+ public function getDatabasePlatformName($nameFormat = self::NAME_FORMAT_CAMELCASE)
+ {
+ return 'Oracle';
+ }
+
+ /**
+ * Check environment
+ */
+ public function checkEnvironment()
+ {
+ if (!extension_loaded('oci8')) {
+ throw new Exception\RuntimeException('The Oci8 extension is required for this adapter but the extension is not loaded');
+ }
+ }
+
+ /**
+ * @return Connection
+ */
+ public function getConnection()
+ {
+ return $this->connection;
+ }
+
+ /**
+ * @param string $sqlOrResource
+ * @return Statement
+ */
+ public function createStatement($sqlOrResource = null)
+ {
+ $statement = clone $this->statementPrototype;
+ if (is_resource($sqlOrResource) && get_resource_type($sqlOrResource) == 'oci8 statement') {
+ $statement->setResource($sqlOrResource);
+ } else {
+ if (is_string($sqlOrResource)) {
+ $statement->setSql($sqlOrResource);
+ } elseif ($sqlOrResource !== null) {
+ throw new Exception\InvalidArgumentException(
+ 'Oci8 only accepts an SQL string or an oci8 resource in ' . __FUNCTION__
+ );
+ }
+ if (!$this->connection->isConnected()) {
+ $this->connection->connect();
+ }
+ $statement->initialize($this->connection->getResource());
+ }
+ return $statement;
+ }
+
+ /**
+ * @param resource $resource
+ * @param null $isBuffered
+ * @return Result
+ */
+ public function createResult($resource, $isBuffered = null)
+ {
+ $result = clone $this->resultPrototype;
+ $result->initialize($resource, $this->connection->getLastGeneratedValue(), $isBuffered);
+ return $result;
+ }
+
+ /**
+ * @return array
+ */
+ public function getPrepareType()
+ {
+ return self::PARAMETERIZATION_NAMED;
+ }
+
+ /**
+ * @param string $name
+ * @param mixed $type
+ * @return string
+ */
+ public function formatParameterName($name, $type = null)
+ {
+ return ':' . $name;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getLastGeneratedValue()
+ {
+ return $this->getConnection()->getLastGeneratedValue();
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Oci8/Result.php b/library/Zend/Db/Adapter/Driver/Oci8/Result.php
new file mode 100755
index 0000000000..f0ae96abd3
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Oci8/Result.php
@@ -0,0 +1,224 @@
+ null, 'values' => array());
+
+ /**
+ * @var mixed
+ */
+ protected $generatedValue = null;
+
+ /**
+ * Initialize
+ * @param resource $resource
+ * @return Result
+ */
+ public function initialize($resource /*, $generatedValue, $isBuffered = null*/)
+ {
+ if (!is_resource($resource) && get_resource_type($resource) !== 'oci8 statement') {
+ throw new Exception\InvalidArgumentException('Invalid resource provided.');
+ }
+ $this->resource = $resource;
+ return $this;
+ }
+
+ /**
+ * Force buffering at driver level
+ *
+ * Oracle does not support this, to my knowledge (@ralphschindler)
+ *
+ * @throws Exception\RuntimeException
+ */
+ public function buffer()
+ {
+ return null;
+ }
+
+ /**
+ * Is the result buffered?
+ *
+ * @return bool
+ */
+ public function isBuffered()
+ {
+ return false;
+ }
+
+ /**
+ * Return the resource
+ * @return mixed
+ */
+ public function getResource()
+ {
+ return $this->resource;
+ }
+
+ /**
+ * Is query result?
+ *
+ * @return bool
+ */
+ public function isQueryResult()
+ {
+ return (oci_num_fields($this->resource) > 0);
+ }
+
+ /**
+ * Get affected rows
+ * @return int
+ */
+ public function getAffectedRows()
+ {
+ return oci_num_rows($this->resource);
+ }
+
+ /**
+ * Current
+ * @return mixed
+ */
+ public function current()
+ {
+ if ($this->currentComplete == false) {
+ if ($this->loadData() === false) {
+ return false;
+ }
+ }
+
+ return $this->currentData;
+ }
+
+ /**
+ * Load from oci8 result
+ *
+ * @return bool
+ */
+ protected function loadData()
+ {
+ $this->currentComplete = true;
+ $this->currentData = oci_fetch_assoc($this->resource);
+
+ if ($this->currentData !== false) {
+ $this->position++;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Next
+ */
+ public function next()
+ {
+ return $this->loadData();
+ }
+
+ /**
+ * Key
+ * @return mixed
+ */
+ public function key()
+ {
+ return $this->position;
+ }
+
+ /**
+ * Rewind
+ */
+ public function rewind()
+ {
+ if ($this->position > 0) {
+ throw new Exception\RuntimeException('Oci8 results cannot be rewound for multiple iterations');
+ }
+ }
+
+ /**
+ * Valid
+ * @return bool
+ */
+ public function valid()
+ {
+ if ($this->currentComplete) {
+ return ($this->currentData !== false);
+ }
+
+ return $this->loadData();
+ }
+
+ /**
+ * Count
+ * @return int
+ */
+ public function count()
+ {
+ // @todo OCI8 row count in Driver Result
+ return null;
+ }
+
+ /**
+ * @return int
+ */
+ public function getFieldCount()
+ {
+ return oci_num_fields($this->resource);
+ }
+
+ /**
+ * @return mixed|null
+ */
+ public function getGeneratedValue()
+ {
+ // @todo OCI8 generated value in Driver Result
+ return null;
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Oci8/Statement.php b/library/Zend/Db/Adapter/Driver/Oci8/Statement.php
new file mode 100755
index 0000000000..707442fdeb
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Oci8/Statement.php
@@ -0,0 +1,311 @@
+driver = $driver;
+ return $this;
+ }
+
+ /**
+ * @param Profiler\ProfilerInterface $profiler
+ * @return Statement
+ */
+ public function setProfiler(Profiler\ProfilerInterface $profiler)
+ {
+ $this->profiler = $profiler;
+ return $this;
+ }
+
+ /**
+ * @return null|Profiler\ProfilerInterface
+ */
+ public function getProfiler()
+ {
+ return $this->profiler;
+ }
+
+ /**
+ * Initialize
+ *
+ * @param resource $oci8
+ * @return Statement
+ */
+ public function initialize($oci8)
+ {
+ $this->oci8 = $oci8;
+ return $this;
+ }
+
+ /**
+ * Set sql
+ *
+ * @param string $sql
+ * @return Statement
+ */
+ public function setSql($sql)
+ {
+ $this->sql = $sql;
+ return $this;
+ }
+
+ /**
+ * Set Parameter container
+ *
+ * @param ParameterContainer $parameterContainer
+ * @return Statement
+ */
+ public function setParameterContainer(ParameterContainer $parameterContainer)
+ {
+ $this->parameterContainer = $parameterContainer;
+ return $this;
+ }
+
+ /**
+ * Get resource
+ *
+ * @return mixed
+ */
+ public function getResource()
+ {
+ return $this->resource;
+ }
+
+ /**
+ * Set resource
+ *
+ * @param resource $oci8Statement
+ * @return Statement
+ */
+ public function setResource($oci8Statement)
+ {
+ $type = oci_statement_type($oci8Statement);
+ if (false === $type || 'UNKNOWN' == $type) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Invalid statement provided to %s',
+ __METHOD__
+ ));
+ }
+ $this->resource = $oci8Statement;
+ $this->isPrepared = true;
+ return $this;
+ }
+
+ /**
+ * Get sql
+ *
+ * @return string
+ */
+ public function getSql()
+ {
+ return $this->sql;
+ }
+
+ /**
+ * @return ParameterContainer
+ */
+ public function getParameterContainer()
+ {
+ return $this->parameterContainer;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isPrepared()
+ {
+ return $this->isPrepared;
+ }
+
+ /**
+ * @param string $sql
+ * @return Statement
+ */
+ public function prepare($sql = null)
+ {
+ if ($this->isPrepared) {
+ throw new Exception\RuntimeException('This statement has already been prepared');
+ }
+
+ $sql = ($sql) ?: $this->sql;
+
+ // get oci8 statement resource
+ $this->resource = oci_parse($this->oci8, $sql);
+
+ if (!$this->resource) {
+ $e = oci_error($this->oci8);
+ throw new Exception\InvalidQueryException(
+ 'Statement couldn\'t be produced with sql: ' . $sql,
+ null,
+ new Exception\ErrorException($e['message'], $e['code'])
+ );
+ }
+
+ $this->isPrepared = true;
+ return $this;
+ }
+
+ /**
+ * Execute
+ *
+ * @param ParameterContainer $parameters
+ * @return mixed
+ */
+ public function execute($parameters = null)
+ {
+ if (!$this->isPrepared) {
+ $this->prepare();
+ }
+
+ /** START Standard ParameterContainer Merging Block */
+ if (!$this->parameterContainer instanceof ParameterContainer) {
+ if ($parameters instanceof ParameterContainer) {
+ $this->parameterContainer = $parameters;
+ $parameters = null;
+ } else {
+ $this->parameterContainer = new ParameterContainer();
+ }
+ }
+
+ if (is_array($parameters)) {
+ $this->parameterContainer->setFromArray($parameters);
+ }
+
+ if ($this->parameterContainer->count() > 0) {
+ $this->bindParametersFromContainer();
+ }
+ /** END Standard ParameterContainer Merging Block */
+
+ if ($this->profiler) {
+ $this->profiler->profilerStart($this);
+ }
+
+ if ($this->driver->getConnection()->inTransaction()) {
+ $ret = @oci_execute($this->resource, OCI_NO_AUTO_COMMIT);
+ } else {
+ $ret = @oci_execute($this->resource, OCI_COMMIT_ON_SUCCESS);
+ }
+
+ if ($this->profiler) {
+ $this->profiler->profilerFinish();
+ }
+
+ if ($ret === false) {
+ $e = oci_error($this->resource);
+ throw new Exception\RuntimeException($e['message'], $e['code']);
+ }
+
+ $result = $this->driver->createResult($this->resource);
+ return $result;
+ }
+
+ /**
+ * Bind parameters from container
+ *
+ * @param ParameterContainer $pContainer
+ */
+ protected function bindParametersFromContainer()
+ {
+ $parameters = $this->parameterContainer->getNamedArray();
+
+ foreach ($parameters as $name => &$value) {
+ if ($this->parameterContainer->offsetHasErrata($name)) {
+ switch ($this->parameterContainer->offsetGetErrata($name)) {
+ case ParameterContainer::TYPE_NULL:
+ $type = null;
+ $value = null;
+ break;
+ case ParameterContainer::TYPE_DOUBLE:
+ case ParameterContainer::TYPE_INTEGER:
+ $type = SQLT_INT;
+ if (is_string($value)) {
+ $value = (int) $value;
+ }
+ break;
+ case ParameterContainer::TYPE_BINARY:
+ $type = SQLT_BIN;
+ break;
+ case ParameterContainer::TYPE_LOB:
+ $type = OCI_B_CLOB;
+ $clob = oci_new_descriptor($this->driver->getConnection()->getResource(), OCI_DTYPE_LOB);
+ $clob->writetemporary($value, OCI_TEMP_CLOB);
+ $value = $clob;
+ break;
+ case ParameterContainer::TYPE_STRING:
+ default:
+ $type = SQLT_CHR;
+ break;
+ }
+ } else {
+ $type = SQLT_CHR;
+ }
+
+ oci_bind_by_name($this->resource, $name, $value, -1, $type);
+ }
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Pdo/Connection.php b/library/Zend/Db/Adapter/Driver/Pdo/Connection.php
new file mode 100755
index 0000000000..1cd2c66675
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Pdo/Connection.php
@@ -0,0 +1,488 @@
+setConnectionParameters($connectionParameters);
+ } elseif ($connectionParameters instanceof \PDO) {
+ $this->setResource($connectionParameters);
+ } elseif (null !== $connectionParameters) {
+ throw new Exception\InvalidArgumentException('$connection must be an array of parameters, a PDO object or null');
+ }
+ }
+
+ /**
+ * Set driver
+ *
+ * @param Pdo $driver
+ * @return Connection
+ */
+ public function setDriver(Pdo $driver)
+ {
+ $this->driver = $driver;
+ return $this;
+ }
+
+ /**
+ * @param Profiler\ProfilerInterface $profiler
+ * @return Connection
+ */
+ public function setProfiler(Profiler\ProfilerInterface $profiler)
+ {
+ $this->profiler = $profiler;
+ return $this;
+ }
+
+ /**
+ * @return null|Profiler\ProfilerInterface
+ */
+ public function getProfiler()
+ {
+ return $this->profiler;
+ }
+
+ /**
+ * Get driver name
+ *
+ * @return null|string
+ */
+ public function getDriverName()
+ {
+ return $this->driverName;
+ }
+
+ /**
+ * Set connection parameters
+ *
+ * @param array $connectionParameters
+ * @return void
+ */
+ public function setConnectionParameters(array $connectionParameters)
+ {
+ $this->connectionParameters = $connectionParameters;
+ if (isset($connectionParameters['dsn'])) {
+ $this->driverName = substr($connectionParameters['dsn'], 0,
+ strpos($connectionParameters['dsn'], ':')
+ );
+ } elseif (isset($connectionParameters['pdodriver'])) {
+ $this->driverName = strtolower($connectionParameters['pdodriver']);
+ } elseif (isset($connectionParameters['driver'])) {
+ $this->driverName = strtolower(substr(
+ str_replace(array('-', '_', ' '), '', $connectionParameters['driver']),
+ 3
+ ));
+ }
+ }
+
+ /**
+ * Get connection parameters
+ *
+ * @return array
+ */
+ public function getConnectionParameters()
+ {
+ return $this->connectionParameters;
+ }
+
+ /**
+ * Get the dsn string for this connection
+ * @throws \Zend\Db\Adapter\Exception\RunTimeException
+ * @return string
+ */
+ public function getDsn()
+ {
+ if (!$this->dsn) {
+ throw new Exception\RunTimeException("The DSN has not been set or constructed from parameters in connect() for this Connection");
+ }
+
+ return $this->dsn;
+ }
+
+ /**
+ * Get current schema
+ *
+ * @return string
+ */
+ public function getCurrentSchema()
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+
+ switch ($this->driverName) {
+ case 'mysql':
+ $sql = 'SELECT DATABASE()';
+ break;
+ case 'sqlite':
+ return 'main';
+ case 'sqlsrv':
+ case 'dblib':
+ $sql = 'SELECT SCHEMA_NAME()';
+ break;
+ case 'pgsql':
+ default:
+ $sql = 'SELECT CURRENT_SCHEMA';
+ break;
+ }
+
+ /** @var $result \PDOStatement */
+ $result = $this->resource->query($sql);
+ if ($result instanceof \PDOStatement) {
+ return $result->fetchColumn();
+ }
+ return false;
+ }
+
+ /**
+ * Set resource
+ *
+ * @param \PDO $resource
+ * @return Connection
+ */
+ public function setResource(\PDO $resource)
+ {
+ $this->resource = $resource;
+ $this->driverName = strtolower($this->resource->getAttribute(\PDO::ATTR_DRIVER_NAME));
+ return $this;
+ }
+
+ /**
+ * Get resource
+ *
+ * @return \PDO
+ */
+ public function getResource()
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+ return $this->resource;
+ }
+
+ /**
+ * Connect
+ *
+ * @return Connection
+ * @throws Exception\InvalidConnectionParametersException
+ * @throws Exception\RuntimeException
+ */
+ public function connect()
+ {
+ if ($this->resource) {
+ return $this;
+ }
+
+ $dsn = $username = $password = $hostname = $database = null;
+ $options = array();
+ foreach ($this->connectionParameters as $key => $value) {
+ switch (strtolower($key)) {
+ case 'dsn':
+ $dsn = $value;
+ break;
+ case 'driver':
+ $value = strtolower($value);
+ if (strpos($value, 'pdo') === 0) {
+ $pdoDriver = strtolower(substr(str_replace(array('-', '_', ' '), '', $value), 3));
+ }
+ break;
+ case 'pdodriver':
+ $pdoDriver = (string) $value;
+ break;
+ case 'user':
+ case 'username':
+ $username = (string) $value;
+ break;
+ case 'pass':
+ case 'password':
+ $password = (string) $value;
+ break;
+ case 'host':
+ case 'hostname':
+ $hostname = (string) $value;
+ break;
+ case 'port':
+ $port = (int) $value;
+ break;
+ case 'database':
+ case 'dbname':
+ $database = (string) $value;
+ break;
+ case 'charset':
+ $charset = (string) $value;
+ break;
+ case 'driver_options':
+ case 'options':
+ $value = (array) $value;
+ $options = array_diff_key($options, $value) + $value;
+ break;
+ default:
+ $options[$key] = $value;
+ break;
+ }
+ }
+
+ if (!isset($dsn) && isset($pdoDriver)) {
+ $dsn = array();
+ switch ($pdoDriver) {
+ case 'sqlite':
+ $dsn[] = $database;
+ break;
+ case 'sqlsrv':
+ if (isset($database)) {
+ $dsn[] = "database={$database}";
+ }
+ if (isset($hostname)) {
+ $dsn[] = "server={$hostname}";
+ }
+ break;
+ default:
+ if (isset($database)) {
+ $dsn[] = "dbname={$database}";
+ }
+ if (isset($hostname)) {
+ $dsn[] = "host={$hostname}";
+ }
+ if (isset($port)) {
+ $dsn[] = "port={$port}";
+ }
+ if (isset($charset) && $pdoDriver != 'pgsql') {
+ $dsn[] = "charset={$charset}";
+ }
+ break;
+ }
+ $dsn = $pdoDriver . ':' . implode(';', $dsn);
+ } elseif (!isset($dsn)) {
+ throw new Exception\InvalidConnectionParametersException(
+ 'A dsn was not provided or could not be constructed from your parameters',
+ $this->connectionParameters
+ );
+ }
+
+ $this->dsn = $dsn;
+
+ try {
+ $this->resource = new \PDO($dsn, $username, $password, $options);
+ $this->resource->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+ if (isset($charset) && $pdoDriver == 'pgsql') {
+ $this->resource->exec('SET NAMES ' . $this->resource->quote($charset));
+ }
+ $this->driverName = strtolower($this->resource->getAttribute(\PDO::ATTR_DRIVER_NAME));
+ } catch (\PDOException $e) {
+ $code = $e->getCode();
+ if (!is_long($code)) {
+ $code = null;
+ }
+ throw new Exception\RuntimeException('Connect Error: ' . $e->getMessage(), $code, $e);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Is connected
+ *
+ * @return bool
+ */
+ public function isConnected()
+ {
+ return ($this->resource instanceof \PDO);
+ }
+
+ /**
+ * Disconnect
+ *
+ * @return Connection
+ */
+ public function disconnect()
+ {
+ if ($this->isConnected()) {
+ $this->resource = null;
+ }
+ return $this;
+ }
+
+ /**
+ * Begin transaction
+ *
+ * @return Connection
+ */
+ public function beginTransaction()
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+ $this->resource->beginTransaction();
+ $this->inTransaction = true;
+ return $this;
+ }
+
+ /**
+ * In transaction
+ *
+ * @return bool
+ */
+ public function inTransaction()
+ {
+ return $this->inTransaction;
+ }
+
+ /**
+ * Commit
+ *
+ * @return Connection
+ */
+ public function commit()
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+
+ $this->resource->commit();
+ $this->inTransaction = false;
+ return $this;
+ }
+
+ /**
+ * Rollback
+ *
+ * @return Connection
+ * @throws Exception\RuntimeException
+ */
+ public function rollback()
+ {
+ if (!$this->isConnected()) {
+ throw new Exception\RuntimeException('Must be connected before you can rollback');
+ }
+
+ if (!$this->inTransaction) {
+ throw new Exception\RuntimeException('Must call beginTransaction() before you can rollback');
+ }
+
+ $this->resource->rollBack();
+ return $this;
+ }
+
+ /**
+ * Execute
+ *
+ * @param $sql
+ * @return Result
+ * @throws Exception\InvalidQueryException
+ */
+ public function execute($sql)
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+
+ if ($this->profiler) {
+ $this->profiler->profilerStart($sql);
+ }
+
+ $resultResource = $this->resource->query($sql);
+
+ if ($this->profiler) {
+ $this->profiler->profilerFinish($sql);
+ }
+
+ if ($resultResource === false) {
+ $errorInfo = $this->resource->errorInfo();
+ throw new Exception\InvalidQueryException($errorInfo[2]);
+ }
+
+ $result = $this->driver->createResult($resultResource, $sql);
+ return $result;
+ }
+
+ /**
+ * Prepare
+ *
+ * @param string $sql
+ * @return Statement
+ */
+ public function prepare($sql)
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+
+ $statement = $this->driver->createStatement($sql);
+ return $statement;
+ }
+
+ /**
+ * Get last generated id
+ *
+ * @param string $name
+ * @return string|null|false
+ */
+ public function getLastGeneratedValue($name = null)
+ {
+ if ($name === null && $this->driverName == 'pgsql') {
+ return null;
+ }
+
+ try {
+ return $this->resource->lastInsertId($name);
+ } catch (\Exception $e) {
+ // do nothing
+ }
+ return false;
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Pdo/Feature/OracleRowCounter.php b/library/Zend/Db/Adapter/Driver/Pdo/Feature/OracleRowCounter.php
new file mode 100755
index 0000000000..2a25cdd6bf
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Pdo/Feature/OracleRowCounter.php
@@ -0,0 +1,78 @@
+getSql();
+ if ($sql == '' || stripos($sql, 'select') === false) {
+ return null;
+ }
+ $countSql = 'SELECT COUNT(*) as "count" FROM (' . $sql . ')';
+ $countStmt->prepare($countSql);
+ $result = $countStmt->execute();
+ $countRow = $result->getResource()->fetch(\PDO::FETCH_ASSOC);
+ unset($statement, $result);
+ return $countRow['count'];
+ }
+
+ /**
+ * @param $sql
+ * @return null|int
+ */
+ public function getCountForSql($sql)
+ {
+ if (stripos($sql, 'select') === false) {
+ return null;
+ }
+ $countSql = 'SELECT COUNT(*) as count FROM (' . $sql . ')';
+ /** @var $pdo \PDO */
+ $pdo = $this->driver->getConnection()->getResource();
+ $result = $pdo->query($countSql);
+ $countRow = $result->fetch(\PDO::FETCH_ASSOC);
+ return $countRow['count'];
+ }
+
+ /**
+ * @param $context
+ * @return \Closure
+ */
+ public function getRowCountClosure($context)
+ {
+ $oracleRowCounter = $this;
+ return function () use ($oracleRowCounter, $context) {
+ /** @var $oracleRowCounter OracleRowCounter */
+ return ($context instanceof Pdo\Statement)
+ ? $oracleRowCounter->getCountForStatement($context)
+ : $oracleRowCounter->getCountForSql($context);
+ };
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Pdo/Feature/SqliteRowCounter.php b/library/Zend/Db/Adapter/Driver/Pdo/Feature/SqliteRowCounter.php
new file mode 100755
index 0000000000..13c8d66d41
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Pdo/Feature/SqliteRowCounter.php
@@ -0,0 +1,78 @@
+getSql();
+ if ($sql == '' || stripos($sql, 'select') === false) {
+ return null;
+ }
+ $countSql = 'SELECT COUNT(*) as "count" FROM (' . $sql . ')';
+ $countStmt->prepare($countSql);
+ $result = $countStmt->execute();
+ $countRow = $result->getResource()->fetch(\PDO::FETCH_ASSOC);
+ unset($statement, $result);
+ return $countRow['count'];
+ }
+
+ /**
+ * @param $sql
+ * @return null|int
+ */
+ public function getCountForSql($sql)
+ {
+ if (stripos($sql, 'select') === false) {
+ return null;
+ }
+ $countSql = 'SELECT COUNT(*) as count FROM (' . $sql . ')';
+ /** @var $pdo \PDO */
+ $pdo = $this->driver->getConnection()->getResource();
+ $result = $pdo->query($countSql);
+ $countRow = $result->fetch(\PDO::FETCH_ASSOC);
+ return $countRow['count'];
+ }
+
+ /**
+ * @param $context
+ * @return \Closure
+ */
+ public function getRowCountClosure($context)
+ {
+ $sqliteRowCounter = $this;
+ return function () use ($sqliteRowCounter, $context) {
+ /** @var $sqliteRowCounter SqliteRowCounter */
+ return ($context instanceof Pdo\Statement)
+ ? $sqliteRowCounter->getCountForStatement($context)
+ : $sqliteRowCounter->getCountForSql($context);
+ };
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Pdo/Pdo.php b/library/Zend/Db/Adapter/Driver/Pdo/Pdo.php
new file mode 100755
index 0000000000..3de7beb498
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Pdo/Pdo.php
@@ -0,0 +1,314 @@
+registerConnection($connection);
+ $this->registerStatementPrototype(($statementPrototype) ?: new Statement());
+ $this->registerResultPrototype(($resultPrototype) ?: new Result());
+ if (is_array($features)) {
+ foreach ($features as $name => $feature) {
+ $this->addFeature($name, $feature);
+ }
+ } elseif ($features instanceof AbstractFeature) {
+ $this->addFeature($features->getName(), $features);
+ } elseif ($features === self::FEATURES_DEFAULT) {
+ $this->setupDefaultFeatures();
+ }
+ }
+
+ /**
+ * @param Profiler\ProfilerInterface $profiler
+ * @return Pdo
+ */
+ public function setProfiler(Profiler\ProfilerInterface $profiler)
+ {
+ $this->profiler = $profiler;
+ if ($this->connection instanceof Profiler\ProfilerAwareInterface) {
+ $this->connection->setProfiler($profiler);
+ }
+ if ($this->statementPrototype instanceof Profiler\ProfilerAwareInterface) {
+ $this->statementPrototype->setProfiler($profiler);
+ }
+ return $this;
+ }
+
+ /**
+ * @return null|Profiler\ProfilerInterface
+ */
+ public function getProfiler()
+ {
+ return $this->profiler;
+ }
+
+ /**
+ * Register connection
+ *
+ * @param Connection $connection
+ * @return Pdo
+ */
+ public function registerConnection(Connection $connection)
+ {
+ $this->connection = $connection;
+ $this->connection->setDriver($this);
+ return $this;
+ }
+
+ /**
+ * Register statement prototype
+ *
+ * @param Statement $statementPrototype
+ */
+ public function registerStatementPrototype(Statement $statementPrototype)
+ {
+ $this->statementPrototype = $statementPrototype;
+ $this->statementPrototype->setDriver($this);
+ }
+
+ /**
+ * Register result prototype
+ *
+ * @param Result $resultPrototype
+ */
+ public function registerResultPrototype(Result $resultPrototype)
+ {
+ $this->resultPrototype = $resultPrototype;
+ }
+
+ /**
+ * Add feature
+ *
+ * @param string $name
+ * @param AbstractFeature $feature
+ * @return Pdo
+ */
+ public function addFeature($name, $feature)
+ {
+ if ($feature instanceof AbstractFeature) {
+ $name = $feature->getName(); // overwrite the name, just in case
+ $feature->setDriver($this);
+ }
+ $this->features[$name] = $feature;
+ return $this;
+ }
+
+ /**
+ * Setup the default features for Pdo
+ *
+ * @return Pdo
+ */
+ public function setupDefaultFeatures()
+ {
+ $driverName = $this->connection->getDriverName();
+ if ($driverName == 'sqlite') {
+ $this->addFeature(null, new Feature\SqliteRowCounter);
+ } elseif ($driverName == 'oci') {
+ $this->addFeature(null, new Feature\OracleRowCounter);
+ }
+ return $this;
+ }
+
+ /**
+ * Get feature
+ *
+ * @param $name
+ * @return AbstractFeature|false
+ */
+ public function getFeature($name)
+ {
+ if (isset($this->features[$name])) {
+ return $this->features[$name];
+ }
+ return false;
+ }
+
+ /**
+ * Get database platform name
+ *
+ * @param string $nameFormat
+ * @return string
+ */
+ public function getDatabasePlatformName($nameFormat = self::NAME_FORMAT_CAMELCASE)
+ {
+ $name = $this->getConnection()->getDriverName();
+ if ($nameFormat == self::NAME_FORMAT_CAMELCASE) {
+ switch ($name) {
+ case 'pgsql':
+ return 'Postgresql';
+ case 'oci':
+ return 'Oracle';
+ case 'dblib':
+ case 'sqlsrv':
+ return 'SqlServer';
+ default:
+ return ucfirst($name);
+ }
+ } else {
+ switch ($name) {
+ case 'sqlite':
+ return 'SQLite';
+ case 'mysql':
+ return 'MySQL';
+ case 'pgsql':
+ return 'PostgreSQL';
+ case 'oci':
+ return 'Oracle';
+ case 'dblib':
+ case 'sqlsrv':
+ return 'SQLServer';
+ default:
+ return ucfirst($name);
+ }
+ }
+ }
+
+ /**
+ * Check environment
+ */
+ public function checkEnvironment()
+ {
+ if (!extension_loaded('PDO')) {
+ throw new Exception\RuntimeException('The PDO extension is required for this adapter but the extension is not loaded');
+ }
+ }
+
+ /**
+ * @return Connection
+ */
+ public function getConnection()
+ {
+ return $this->connection;
+ }
+
+ /**
+ * @param string|PDOStatement $sqlOrResource
+ * @return Statement
+ */
+ public function createStatement($sqlOrResource = null)
+ {
+ $statement = clone $this->statementPrototype;
+ if ($sqlOrResource instanceof PDOStatement) {
+ $statement->setResource($sqlOrResource);
+ } else {
+ if (is_string($sqlOrResource)) {
+ $statement->setSql($sqlOrResource);
+ }
+ if (!$this->connection->isConnected()) {
+ $this->connection->connect();
+ }
+ $statement->initialize($this->connection->getResource());
+ }
+ return $statement;
+ }
+
+ /**
+ * @param resource $resource
+ * @param mixed $context
+ * @return Result
+ */
+ public function createResult($resource, $context = null)
+ {
+ $result = clone $this->resultPrototype;
+ $rowCount = null;
+
+ // special feature, sqlite PDO counter
+ if ($this->connection->getDriverName() == 'sqlite'
+ && ($sqliteRowCounter = $this->getFeature('SqliteRowCounter'))
+ && $resource->columnCount() > 0) {
+ $rowCount = $sqliteRowCounter->getRowCountClosure($context);
+ }
+
+ // special feature, oracle PDO counter
+ if ($this->connection->getDriverName() == 'oci'
+ && ($oracleRowCounter = $this->getFeature('OracleRowCounter'))
+ && $resource->columnCount() > 0) {
+ $rowCount = $oracleRowCounter->getRowCountClosure($context);
+ }
+
+
+ $result->initialize($resource, $this->connection->getLastGeneratedValue(), $rowCount);
+ return $result;
+ }
+
+ /**
+ * @return array
+ */
+ public function getPrepareType()
+ {
+ return self::PARAMETERIZATION_NAMED;
+ }
+
+ /**
+ * @param string $name
+ * @param string|null $type
+ * @return string
+ */
+ public function formatParameterName($name, $type = null)
+ {
+ if ($type == null && !is_numeric($name) || $type == self::PARAMETERIZATION_NAMED) {
+ return ':' . $name;
+ }
+
+ return '?';
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getLastGeneratedValue($name = null)
+ {
+ return $this->connection->getLastGeneratedValue($name);
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Pdo/Result.php b/library/Zend/Db/Adapter/Driver/Pdo/Result.php
new file mode 100755
index 0000000000..9323282d6f
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Pdo/Result.php
@@ -0,0 +1,226 @@
+resource = $resource;
+ $this->generatedValue = $generatedValue;
+ $this->rowCount = $rowCount;
+
+ return $this;
+ }
+
+ /**
+ * @return null
+ */
+ public function buffer()
+ {
+ return null;
+ }
+
+ /**
+ * @return bool|null
+ */
+ public function isBuffered()
+ {
+ return false;
+ }
+
+ /**
+ * Get resource
+ *
+ * @return mixed
+ */
+ public function getResource()
+ {
+ return $this->resource;
+ }
+
+ /**
+ * Get the data
+ * @return array
+ */
+ public function current()
+ {
+ if ($this->currentComplete) {
+ return $this->currentData;
+ }
+
+ $this->currentData = $this->resource->fetch(\PDO::FETCH_ASSOC);
+ $this->currentComplete = true;
+ return $this->currentData;
+ }
+
+ /**
+ * Next
+ *
+ * @return mixed
+ */
+ public function next()
+ {
+ $this->currentData = $this->resource->fetch(\PDO::FETCH_ASSOC);
+ $this->currentComplete = true;
+ $this->position++;
+ return $this->currentData;
+ }
+
+ /**
+ * Key
+ *
+ * @return mixed
+ */
+ public function key()
+ {
+ return $this->position;
+ }
+
+ /**
+ * @throws Exception\RuntimeException
+ * @return void
+ */
+ public function rewind()
+ {
+ if ($this->statementMode == self::STATEMENT_MODE_FORWARD && $this->position > 0) {
+ throw new Exception\RuntimeException(
+ 'This result is a forward only result set, calling rewind() after moving forward is not supported'
+ );
+ }
+ $this->currentData = $this->resource->fetch(\PDO::FETCH_ASSOC);
+ $this->currentComplete = true;
+ $this->position = 0;
+ }
+
+ /**
+ * Valid
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return ($this->currentData !== false);
+ }
+
+ /**
+ * Count
+ *
+ * @return int
+ */
+ public function count()
+ {
+ if (is_int($this->rowCount)) {
+ return $this->rowCount;
+ }
+ if ($this->rowCount instanceof \Closure) {
+ $this->rowCount = (int) call_user_func($this->rowCount);
+ } else {
+ $this->rowCount = (int) $this->resource->rowCount();
+ }
+ return $this->rowCount;
+ }
+
+ /**
+ * @return int
+ */
+ public function getFieldCount()
+ {
+ return $this->resource->columnCount();
+ }
+
+ /**
+ * Is query result
+ *
+ * @return bool
+ */
+ public function isQueryResult()
+ {
+ return ($this->resource->columnCount() > 0);
+ }
+
+ /**
+ * Get affected rows
+ *
+ * @return int
+ */
+ public function getAffectedRows()
+ {
+ return $this->resource->rowCount();
+ }
+
+ /**
+ * @return mixed|null
+ */
+ public function getGeneratedValue()
+ {
+ return $this->generatedValue;
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Pdo/Statement.php b/library/Zend/Db/Adapter/Driver/Pdo/Statement.php
new file mode 100755
index 0000000000..891bec9d7e
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Pdo/Statement.php
@@ -0,0 +1,310 @@
+driver = $driver;
+ return $this;
+ }
+
+ /**
+ * @param Profiler\ProfilerInterface $profiler
+ * @return Statement
+ */
+ public function setProfiler(Profiler\ProfilerInterface $profiler)
+ {
+ $this->profiler = $profiler;
+ return $this;
+ }
+
+ /**
+ * @return null|Profiler\ProfilerInterface
+ */
+ public function getProfiler()
+ {
+ return $this->profiler;
+ }
+
+ /**
+ * Initialize
+ *
+ * @param \PDO $connectionResource
+ * @return Statement
+ */
+ public function initialize(\PDO $connectionResource)
+ {
+ $this->pdo = $connectionResource;
+ return $this;
+ }
+
+ /**
+ * Set resource
+ *
+ * @param \PDOStatement $pdoStatement
+ * @return Statement
+ */
+ public function setResource(\PDOStatement $pdoStatement)
+ {
+ $this->resource = $pdoStatement;
+ return $this;
+ }
+
+ /**
+ * Get resource
+ *
+ * @return mixed
+ */
+ public function getResource()
+ {
+ return $this->resource;
+ }
+
+ /**
+ * Set sql
+ *
+ * @param string $sql
+ * @return Statement
+ */
+ public function setSql($sql)
+ {
+ $this->sql = $sql;
+ return $this;
+ }
+
+ /**
+ * Get sql
+ *
+ * @return string
+ */
+ public function getSql()
+ {
+ return $this->sql;
+ }
+
+ /**
+ * @param ParameterContainer $parameterContainer
+ * @return Statement
+ */
+ public function setParameterContainer(ParameterContainer $parameterContainer)
+ {
+ $this->parameterContainer = $parameterContainer;
+ return $this;
+ }
+
+ /**
+ * @return ParameterContainer
+ */
+ public function getParameterContainer()
+ {
+ return $this->parameterContainer;
+ }
+
+ /**
+ * @param string $sql
+ * @throws Exception\RuntimeException
+ */
+ public function prepare($sql = null)
+ {
+ if ($this->isPrepared) {
+ throw new Exception\RuntimeException('This statement has been prepared already');
+ }
+
+ if ($sql == null) {
+ $sql = $this->sql;
+ }
+
+ $this->resource = $this->pdo->prepare($sql);
+
+ if ($this->resource === false) {
+ $error = $this->pdo->errorInfo();
+ throw new Exception\RuntimeException($error[2]);
+ }
+
+ $this->isPrepared = true;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isPrepared()
+ {
+ return $this->isPrepared;
+ }
+
+ /**
+ * @param mixed $parameters
+ * @throws Exception\InvalidQueryException
+ * @return Result
+ */
+ public function execute($parameters = null)
+ {
+ if (!$this->isPrepared) {
+ $this->prepare();
+ }
+
+ /** START Standard ParameterContainer Merging Block */
+ if (!$this->parameterContainer instanceof ParameterContainer) {
+ if ($parameters instanceof ParameterContainer) {
+ $this->parameterContainer = $parameters;
+ $parameters = null;
+ } else {
+ $this->parameterContainer = new ParameterContainer();
+ }
+ }
+
+ if (is_array($parameters)) {
+ $this->parameterContainer->setFromArray($parameters);
+ }
+
+ if ($this->parameterContainer->count() > 0) {
+ $this->bindParametersFromContainer();
+ }
+ /** END Standard ParameterContainer Merging Block */
+
+ if ($this->profiler) {
+ $this->profiler->profilerStart($this);
+ }
+
+ try {
+ $this->resource->execute();
+ } catch (\PDOException $e) {
+ if ($this->profiler) {
+ $this->profiler->profilerFinish();
+ }
+ throw new Exception\InvalidQueryException(
+ 'Statement could not be executed (' . implode(' - ', $this->resource->errorInfo()) . ')',
+ null,
+ $e
+ );
+ }
+
+ if ($this->profiler) {
+ $this->profiler->profilerFinish();
+ }
+
+ $result = $this->driver->createResult($this->resource, $this);
+ return $result;
+ }
+
+ /**
+ * Bind parameters from container
+ */
+ protected function bindParametersFromContainer()
+ {
+ if ($this->parametersBound) {
+ return;
+ }
+
+ $parameters = $this->parameterContainer->getNamedArray();
+ foreach ($parameters as $name => &$value) {
+ if (is_bool($value)) {
+ $type = \PDO::PARAM_BOOL;
+ } elseif (is_int($value)) {
+ $type = \PDO::PARAM_INT;
+ } else {
+ $type = \PDO::PARAM_STR;
+ }
+ if ($this->parameterContainer->offsetHasErrata($name)) {
+ switch ($this->parameterContainer->offsetGetErrata($name)) {
+ case ParameterContainer::TYPE_INTEGER:
+ $type = \PDO::PARAM_INT;
+ break;
+ case ParameterContainer::TYPE_NULL:
+ $type = \PDO::PARAM_NULL;
+ break;
+ case ParameterContainer::TYPE_LOB:
+ $type = \PDO::PARAM_LOB;
+ break;
+ }
+ }
+
+ // parameter is named or positional, value is reference
+ $parameter = is_int($name) ? ($name + 1) : $name;
+ $this->resource->bindParam($parameter, $value, $type);
+ }
+ }
+
+ /**
+ * Perform a deep clone
+ * @return Statement A cloned statement
+ */
+ public function __clone()
+ {
+ $this->isPrepared = false;
+ $this->parametersBound = false;
+ $this->resource = null;
+ if ($this->parameterContainer) {
+ $this->parameterContainer = clone $this->parameterContainer;
+ }
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Pgsql/Connection.php b/library/Zend/Db/Adapter/Driver/Pgsql/Connection.php
new file mode 100755
index 0000000000..fa91289a43
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Pgsql/Connection.php
@@ -0,0 +1,311 @@
+setConnectionParameters($connectionInfo);
+ } elseif (is_resource($connectionInfo)) {
+ $this->setResource($connectionInfo);
+ }
+ }
+
+ /**
+ * Set connection parameters
+ *
+ * @param array $connectionParameters
+ * @return Connection
+ */
+ public function setConnectionParameters(array $connectionParameters)
+ {
+ $this->connectionParameters = $connectionParameters;
+ return $this;
+ }
+
+ /**
+ * Set driver
+ *
+ * @param Pgsql $driver
+ * @return Connection
+ */
+ public function setDriver(Pgsql $driver)
+ {
+ $this->driver = $driver;
+ return $this;
+ }
+
+ /**
+ * @param Profiler\ProfilerInterface $profiler
+ * @return Connection
+ */
+ public function setProfiler(Profiler\ProfilerInterface $profiler)
+ {
+ $this->profiler = $profiler;
+ return $this;
+ }
+
+ /**
+ * @return null|Profiler\ProfilerInterface
+ */
+ public function getProfiler()
+ {
+ return $this->profiler;
+ }
+
+ /**
+ * Set resource
+ *
+ * @param resource $resource
+ * @return Connection
+ */
+ public function setResource($resource)
+ {
+ $this->resource = $resource;
+ return;
+ }
+
+ /**
+ * Get current schema
+ *
+ * @return null|string
+ */
+ public function getCurrentSchema()
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+
+ $result = pg_query($this->resource, 'SELECT CURRENT_SCHEMA AS "currentschema"');
+ if ($result == false) {
+ return null;
+ }
+ return pg_fetch_result($result, 0, 'currentschema');
+ }
+
+ /**
+ * Get resource
+ *
+ * @return resource
+ */
+ public function getResource()
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+ return $this->resource;
+ }
+
+ /**
+ * Connect to the database
+ *
+ * @return Connection
+ * @throws Exception\RuntimeException on failure
+ */
+ public function connect()
+ {
+ if (is_resource($this->resource)) {
+ return $this;
+ }
+
+ // localize
+ $p = $this->connectionParameters;
+
+ // given a list of key names, test for existence in $p
+ $findParameterValue = function (array $names) use ($p) {
+ foreach ($names as $name) {
+ if (isset($p[$name])) {
+ return $p[$name];
+ }
+ }
+ return null;
+ };
+
+ $connection = array();
+ $connection['host'] = $findParameterValue(array('hostname', 'host'));
+ $connection['user'] = $findParameterValue(array('username', 'user'));
+ $connection['password'] = $findParameterValue(array('password', 'passwd', 'pw'));
+ $connection['dbname'] = $findParameterValue(array('database', 'dbname', 'db', 'schema'));
+ $connection['port'] = (isset($p['port'])) ? (int) $p['port'] : null;
+ $connection['socket'] = (isset($p['socket'])) ? $p['socket'] : null;
+
+ $connection = array_filter($connection); // remove nulls
+ $connection = http_build_query($connection, null, ' '); // @link http://php.net/pg_connect
+
+ set_error_handler(function ($number, $string) {
+ throw new Exception\RuntimeException(
+ __METHOD__ . ': Unable to connect to database', null, new Exception\ErrorException($string, $number)
+ );
+ });
+ $this->resource = pg_connect($connection);
+ restore_error_handler();
+
+ if ($this->resource === false) {
+ throw new Exception\RuntimeException(sprintf(
+ '%s: Unable to connect to database',
+ __METHOD__
+ ));
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isConnected()
+ {
+ return (is_resource($this->resource));
+ }
+
+ /**
+ * @return void
+ */
+ public function disconnect()
+ {
+ pg_close($this->resource);
+ }
+
+ /**
+ * @return void
+ */
+ public function beginTransaction()
+ {
+ if ($this->inTransaction) {
+ throw new Exception\RuntimeException('Nested transactions are not supported');
+ }
+
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+
+ pg_query($this->resource, 'BEGIN');
+ $this->inTransaction = true;
+ }
+
+ /**
+ * In transaction
+ *
+ * @return bool
+ */
+ public function inTransaction()
+ {
+ return $this->inTransaction;
+ }
+
+ /**
+ * @return void
+ */
+ public function commit()
+ {
+ if (!$this->inTransaction) {
+ return; // We ignore attempts to commit non-existing transaction
+ }
+
+ pg_query($this->resource, 'COMMIT');
+ $this->inTransaction = false;
+ }
+
+ /**
+ * @return void
+ */
+ public function rollback()
+ {
+ if (!$this->inTransaction) {
+ return;
+ }
+
+ pg_query($this->resource, 'ROLLBACK');
+ $this->inTransaction = false;
+ }
+
+ /**
+ * @param string $sql
+ * @throws Exception\InvalidQueryException
+ * @return resource|\Zend\Db\ResultSet\ResultSetInterface
+ */
+ public function execute($sql)
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+
+ if ($this->profiler) {
+ $this->profiler->profilerStart($sql);
+ }
+
+ $resultResource = pg_query($this->resource, $sql);
+
+ if ($this->profiler) {
+ $this->profiler->profilerFinish($sql);
+ }
+
+ // if the returnValue is something other than a pg result resource, bypass wrapping it
+ if ($resultResource === false) {
+ throw new Exception\InvalidQueryException(pg_errormessage());
+ }
+
+ $resultPrototype = $this->driver->createResult(($resultResource === true) ? $this->resource : $resultResource);
+ return $resultPrototype;
+ }
+
+ /**
+ * @param null $name Ignored
+ * @return string
+ */
+ public function getLastGeneratedValue($name = null)
+ {
+ if ($name == null) {
+ return null;
+ }
+ $result = pg_query($this->resource, 'SELECT CURRVAL(\'' . str_replace('\'', '\\\'', $name) . '\') as "currval"');
+ return pg_fetch_result($result, 0, 'currval');
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Pgsql/Pgsql.php b/library/Zend/Db/Adapter/Driver/Pgsql/Pgsql.php
new file mode 100755
index 0000000000..36e5e0f294
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Pgsql/Pgsql.php
@@ -0,0 +1,227 @@
+ false
+ );
+
+ /**
+ * Constructor
+ *
+ * @param array|Connection|resource $connection
+ * @param null|Statement $statementPrototype
+ * @param null|Result $resultPrototype
+ * @param array $options
+ */
+ public function __construct($connection, Statement $statementPrototype = null, Result $resultPrototype = null, $options = null)
+ {
+ if (!$connection instanceof Connection) {
+ $connection = new Connection($connection);
+ }
+
+ $this->registerConnection($connection);
+ $this->registerStatementPrototype(($statementPrototype) ?: new Statement());
+ $this->registerResultPrototype(($resultPrototype) ?: new Result());
+ }
+
+ public function setProfiler(Profiler\ProfilerInterface $profiler)
+ {
+ $this->profiler = $profiler;
+ if ($this->connection instanceof Profiler\ProfilerAwareInterface) {
+ $this->connection->setProfiler($profiler);
+ }
+ if ($this->statementPrototype instanceof Profiler\ProfilerAwareInterface) {
+ $this->statementPrototype->setProfiler($profiler);
+ }
+ return $this;
+ }
+
+ /**
+ * @return null|Profiler\ProfilerInterface
+ */
+ public function getProfiler()
+ {
+ return $this->profiler;
+ }
+
+ /**
+ * Register connection
+ *
+ * @param Connection $connection
+ * @return Pgsql
+ */
+ public function registerConnection(Connection $connection)
+ {
+ $this->connection = $connection;
+ $this->connection->setDriver($this);
+ return $this;
+ }
+
+ /**
+ * Register statement prototype
+ *
+ * @param Statement $statement
+ * @return Pgsql
+ */
+ public function registerStatementPrototype(Statement $statement)
+ {
+ $this->statementPrototype = $statement;
+ $this->statementPrototype->setDriver($this); // needs access to driver to createResult()
+ return $this;
+ }
+
+ /**
+ * Register result prototype
+ *
+ * @param Result $result
+ * @return Pgsql
+ */
+ public function registerResultPrototype(Result $result)
+ {
+ $this->resultPrototype = $result;
+ return $this;
+ }
+
+ /**
+ * Get database platform name
+ *
+ * @param string $nameFormat
+ * @return string
+ */
+ public function getDatabasePlatformName($nameFormat = self::NAME_FORMAT_CAMELCASE)
+ {
+ if ($nameFormat == self::NAME_FORMAT_CAMELCASE) {
+ return 'Postgresql';
+ }
+
+ return 'PostgreSQL';
+ }
+
+ /**
+ * Check environment
+ *
+ * @throws Exception\RuntimeException
+ * @return bool
+ */
+ public function checkEnvironment()
+ {
+ if (!extension_loaded('pgsql')) {
+ throw new Exception\RuntimeException('The PostgreSQL (pgsql) extension is required for this adapter but the extension is not loaded');
+ }
+ }
+
+ /**
+ * Get connection
+ *
+ * @return Connection
+ */
+ public function getConnection()
+ {
+ return $this->connection;
+ }
+
+ /**
+ * Create statement
+ *
+ * @param string|null $sqlOrResource
+ * @return Statement
+ */
+ public function createStatement($sqlOrResource = null)
+ {
+ $statement = clone $this->statementPrototype;
+
+ if (is_string($sqlOrResource)) {
+ $statement->setSql($sqlOrResource);
+ }
+
+ if (!$this->connection->isConnected()) {
+ $this->connection->connect();
+ }
+
+ $statement->initialize($this->connection->getResource());
+ return $statement;
+ }
+
+ /**
+ * Create result
+ *
+ * @param resource $resource
+ * @return Result
+ */
+ public function createResult($resource)
+ {
+ $result = clone $this->resultPrototype;
+ $result->initialize($resource, $this->connection->getLastGeneratedValue());
+ return $result;
+ }
+
+ /**
+ * Get prepare Type
+ *
+ * @return array
+ */
+ public function getPrepareType()
+ {
+ return self::PARAMETERIZATION_POSITIONAL;
+ }
+
+ /**
+ * Format parameter name
+ *
+ * @param string $name
+ * @param mixed $type
+ * @return string
+ */
+ public function formatParameterName($name, $type = null)
+ {
+ return '$#';
+ }
+
+ /**
+ * Get last generated value
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function getLastGeneratedValue($name = null)
+ {
+ return $this->connection->getLastGeneratedValue($name);
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Pgsql/Result.php b/library/Zend/Db/Adapter/Driver/Pgsql/Result.php
new file mode 100755
index 0000000000..6c2410dae8
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Pgsql/Result.php
@@ -0,0 +1,192 @@
+resource = $resource;
+ $this->count = pg_num_rows($this->resource);
+ $this->generatedValue = $generatedValue;
+ }
+
+ /**
+ * Current
+ *
+ * @return array|bool|mixed
+ */
+ public function current()
+ {
+ if ($this->count === 0) {
+ return false;
+ }
+ return pg_fetch_assoc($this->resource, $this->position);
+ }
+
+ /**
+ * Next
+ *
+ * @return void
+ */
+ public function next()
+ {
+ $this->position++;
+ }
+
+ /**
+ * Key
+ *
+ * @return int|mixed
+ */
+ public function key()
+ {
+ return $this->position;
+ }
+
+ /**
+ * Valid
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return ($this->position < $this->count);
+ }
+
+ /**
+ * Rewind
+ *
+ * @return void
+ */
+ public function rewind()
+ {
+ $this->position = 0;
+ }
+
+ /**
+ * Buffer
+ *
+ * @return null
+ */
+ public function buffer()
+ {
+ return null;
+ }
+
+ /**
+ * Is buffered
+ *
+ * @return false
+ */
+ public function isBuffered()
+ {
+ return false;
+ }
+
+ /**
+ * Is query result
+ *
+ * @return bool
+ */
+ public function isQueryResult()
+ {
+ return (pg_num_fields($this->resource) > 0);
+ }
+
+ /**
+ * Get affected rows
+ *
+ * @return int
+ */
+ public function getAffectedRows()
+ {
+ return pg_affected_rows($this->resource);
+ }
+
+ /**
+ * Get generated value
+ *
+ * @return mixed|null
+ */
+ public function getGeneratedValue()
+ {
+ return $this->generatedValue;
+ }
+
+ /**
+ * Get resource
+ */
+ public function getResource()
+ {
+ // TODO: Implement getResource() method.
+ }
+
+ /**
+ * Count
+ *
+ * (PHP 5 >= 5.1.0)
+ * Count elements of an object
+ * @link http://php.net/manual/en/countable.count.php
+ * @return int The custom count as an integer.
+ *
+ *
+ * The return value is cast to an integer.
+ */
+ public function count()
+ {
+ return $this->count;
+ }
+
+ /**
+ * Get field count
+ *
+ * @return int
+ */
+ public function getFieldCount()
+ {
+ return pg_num_fields($this->resource);
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Pgsql/Statement.php b/library/Zend/Db/Adapter/Driver/Pgsql/Statement.php
new file mode 100755
index 0000000000..c105a6647e
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Pgsql/Statement.php
@@ -0,0 +1,241 @@
+driver = $driver;
+ return $this;
+ }
+
+ /**
+ * @param Profiler\ProfilerInterface $profiler
+ * @return Statement
+ */
+ public function setProfiler(Profiler\ProfilerInterface $profiler)
+ {
+ $this->profiler = $profiler;
+ return $this;
+ }
+
+ /**
+ * @return null|Profiler\ProfilerInterface
+ */
+ public function getProfiler()
+ {
+ return $this->profiler;
+ }
+
+ /**
+ * Initialize
+ *
+ * @param resource $pgsql
+ * @return void
+ * @throws Exception\RuntimeException for invalid or missing postgresql connection
+ */
+ public function initialize($pgsql)
+ {
+ if (!is_resource($pgsql) || get_resource_type($pgsql) !== 'pgsql link') {
+ throw new Exception\RuntimeException(sprintf(
+ '%s: Invalid or missing postgresql connection; received "%s"',
+ __METHOD__,
+ get_resource_type($pgsql)
+ ));
+ }
+ $this->pgsql = $pgsql;
+ }
+
+ /**
+ * Get resource
+ *
+ * @return resource
+ */
+ public function getResource()
+ {
+ // TODO: Implement getResource() method.
+ }
+
+ /**
+ * Set sql
+ *
+ * @param string $sql
+ * @return Statement
+ */
+ public function setSql($sql)
+ {
+ $this->sql = $sql;
+ return $this;
+ }
+
+ /**
+ * Get sql
+ *
+ * @return string
+ */
+ public function getSql()
+ {
+ return $this->sql;
+ }
+
+ /**
+ * Set parameter container
+ *
+ * @param ParameterContainer $parameterContainer
+ * @return Statement
+ */
+ public function setParameterContainer(ParameterContainer $parameterContainer)
+ {
+ $this->parameterContainer = $parameterContainer;
+ return $this;
+ }
+
+ /**
+ * Get parameter container
+ *
+ * @return ParameterContainer
+ */
+ public function getParameterContainer()
+ {
+ return $this->parameterContainer;
+ }
+
+ /**
+ * Prepare
+ *
+ * @param string $sql
+ */
+ public function prepare($sql = null)
+ {
+ $sql = ($sql) ?: $this->sql;
+
+ $pCount = 1;
+ $sql = preg_replace_callback(
+ '#\$\##', function ($foo) use (&$pCount) {
+ return '$' . $pCount++;
+ },
+ $sql
+ );
+
+ $this->sql = $sql;
+ $this->statementName = 'statement' . ++static::$statementIndex;
+ $this->resource = pg_prepare($this->pgsql, $this->statementName, $sql);
+ }
+
+ /**
+ * Is prepared
+ *
+ * @return bool
+ */
+ public function isPrepared()
+ {
+ return isset($this->resource);
+ }
+
+ /**
+ * Execute
+ *
+ * @param ParameterContainer|null $parameters
+ * @throws Exception\InvalidQueryException
+ * @return Result
+ */
+ public function execute($parameters = null)
+ {
+ if (!$this->isPrepared()) {
+ $this->prepare();
+ }
+
+ /** START Standard ParameterContainer Merging Block */
+ if (!$this->parameterContainer instanceof ParameterContainer) {
+ if ($parameters instanceof ParameterContainer) {
+ $this->parameterContainer = $parameters;
+ $parameters = null;
+ } else {
+ $this->parameterContainer = new ParameterContainer();
+ }
+ }
+
+ if (is_array($parameters)) {
+ $this->parameterContainer->setFromArray($parameters);
+ }
+
+ if ($this->parameterContainer->count() > 0) {
+ $parameters = $this->parameterContainer->getPositionalArray();
+ }
+ /** END Standard ParameterContainer Merging Block */
+
+ if ($this->profiler) {
+ $this->profiler->profilerStart($this);
+ }
+
+ $resultResource = pg_execute($this->pgsql, $this->statementName, (array) $parameters);
+
+ if ($this->profiler) {
+ $this->profiler->profilerFinish();
+ }
+
+ if ($resultResource === false) {
+ throw new Exception\InvalidQueryException(pg_last_error());
+ }
+
+ $result = $this->driver->createResult($resultResource);
+ return $result;
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/ResultInterface.php b/library/Zend/Db/Adapter/Driver/ResultInterface.php
new file mode 100755
index 0000000000..cb1f407849
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/ResultInterface.php
@@ -0,0 +1,67 @@
+setConnectionParameters($connectionInfo);
+ } elseif (is_resource($connectionInfo)) {
+ $this->setResource($connectionInfo);
+ } else {
+ throw new Exception\InvalidArgumentException('$connection must be an array of parameters or a resource');
+ }
+ }
+
+ /**
+ * Set driver
+ *
+ * @param Sqlsrv $driver
+ * @return Connection
+ */
+ public function setDriver(Sqlsrv $driver)
+ {
+ $this->driver = $driver;
+ return $this;
+ }
+
+ /**
+ * @param Profiler\ProfilerInterface $profiler
+ * @return Connection
+ */
+ public function setProfiler(Profiler\ProfilerInterface $profiler)
+ {
+ $this->profiler = $profiler;
+ return $this;
+ }
+
+ /**
+ * @return null|Profiler\ProfilerInterface
+ */
+ public function getProfiler()
+ {
+ return $this->profiler;
+ }
+
+ /**
+ * Set connection parameters
+ *
+ * @param array $connectionParameters
+ * @return Connection
+ */
+ public function setConnectionParameters(array $connectionParameters)
+ {
+ $this->connectionParameters = $connectionParameters;
+ return $this;
+ }
+
+ /**
+ * Get connection parameters
+ *
+ * @return array
+ */
+ public function getConnectionParameters()
+ {
+ return $this->connectionParameters;
+ }
+
+ /**
+ * Get current schema
+ *
+ * @return string
+ */
+ public function getCurrentSchema()
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+
+ $result = sqlsrv_query($this->resource, 'SELECT SCHEMA_NAME()');
+ $r = sqlsrv_fetch_array($result);
+ return $r[0];
+ }
+
+ /**
+ * Set resource
+ *
+ * @param resource $resource
+ * @throws Exception\InvalidArgumentException
+ * @return Connection
+ */
+ public function setResource($resource)
+ {
+ if (get_resource_type($resource) !== 'SQL Server Connection') {
+ throw new Exception\InvalidArgumentException('Resource provided was not of type SQL Server Connection');
+ }
+ $this->resource = $resource;
+ return $this;
+ }
+
+ /**
+ * @return resource
+ */
+ public function getResource()
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+ return $this->resource;
+ }
+
+ /**
+ * Connect
+ *
+ * @throws Exception\RuntimeException
+ * @return Connection
+ */
+ public function connect()
+ {
+ if ($this->resource) {
+ return $this;
+ }
+
+ $serverName = '.';
+ $params = array(
+ 'ReturnDatesAsStrings' => true
+ );
+ foreach ($this->connectionParameters as $key => $value) {
+ switch (strtolower($key)) {
+ case 'hostname':
+ case 'servername':
+ $serverName = (string) $value;
+ break;
+ case 'username':
+ case 'uid':
+ $params['UID'] = (string) $value;
+ break;
+ case 'password':
+ case 'pwd':
+ $params['PWD'] = (string) $value;
+ break;
+ case 'database':
+ case 'dbname':
+ $params['Database'] = (string) $value;
+ break;
+ case 'charset':
+ $params['CharacterSet'] = (string) $value;
+ break;
+ case 'driver_options':
+ case 'options':
+ $params = array_merge($params, (array) $value);
+ break;
+
+ }
+ }
+
+ $this->resource = sqlsrv_connect($serverName, $params);
+
+ if (!$this->resource) {
+ throw new Exception\RuntimeException(
+ 'Connect Error',
+ null,
+ new ErrorException(sqlsrv_errors())
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Is connected
+ * @return bool
+ */
+ public function isConnected()
+ {
+ return (is_resource($this->resource));
+ }
+
+ /**
+ * Disconnect
+ */
+ public function disconnect()
+ {
+ sqlsrv_close($this->resource);
+ $this->resource = null;
+ }
+
+ /**
+ * Begin transaction
+ */
+ public function beginTransaction()
+ {
+ if (!$this->resource) {
+ $this->connect();
+ }
+ if (sqlsrv_begin_transaction($this->resource) === false) {
+ throw new Exception\RuntimeException(
+ 'Begin transaction failed',
+ null,
+ new ErrorException(sqlsrv_errors())
+ );
+ }
+
+ $this->inTransaction = true;
+ }
+
+ /**
+ * In transaction
+ *
+ * @return bool
+ */
+ public function inTransaction()
+ {
+ return $this->inTransaction;
+ }
+
+ /**
+ * Commit
+ */
+ public function commit()
+ {
+ // http://msdn.microsoft.com/en-us/library/cc296194.aspx
+
+ if (!$this->resource) {
+ $this->connect();
+ }
+
+ $this->inTransaction = false;
+
+ return sqlsrv_commit($this->resource);
+ }
+
+ /**
+ * Rollback
+ */
+ public function rollback()
+ {
+ // http://msdn.microsoft.com/en-us/library/cc296176.aspx
+
+ if (!$this->resource) {
+ throw new Exception\RuntimeException('Must be connected before you can rollback.');
+ }
+
+ return sqlsrv_rollback($this->resource);
+ }
+
+ /**
+ * Execute
+ *
+ * @param string $sql
+ * @throws Exception\RuntimeException
+ * @return mixed
+ */
+ public function execute($sql)
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+
+ if (!$this->driver instanceof Sqlsrv) {
+ throw new Exception\RuntimeException('Connection is missing an instance of Sqlsrv');
+ }
+
+ if ($this->profiler) {
+ $this->profiler->profilerStart($sql);
+ }
+
+ $returnValue = sqlsrv_query($this->resource, $sql);
+
+ if ($this->profiler) {
+ $this->profiler->profilerFinish($sql);
+ }
+
+ // if the returnValue is something other than a Sqlsrv_result, bypass wrapping it
+ if ($returnValue === false) {
+ $errors = sqlsrv_errors();
+ // ignore general warnings
+ if ($errors[0]['SQLSTATE'] != '01000') {
+ throw new Exception\RuntimeException(
+ 'An exception occurred while trying to execute the provided $sql',
+ null,
+ new ErrorException($errors)
+ );
+ }
+ }
+
+ $result = $this->driver->createResult($returnValue);
+ return $result;
+ }
+
+ /**
+ * Prepare
+ *
+ * @param string $sql
+ * @return string
+ */
+ public function prepare($sql)
+ {
+ if (!$this->isConnected()) {
+ $this->connect();
+ }
+
+ $statement = $this->driver->createStatement($sql);
+ return $statement;
+ }
+
+ /**
+ * Get last generated id
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function getLastGeneratedValue($name = null)
+ {
+ if (!$this->resource) {
+ $this->connect();
+ }
+ $sql = 'SELECT @@IDENTITY as Current_Identity';
+ $result = sqlsrv_query($this->resource, $sql);
+ $row = sqlsrv_fetch_array($result);
+ return $row['Current_Identity'];
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Sqlsrv/Exception/ErrorException.php b/library/Zend/Db/Adapter/Driver/Sqlsrv/Exception/ErrorException.php
new file mode 100755
index 0000000000..9976eee637
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Sqlsrv/Exception/ErrorException.php
@@ -0,0 +1,32 @@
+errors = ($errors === false) ? sqlsrv_errors() : $errors;
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Sqlsrv/Exception/ExceptionInterface.php b/library/Zend/Db/Adapter/Driver/Sqlsrv/Exception/ExceptionInterface.php
new file mode 100755
index 0000000000..a7168e8d6e
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Sqlsrv/Exception/ExceptionInterface.php
@@ -0,0 +1,16 @@
+resource = $resource;
+ $this->generatedValue = $generatedValue;
+ return $this;
+ }
+
+ /**
+ * @return null
+ */
+ public function buffer()
+ {
+ return null;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isBuffered()
+ {
+ return false;
+ }
+
+ /**
+ * Get resource
+ *
+ * @return resource
+ */
+ public function getResource()
+ {
+ return $this->resource;
+ }
+
+ /**
+ * Current
+ *
+ * @return mixed
+ */
+ public function current()
+ {
+ if ($this->currentComplete) {
+ return $this->currentData;
+ }
+
+ $this->load();
+ return $this->currentData;
+ }
+
+ /**
+ * Next
+ *
+ * @return bool
+ */
+ public function next()
+ {
+ $this->load();
+ return true;
+ }
+
+ /**
+ * Load
+ *
+ * @param int $row
+ * @return mixed
+ */
+ protected function load($row = SQLSRV_SCROLL_NEXT)
+ {
+ $this->currentData = sqlsrv_fetch_array($this->resource, SQLSRV_FETCH_ASSOC, $row);
+ $this->currentComplete = true;
+ $this->position++;
+ return $this->currentData;
+ }
+
+ /**
+ * Key
+ *
+ * @return mixed
+ */
+ public function key()
+ {
+ return $this->position;
+ }
+
+ /**
+ * Rewind
+ *
+ * @return bool
+ */
+ public function rewind()
+ {
+ $this->position = 0;
+ $this->load(SQLSRV_SCROLL_FIRST);
+ return true;
+ }
+
+ /**
+ * Valid
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ if ($this->currentComplete && $this->currentData) {
+ return true;
+ }
+
+ return $this->load();
+ }
+
+ /**
+ * Count
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return sqlsrv_num_rows($this->resource);
+ }
+
+ /**
+ * @return bool|int
+ */
+ public function getFieldCount()
+ {
+ return sqlsrv_num_fields($this->resource);
+ }
+
+ /**
+ * Is query result
+ *
+ * @return bool
+ */
+ public function isQueryResult()
+ {
+ if (is_bool($this->resource)) {
+ return false;
+ }
+ return (sqlsrv_num_fields($this->resource) > 0);
+ }
+
+ /**
+ * Get affected rows
+ *
+ * @return int
+ */
+ public function getAffectedRows()
+ {
+ return sqlsrv_rows_affected($this->resource);
+ }
+
+ /**
+ * @return mixed|null
+ */
+ public function getGeneratedValue()
+ {
+ return $this->generatedValue;
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Sqlsrv/Sqlsrv.php b/library/Zend/Db/Adapter/Driver/Sqlsrv/Sqlsrv.php
new file mode 100755
index 0000000000..0cb8b24356
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Sqlsrv/Sqlsrv.php
@@ -0,0 +1,211 @@
+registerConnection($connection);
+ $this->registerStatementPrototype(($statementPrototype) ?: new Statement());
+ $this->registerResultPrototype(($resultPrototype) ?: new Result());
+ }
+
+ /**
+ * @param Profiler\ProfilerInterface $profiler
+ * @return Sqlsrv
+ */
+ public function setProfiler(Profiler\ProfilerInterface $profiler)
+ {
+ $this->profiler = $profiler;
+ if ($this->connection instanceof Profiler\ProfilerAwareInterface) {
+ $this->connection->setProfiler($profiler);
+ }
+ if ($this->statementPrototype instanceof Profiler\ProfilerAwareInterface) {
+ $this->statementPrototype->setProfiler($profiler);
+ }
+ return $this;
+ }
+
+ /**
+ * @return null|Profiler\ProfilerInterface
+ */
+ public function getProfiler()
+ {
+ return $this->profiler;
+ }
+
+ /**
+ * Register connection
+ *
+ * @param Connection $connection
+ * @return Sqlsrv
+ */
+ public function registerConnection(Connection $connection)
+ {
+ $this->connection = $connection;
+ $this->connection->setDriver($this);
+ return $this;
+ }
+
+ /**
+ * Register statement prototype
+ *
+ * @param Statement $statementPrototype
+ * @return Sqlsrv
+ */
+ public function registerStatementPrototype(Statement $statementPrototype)
+ {
+ $this->statementPrototype = $statementPrototype;
+ $this->statementPrototype->setDriver($this);
+ return $this;
+ }
+
+ /**
+ * Register result prototype
+ *
+ * @param Result $resultPrototype
+ * @return Sqlsrv
+ */
+ public function registerResultPrototype(Result $resultPrototype)
+ {
+ $this->resultPrototype = $resultPrototype;
+ return $this;
+ }
+
+ /**
+ * Get database paltform name
+ *
+ * @param string $nameFormat
+ * @return string
+ */
+ public function getDatabasePlatformName($nameFormat = self::NAME_FORMAT_CAMELCASE)
+ {
+ if ($nameFormat == self::NAME_FORMAT_CAMELCASE) {
+ return 'SqlServer';
+ }
+
+ return 'SQLServer';
+ }
+
+ /**
+ * Check environment
+ *
+ * @throws Exception\RuntimeException
+ * @return void
+ */
+ public function checkEnvironment()
+ {
+ if (!extension_loaded('sqlsrv')) {
+ throw new Exception\RuntimeException('The Sqlsrv extension is required for this adapter but the extension is not loaded');
+ }
+ }
+
+ /**
+ * @return Connection
+ */
+ public function getConnection()
+ {
+ return $this->connection;
+ }
+
+ /**
+ * @param string|resource $sqlOrResource
+ * @return Statement
+ */
+ public function createStatement($sqlOrResource = null)
+ {
+ $statement = clone $this->statementPrototype;
+ if (is_resource($sqlOrResource)) {
+ $statement->initialize($sqlOrResource);
+ } else {
+ if (!$this->connection->isConnected()) {
+ $this->connection->connect();
+ }
+ $statement->initialize($this->connection->getResource());
+ if (is_string($sqlOrResource)) {
+ $statement->setSql($sqlOrResource);
+ } elseif ($sqlOrResource != null) {
+ throw new Exception\InvalidArgumentException('createStatement() only accepts an SQL string or a Sqlsrv resource');
+ }
+ }
+ return $statement;
+ }
+
+ /**
+ * @param resource $resource
+ * @return Result
+ */
+ public function createResult($resource)
+ {
+ $result = clone $this->resultPrototype;
+ $result->initialize($resource, $this->connection->getLastGeneratedValue());
+ return $result;
+ }
+
+ /**
+ * @return array
+ */
+ public function getPrepareType()
+ {
+ return self::PARAMETERIZATION_POSITIONAL;
+ }
+
+ /**
+ * @param string $name
+ * @param mixed $type
+ * @return string
+ */
+ public function formatParameterName($name, $type = null)
+ {
+ return '?';
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getLastGeneratedValue()
+ {
+ return $this->getConnection()->getLastGeneratedValue();
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/Sqlsrv/Statement.php b/library/Zend/Db/Adapter/Driver/Sqlsrv/Statement.php
new file mode 100755
index 0000000000..4aefa9ff90
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/Sqlsrv/Statement.php
@@ -0,0 +1,313 @@
+driver = $driver;
+ return $this;
+ }
+
+ /**
+ * @param Profiler\ProfilerInterface $profiler
+ * @return Statement
+ */
+ public function setProfiler(Profiler\ProfilerInterface $profiler)
+ {
+ $this->profiler = $profiler;
+ return $this;
+ }
+
+ /**
+ * @return null|Profiler\ProfilerInterface
+ */
+ public function getProfiler()
+ {
+ return $this->profiler;
+ }
+
+ /**
+ *
+ * One of two resource types will be provided here:
+ * a) "SQL Server Connection" when a prepared statement needs to still be produced
+ * b) "SQL Server Statement" when a prepared statement has been already produced
+ * (there will need to already be a bound param set if it applies to this query)
+ *
+ * @param resource $resource
+ * @throws Exception\InvalidArgumentException
+ * @return Statement
+ */
+ public function initialize($resource)
+ {
+ $resourceType = get_resource_type($resource);
+
+ if ($resourceType == 'SQL Server Connection') {
+ $this->sqlsrv = $resource;
+ } elseif ($resourceType == 'SQL Server Statement') {
+ $this->resource = $resource;
+ $this->isPrepared = true;
+ } else {
+ throw new Exception\InvalidArgumentException('Invalid resource provided to ' . __CLASS__);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set parameter container
+ *
+ * @param ParameterContainer $parameterContainer
+ * @return Statement
+ */
+ public function setParameterContainer(ParameterContainer $parameterContainer)
+ {
+ $this->parameterContainer = $parameterContainer;
+ return $this;
+ }
+
+ /**
+ * @return ParameterContainer
+ */
+ public function getParameterContainer()
+ {
+ return $this->parameterContainer;
+ }
+
+ /**
+ * @param $resource
+ * @return Statement
+ */
+ public function setResource($resource)
+ {
+ $this->resource = $resource;
+ return $this;
+ }
+
+ /**
+ * Get resource
+ *
+ * @return resource
+ */
+ public function getResource()
+ {
+ return $this->resource;
+ }
+
+ /**
+ * @param string $sql
+ * @return Statement
+ */
+ public function setSql($sql)
+ {
+ $this->sql = $sql;
+ return $this;
+ }
+
+ /**
+ * Get sql
+ *
+ * @return string
+ */
+ public function getSql()
+ {
+ return $this->sql;
+ }
+
+ /**
+ * @param string $sql
+ * @param array $options
+ * @throws Exception\RuntimeException
+ * @return Statement
+ */
+ public function prepare($sql = null, array $options = array())
+ {
+ if ($this->isPrepared) {
+ throw new Exception\RuntimeException('Already prepared');
+ }
+ $sql = ($sql) ?: $this->sql;
+ $options = ($options) ?: $this->prepareOptions;
+
+ $pRef = &$this->parameterReferences;
+ for ($position = 0, $count = substr_count($sql, '?'); $position < $count; $position++) {
+ if (!isset($this->prepareParams[$position])) {
+ $pRef[$position] = array('', SQLSRV_PARAM_IN, null, null);
+ } else {
+ $pRef[$position] = &$this->prepareParams[$position];
+ }
+ }
+
+ $this->resource = sqlsrv_prepare($this->sqlsrv, $sql, $pRef, $options);
+
+ $this->isPrepared = true;
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isPrepared()
+ {
+ return $this->isPrepared;
+ }
+
+ /**
+ * Execute
+ *
+ * @param array|ParameterContainer $parameters
+ * @throws Exception\RuntimeException
+ * @return Result
+ */
+ public function execute($parameters = null)
+ {
+ /** END Standard ParameterContainer Merging Block */
+ if (!$this->isPrepared) {
+ $this->prepare();
+ }
+
+ /** START Standard ParameterContainer Merging Block */
+ if (!$this->parameterContainer instanceof ParameterContainer) {
+ if ($parameters instanceof ParameterContainer) {
+ $this->parameterContainer = $parameters;
+ $parameters = null;
+ } else {
+ $this->parameterContainer = new ParameterContainer();
+ }
+ }
+
+ if (is_array($parameters)) {
+ $this->parameterContainer->setFromArray($parameters);
+ }
+
+ if ($this->parameterContainer->count() > 0) {
+ $this->bindParametersFromContainer();
+ }
+
+ if ($this->profiler) {
+ $this->profiler->profilerStart($this);
+ }
+
+ $resultValue = sqlsrv_execute($this->resource);
+
+ if ($this->profiler) {
+ $this->profiler->profilerFinish();
+ }
+
+ if ($resultValue === false) {
+ $errors = sqlsrv_errors();
+ // ignore general warnings
+ if ($errors[0]['SQLSTATE'] != '01000') {
+ throw new Exception\RuntimeException($errors[0]['message']);
+ }
+ }
+
+ $result = $this->driver->createResult($this->resource);
+ return $result;
+ }
+
+ /**
+ * Bind parameters from container
+ *
+ */
+ protected function bindParametersFromContainer()
+ {
+ $values = $this->parameterContainer->getPositionalArray();
+ $position = 0;
+ foreach ($values as $value) {
+ $this->parameterReferences[$position++][0] = $value;
+ }
+ }
+
+ /**
+ * @param array $prepareParams
+ */
+ public function setPrepareParams(array $prepareParams)
+ {
+ $this->prepareParams = $prepareParams;
+ }
+
+ /**
+ * @param array $prepareOptions
+ */
+ public function setPrepareOptions(array $prepareOptions)
+ {
+ $this->prepareOptions = $prepareOptions;
+ }
+}
diff --git a/library/Zend/Db/Adapter/Driver/StatementInterface.php b/library/Zend/Db/Adapter/Driver/StatementInterface.php
new file mode 100755
index 0000000000..a1ba567095
--- /dev/null
+++ b/library/Zend/Db/Adapter/Driver/StatementInterface.php
@@ -0,0 +1,44 @@
+parameters = $parameters;
+ }
+}
diff --git a/library/Zend/Db/Adapter/Exception/InvalidQueryException.php b/library/Zend/Db/Adapter/Exception/InvalidQueryException.php
new file mode 100755
index 0000000000..1372237fe1
--- /dev/null
+++ b/library/Zend/Db/Adapter/Exception/InvalidQueryException.php
@@ -0,0 +1,14 @@
+setFromArray($data);
+ }
+ }
+
+ /**
+ * Offset exists
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function offsetExists($name)
+ {
+ return (isset($this->data[$name]));
+ }
+
+ /**
+ * Offset get
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function offsetGet($name)
+ {
+ return (isset($this->data[$name])) ? $this->data[$name] : null;
+ }
+
+ /**
+ * @param $name
+ * @param $from
+ */
+ public function offsetSetReference($name, $from)
+ {
+ $this->data[$name] =& $this->data[$from];
+ }
+
+ /**
+ * Offset set
+ *
+ * @param string|int $name
+ * @param mixed $value
+ * @param mixed $errata
+ */
+ public function offsetSet($name, $value, $errata = null)
+ {
+ $position = false;
+
+ // if integer, get name for this position
+ if (is_int($name)) {
+ if (isset($this->positions[$name])) {
+ $position = $name;
+ $name = $this->positions[$name];
+ } else {
+ $name = (string) $name;
+ }
+ } elseif (is_string($name)) {
+ // is a string:
+ $position = array_key_exists($name, $this->data);
+ } elseif ($name === null) {
+ $name = (string) count($this->data);
+ } else {
+ throw new Exception\InvalidArgumentException('Keys must be string, integer or null');
+ }
+
+ if ($position === false) {
+ $this->positions[] = $name;
+ }
+
+ $this->data[$name] = $value;
+
+ if ($errata) {
+ $this->offsetSetErrata($name, $errata);
+ }
+ }
+
+ /**
+ * Offset unset
+ *
+ * @param string $name
+ * @return ParameterContainer
+ */
+ public function offsetUnset($name)
+ {
+ if (is_int($name) && isset($this->positions[$name])) {
+ $name = $this->positions[$name];
+ }
+ unset($this->data[$name]);
+ return $this;
+ }
+
+ /**
+ * Set from array
+ *
+ * @param array $data
+ * @return ParameterContainer
+ */
+ public function setFromArray(Array $data)
+ {
+ foreach ($data as $n => $v) {
+ $this->offsetSet($n, $v);
+ }
+ return $this;
+ }
+
+ /**
+ * Offset set errata
+ *
+ * @param string|int $name
+ * @param mixed $errata
+ */
+ public function offsetSetErrata($name, $errata)
+ {
+ if (is_int($name)) {
+ $name = $this->positions[$name];
+ }
+ $this->errata[$name] = $errata;
+ }
+
+ /**
+ * Offset get errata
+ *
+ * @param string|int $name
+ * @throws Exception\InvalidArgumentException
+ * @return mixed
+ */
+ public function offsetGetErrata($name)
+ {
+ if (is_int($name)) {
+ $name = $this->positions[$name];
+ }
+ if (!array_key_exists($name, $this->data)) {
+ throw new Exception\InvalidArgumentException('Data does not exist for this name/position');
+ }
+ return $this->errata[$name];
+ }
+
+ /**
+ * Offset has errata
+ *
+ * @param string|int $name
+ * @return bool
+ */
+ public function offsetHasErrata($name)
+ {
+ if (is_int($name)) {
+ $name = $this->positions[$name];
+ }
+ return (isset($this->errata[$name]));
+ }
+
+ /**
+ * Offset unset errata
+ *
+ * @param string|int $name
+ * @throws Exception\InvalidArgumentException
+ */
+ public function offsetUnsetErrata($name)
+ {
+ if (is_int($name)) {
+ $name = $this->positions[$name];
+ }
+ if (!array_key_exists($name, $this->errata)) {
+ throw new Exception\InvalidArgumentException('Data does not exist for this name/position');
+ }
+ $this->errata[$name] = null;
+ }
+
+ /**
+ * Get errata iterator
+ *
+ * @return \ArrayIterator
+ */
+ public function getErrataIterator()
+ {
+ return new \ArrayIterator($this->errata);
+ }
+
+ /**
+ * getNamedArray
+ *
+ * @return array
+ */
+ public function getNamedArray()
+ {
+ return $this->data;
+ }
+
+ /**
+ * getNamedArray
+ *
+ * @return array
+ */
+ public function getPositionalArray()
+ {
+ return array_values($this->data);
+ }
+
+ /**
+ * count
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->data);
+ }
+
+ /**
+ * Current
+ *
+ * @return mixed
+ */
+ public function current()
+ {
+ return current($this->data);
+ }
+
+ /**
+ * Next
+ *
+ * @return mixed
+ */
+ public function next()
+ {
+ return next($this->data);
+ }
+
+ /**
+ * Key
+ *
+ * @return mixed
+ */
+ public function key()
+ {
+ return key($this->data);
+ }
+
+ /**
+ * Valid
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return (current($this->data) !== false);
+ }
+
+ /**
+ * Rewind
+ */
+ public function rewind()
+ {
+ reset($this->data);
+ }
+
+ /**
+ * @param array|ParameterContainer $parameters
+ * @throws Exception\InvalidArgumentException
+ * @return ParameterContainer
+ */
+ public function merge($parameters)
+ {
+ if (!is_array($parameters) && !$parameters instanceof ParameterContainer) {
+ throw new Exception\InvalidArgumentException('$parameters must be an array or an instance of ParameterContainer');
+ }
+
+ if (count($parameters) == 0) {
+ return $this;
+ }
+
+ if ($parameters instanceof ParameterContainer) {
+ $parameters = $parameters->getNamedArray();
+ }
+
+ foreach ($parameters as $key => $value) {
+ if (is_int($key)) {
+ $key = null;
+ }
+ $this->offsetSet($key, $value);
+ }
+ return $this;
+ }
+}
diff --git a/library/Zend/Db/Adapter/Platform/IbmDb2.php b/library/Zend/Db/Adapter/Platform/IbmDb2.php
new file mode 100755
index 0000000000..693b647e83
--- /dev/null
+++ b/library/Zend/Db/Adapter/Platform/IbmDb2.php
@@ -0,0 +1,207 @@
+quoteIdentifiers = false;
+ }
+
+ if (isset($options['identifier_separator'])) {
+ $this->identifierSeparator = $options['identifier_separator'];
+ }
+ }
+
+ /**
+ * Get name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return 'IBM DB2';
+ }
+
+ /**
+ * Get quote indentifier symbol
+ *
+ * @return string
+ */
+ public function getQuoteIdentifierSymbol()
+ {
+ return '"';
+ }
+
+ /**
+ * Quote identifier
+ *
+ * @param string $identifier
+ * @return string
+ */
+ public function quoteIdentifier($identifier)
+ {
+ if ($this->quoteIdentifiers === false) {
+ return $identifier;
+ }
+ return '"' . str_replace('"', '\\' . '"', $identifier) . '"';
+ }
+
+ /**
+ * Quote identifier chain
+ *
+ * @param string|string[] $identifierChain
+ * @return string
+ */
+ public function quoteIdentifierChain($identifierChain)
+ {
+ if ($this->quoteIdentifiers === false) {
+ return (is_array($identifierChain)) ? implode($this->identifierSeparator, $identifierChain) : $identifierChain;
+ }
+ $identifierChain = str_replace('"', '\\"', $identifierChain);
+ if (is_array($identifierChain)) {
+ $identifierChain = implode('"' . $this->identifierSeparator . '"', $identifierChain);
+ }
+ return '"' . $identifierChain . '"';
+ }
+
+ /**
+ * Get quote value symbol
+ *
+ * @return string
+ */
+ public function getQuoteValueSymbol()
+ {
+ return '\'';
+ }
+
+ /**
+ * Quote value
+ *
+ * @param string $value
+ * @return string
+ */
+ public function quoteValue($value)
+ {
+ if (function_exists('db2_escape_string')) {
+ return '\'' . db2_escape_string($value) . '\'';
+ }
+ trigger_error(
+ 'Attempting to quote a value in ' . __CLASS__ . ' without extension/driver support '
+ . 'can introduce security vulnerabilities in a production environment.'
+ );
+ return '\'' . str_replace("'", "''", $value) . '\'';
+ }
+
+ /**
+ * Quote Trusted Value
+ *
+ * The ability to quote values without notices
+ *
+ * @param $value
+ * @return mixed
+ */
+ public function quoteTrustedValue($value)
+ {
+ if (function_exists('db2_escape_string')) {
+ return '\'' . db2_escape_string($value) . '\'';
+ }
+ return '\'' . str_replace("'", "''", $value) . '\'';
+ }
+
+ /**
+ * Quote value list
+ *
+ * @param string|string[] $valueList
+ * @return string
+ */
+ public function quoteValueList($valueList)
+ {
+ if (!is_array($valueList)) {
+ return $this->quoteValue($valueList);
+ }
+
+ $value = reset($valueList);
+ do {
+ $valueList[key($valueList)] = $this->quoteValue($value);
+ } while ($value = next($valueList));
+ return implode(', ', $valueList);
+ }
+
+ /**
+ * Get identifier separator
+ *
+ * @return string
+ */
+ public function getIdentifierSeparator()
+ {
+ return $this->identifierSeparator;
+ }
+
+ /**
+ * Quote identifier in fragment
+ *
+ * @param string $identifier
+ * @param array $safeWords
+ * @return string
+ */
+ public function quoteIdentifierInFragment($identifier, array $safeWords = array())
+ {
+ if ($this->quoteIdentifiers === false) {
+ return $identifier;
+ }
+ $parts = preg_split('#([\.\s\W])#', $identifier, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+
+ if ($safeWords) {
+ $safeWords = array_flip($safeWords);
+ $safeWords = array_change_key_case($safeWords, CASE_LOWER);
+ }
+ foreach ($parts as $i => $part) {
+ if ($safeWords && isset($safeWords[strtolower($part)])) {
+ continue;
+ }
+
+ switch ($part) {
+ case ' ':
+ case '.':
+ case '*':
+ case 'AS':
+ case 'As':
+ case 'aS':
+ case 'as':
+ break;
+ default:
+ $parts[$i] = '"' . str_replace('"', '\\' . '"', $part) . '"';
+ }
+ }
+
+ return implode('', $parts);
+ }
+}
diff --git a/library/Zend/Db/Adapter/Platform/Mysql.php b/library/Zend/Db/Adapter/Platform/Mysql.php
new file mode 100755
index 0000000000..6e02f083ab
--- /dev/null
+++ b/library/Zend/Db/Adapter/Platform/Mysql.php
@@ -0,0 +1,214 @@
+setDriver($driver);
+ }
+ }
+
+ /**
+ * @param \Zend\Db\Adapter\Driver\Mysqli\Mysqli|\Zend\Db\Adapter\Driver\Pdo\Pdo||\mysqli|\PDO $driver
+ * @throws \Zend\Db\Adapter\Exception\InvalidArgumentException
+ * @return $this
+ */
+ public function setDriver($driver)
+ {
+ // handle Zend\Db drivers
+ if ($driver instanceof Mysqli\Mysqli
+ || ($driver instanceof Pdo\Pdo && $driver->getDatabasePlatformName() == 'Mysql')
+ || ($driver instanceof \mysqli)
+ || ($driver instanceof \PDO && $driver->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'mysql')
+ ) {
+ $this->resource = $driver;
+ return $this;
+ }
+
+ throw new Exception\InvalidArgumentException('$driver must be a Mysqli or Mysql PDO Zend\Db\Adapter\Driver, Mysqli instance or MySQL PDO instance');
+ }
+
+ /**
+ * Get name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return 'MySQL';
+ }
+
+ /**
+ * Get quote identifier symbol
+ *
+ * @return string
+ */
+ public function getQuoteIdentifierSymbol()
+ {
+ return '`';
+ }
+
+ /**
+ * Quote identifier
+ *
+ * @param string $identifier
+ * @return string
+ */
+ public function quoteIdentifier($identifier)
+ {
+ return '`' . str_replace('`', '``', $identifier) . '`';
+ }
+
+ /**
+ * Quote identifier chain
+ *
+ * @param string|string[] $identifierChain
+ * @return string
+ */
+ public function quoteIdentifierChain($identifierChain)
+ {
+ $identifierChain = str_replace('`', '``', $identifierChain);
+ if (is_array($identifierChain)) {
+ $identifierChain = implode('`.`', $identifierChain);
+ }
+ return '`' . $identifierChain . '`';
+ }
+
+ /**
+ * Get quote value symbol
+ *
+ * @return string
+ */
+ public function getQuoteValueSymbol()
+ {
+ return '\'';
+ }
+
+ /**
+ * Quote value
+ *
+ * @param string $value
+ * @return string
+ */
+ public function quoteValue($value)
+ {
+ if ($this->resource instanceof DriverInterface) {
+ $this->resource = $this->resource->getConnection()->getResource();
+ }
+ if ($this->resource instanceof \mysqli) {
+ return '\'' . $this->resource->real_escape_string($value) . '\'';
+ }
+ if ($this->resource instanceof \PDO) {
+ return $this->resource->quote($value);
+ }
+ trigger_error(
+ 'Attempting to quote a value in ' . __CLASS__ . ' without extension/driver support '
+ . 'can introduce security vulnerabilities in a production environment.'
+ );
+ return '\'' . addcslashes($value, "\x00\n\r\\'\"\x1a") . '\'';
+ }
+
+ /**
+ * Quote Trusted Value
+ *
+ * The ability to quote values without notices
+ *
+ * @param $value
+ * @return mixed
+ */
+ public function quoteTrustedValue($value)
+ {
+ if ($this->resource instanceof DriverInterface) {
+ $this->resource = $this->resource->getConnection()->getResource();
+ }
+ if ($this->resource instanceof \mysqli) {
+ return '\'' . $this->resource->real_escape_string($value) . '\'';
+ }
+ if ($this->resource instanceof \PDO) {
+ return $this->resource->quote($value);
+ }
+ return '\'' . addcslashes($value, "\x00\n\r\\'\"\x1a") . '\'';
+ }
+
+ /**
+ * Quote value list
+ *
+ * @param string|string[] $valueList
+ * @return string
+ */
+ public function quoteValueList($valueList)
+ {
+ if (!is_array($valueList)) {
+ return $this->quoteValue($valueList);
+ }
+
+ $value = reset($valueList);
+ do {
+ $valueList[key($valueList)] = $this->quoteValue($value);
+ } while ($value = next($valueList));
+ return implode(', ', $valueList);
+ }
+
+ /**
+ * Get identifier separator
+ *
+ * @return string
+ */
+ public function getIdentifierSeparator()
+ {
+ return '.';
+ }
+
+ /**
+ * Quote identifier in fragment
+ *
+ * @param string $identifier
+ * @param array $safeWords
+ * @return string
+ */
+ public function quoteIdentifierInFragment($identifier, array $safeWords = array())
+ {
+ // regex taken from @link http://dev.mysql.com/doc/refman/5.0/en/identifiers.html
+ $parts = preg_split('#([^0-9,a-z,A-Z$_])#', $identifier, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+ if ($safeWords) {
+ $safeWords = array_flip($safeWords);
+ $safeWords = array_change_key_case($safeWords, CASE_LOWER);
+ }
+ foreach ($parts as $i => $part) {
+ if ($safeWords && isset($safeWords[strtolower($part)])) {
+ continue;
+ }
+ switch ($part) {
+ case ' ':
+ case '.':
+ case '*':
+ case 'AS':
+ case 'As':
+ case 'aS':
+ case 'as':
+ break;
+ default:
+ $parts[$i] = '`' . str_replace('`', '``', $part) . '`';
+ }
+ }
+ return implode('', $parts);
+ }
+}
diff --git a/library/Zend/Db/Adapter/Platform/Oracle.php b/library/Zend/Db/Adapter/Platform/Oracle.php
new file mode 100755
index 0000000000..61f700a49a
--- /dev/null
+++ b/library/Zend/Db/Adapter/Platform/Oracle.php
@@ -0,0 +1,187 @@
+quoteIdentifiers = false;
+ }
+ }
+
+ /**
+ * Get name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return 'Oracle';
+ }
+
+ /**
+ * Get quote identifier symbol
+ *
+ * @return string
+ */
+ public function getQuoteIdentifierSymbol()
+ {
+ return '"';
+ }
+
+ /**
+ * Quote identifier
+ *
+ * @param string $identifier
+ * @return string
+ */
+ public function quoteIdentifier($identifier)
+ {
+ if ($this->quoteIdentifiers === false) {
+ return $identifier;
+ }
+ return '"' . str_replace('"', '\\' . '"', $identifier) . '"';
+ }
+
+ /**
+ * Quote identifier chain
+ *
+ * @param string|string[] $identifierChain
+ * @return string
+ */
+ public function quoteIdentifierChain($identifierChain)
+ {
+ if ($this->quoteIdentifiers === false) {
+ return (is_array($identifierChain)) ? implode('.', $identifierChain) : $identifierChain;
+ }
+ $identifierChain = str_replace('"', '\\"', $identifierChain);
+ if (is_array($identifierChain)) {
+ $identifierChain = implode('"."', $identifierChain);
+ }
+ return '"' . $identifierChain . '"';
+ }
+
+ /**
+ * Get quote value symbol
+ *
+ * @return string
+ */
+ public function getQuoteValueSymbol()
+ {
+ return '\'';
+ }
+
+ /**
+ * Quote value
+ *
+ * @param string $value
+ * @return string
+ */
+ public function quoteValue($value)
+ {
+ trigger_error(
+ 'Attempting to quote a value in ' . __CLASS__ . ' without extension/driver support '
+ . 'can introduce security vulnerabilities in a production environment.'
+ );
+ return '\'' . addcslashes($value, "\x00\n\r\\'\"\x1a") . '\'';
+ }
+
+ /**
+ * Quote Trusted Value
+ *
+ * The ability to quote values without notices
+ *
+ * @param $value
+ * @return mixed
+ */
+ public function quoteTrustedValue($value)
+ {
+ return '\'' . addcslashes($value, "\x00\n\r\\'\"\x1a") . '\'';
+ }
+
+ /**
+ * Quote value list
+ *
+ * @param string|string[] $valueList
+ * @return string
+ */
+ public function quoteValueList($valueList)
+ {
+ if (!is_array($valueList)) {
+ return $this->quoteValue($valueList);
+ }
+
+ $value = reset($valueList);
+ do {
+ $valueList[key($valueList)] = $this->quoteValue($value);
+ } while ($value = next($valueList));
+ return implode(', ', $valueList);
+ }
+
+ /**
+ * Get identifier separator
+ *
+ * @return string
+ */
+ public function getIdentifierSeparator()
+ {
+ return '.';
+ }
+
+ /**
+ * Quote identifier in fragment
+ *
+ * @param string $identifier
+ * @param array $safeWords
+ * @return string
+ */
+ public function quoteIdentifierInFragment($identifier, array $safeWords = array())
+ {
+ if ($this->quoteIdentifiers === false) {
+ return $identifier;
+ }
+ $parts = preg_split('#([\.\s\W])#', $identifier, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+ if ($safeWords) {
+ $safeWords = array_flip($safeWords);
+ $safeWords = array_change_key_case($safeWords, CASE_LOWER);
+ }
+ foreach ($parts as $i => $part) {
+ if ($safeWords && isset($safeWords[strtolower($part)])) {
+ continue;
+ }
+ switch ($part) {
+ case ' ':
+ case '.':
+ case '*':
+ case 'AS':
+ case 'As':
+ case 'aS':
+ case 'as':
+ break;
+ default:
+ $parts[$i] = '"' . str_replace('"', '\\' . '"', $part) . '"';
+ }
+ }
+ return implode('', $parts);
+ }
+}
diff --git a/library/Zend/Db/Adapter/Platform/PlatformInterface.php b/library/Zend/Db/Adapter/Platform/PlatformInterface.php
new file mode 100755
index 0000000000..d8ec05b2be
--- /dev/null
+++ b/library/Zend/Db/Adapter/Platform/PlatformInterface.php
@@ -0,0 +1,94 @@
+setDriver($driver);
+ }
+ }
+
+ /**
+ * @param \Zend\Db\Adapter\Driver\Pgsql\Pgsql|\Zend\Db\Adapter\Driver\Pdo\Pdo|resource|\PDO $driver
+ * @throws \Zend\Db\Adapter\Exception\InvalidArgumentException
+ * @return $this
+ */
+ public function setDriver($driver)
+ {
+ if ($driver instanceof Pgsql\Pgsql
+ || ($driver instanceof Pdo\Pdo && $driver->getDatabasePlatformName() == 'Postgresql')
+ || (is_resource($driver) && (in_array(get_resource_type($driver), array('pgsql link', 'pgsql link persistent'))))
+ || ($driver instanceof \PDO && $driver->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'pgsql')
+ ) {
+ $this->resource = $driver;
+ return $this;
+ }
+
+ throw new Exception\InvalidArgumentException('$driver must be a Pgsql or Postgresql PDO Zend\Db\Adapter\Driver, pgsql link resource or Postgresql PDO instance');
+ }
+
+ /**
+ * Get name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return 'PostgreSQL';
+ }
+
+ /**
+ * Get quote indentifier symbol
+ *
+ * @return string
+ */
+ public function getQuoteIdentifierSymbol()
+ {
+ return '"';
+ }
+
+ /**
+ * Quote identifier
+ *
+ * @param string $identifier
+ * @return string
+ */
+ public function quoteIdentifier($identifier)
+ {
+ return '"' . str_replace('"', '\\' . '"', $identifier) . '"';
+ }
+
+ /**
+ * Quote identifier chain
+ *
+ * @param string|string[] $identifierChain
+ * @return string
+ */
+ public function quoteIdentifierChain($identifierChain)
+ {
+ $identifierChain = str_replace('"', '\\"', $identifierChain);
+ if (is_array($identifierChain)) {
+ $identifierChain = implode('"."', $identifierChain);
+ }
+ return '"' . $identifierChain . '"';
+ }
+
+ /**
+ * Get quote value symbol
+ *
+ * @return string
+ */
+ public function getQuoteValueSymbol()
+ {
+ return '\'';
+ }
+
+ /**
+ * Quote value
+ *
+ * @param string $value
+ * @return string
+ */
+ public function quoteValue($value)
+ {
+ if ($this->resource instanceof DriverInterface) {
+ $this->resource = $this->resource->getConnection()->getResource();
+ }
+ if (is_resource($this->resource)) {
+ return '\'' . pg_escape_string($this->resource, $value) . '\'';
+ }
+ if ($this->resource instanceof \PDO) {
+ return $this->resource->quote($value);
+ }
+ trigger_error(
+ 'Attempting to quote a value in ' . __CLASS__ . ' without extension/driver support '
+ . 'can introduce security vulnerabilities in a production environment.'
+ );
+ return '\'' . addcslashes($value, "\x00\n\r\\'\"\x1a") . '\'';
+ }
+
+ /**
+ * Quote Trusted Value
+ *
+ * The ability to quote values without notices
+ *
+ * @param $value
+ * @return mixed
+ */
+ public function quoteTrustedValue($value)
+ {
+ if ($this->resource instanceof DriverInterface) {
+ $this->resource = $this->resource->getConnection()->getResource();
+ }
+ if (is_resource($this->resource)) {
+ return '\'' . pg_escape_string($this->resource, $value) . '\'';
+ }
+ if ($this->resource instanceof \PDO) {
+ return $this->resource->quote($value);
+ }
+ return '\'' . addcslashes($value, "\x00\n\r\\'\"\x1a") . '\'';
+ }
+
+ /**
+ * Quote value list
+ *
+ * @param string|string[] $valueList
+ * @return string
+ */
+ public function quoteValueList($valueList)
+ {
+ if (!is_array($valueList)) {
+ return $this->quoteValue($valueList);
+ }
+
+ $value = reset($valueList);
+ do {
+ $valueList[key($valueList)] = $this->quoteValue($value);
+ } while ($value = next($valueList));
+ return implode(', ', $valueList);
+ }
+
+ /**
+ * Get identifier separator
+ *
+ * @return string
+ */
+ public function getIdentifierSeparator()
+ {
+ return '.';
+ }
+
+ /**
+ * Quote identifier in fragment
+ *
+ * @param string $identifier
+ * @param array $safeWords
+ * @return string
+ */
+ public function quoteIdentifierInFragment($identifier, array $safeWords = array())
+ {
+ $parts = preg_split('#([\.\s\W])#', $identifier, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+ if ($safeWords) {
+ $safeWords = array_flip($safeWords);
+ $safeWords = array_change_key_case($safeWords, CASE_LOWER);
+ }
+ foreach ($parts as $i => $part) {
+ if ($safeWords && isset($safeWords[strtolower($part)])) {
+ continue;
+ }
+ switch ($part) {
+ case ' ':
+ case '.':
+ case '*':
+ case 'AS':
+ case 'As':
+ case 'aS':
+ case 'as':
+ break;
+ default:
+ $parts[$i] = '"' . str_replace('"', '\\' . '"', $part) . '"';
+ }
+ }
+ return implode('', $parts);
+ }
+}
diff --git a/library/Zend/Db/Adapter/Platform/Sql92.php b/library/Zend/Db/Adapter/Platform/Sql92.php
new file mode 100755
index 0000000000..bbeda46d7e
--- /dev/null
+++ b/library/Zend/Db/Adapter/Platform/Sql92.php
@@ -0,0 +1,161 @@
+quoteValue($valueList);
+ }
+
+ $value = reset($valueList);
+ do {
+ $valueList[key($valueList)] = $this->quoteValue($value);
+ } while ($value = next($valueList));
+ return implode(', ', $valueList);
+ }
+
+ /**
+ * Get identifier separator
+ *
+ * @return string
+ */
+ public function getIdentifierSeparator()
+ {
+ return '.';
+ }
+
+ /**
+ * Quote identifier in fragment
+ *
+ * @param string $identifier
+ * @param array $safeWords
+ * @return string
+ */
+ public function quoteIdentifierInFragment($identifier, array $safeWords = array())
+ {
+ $parts = preg_split('#([\.\s\W])#', $identifier, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+ if ($safeWords) {
+ $safeWords = array_flip($safeWords);
+ $safeWords = array_change_key_case($safeWords, CASE_LOWER);
+ }
+ foreach ($parts as $i => $part) {
+ if ($safeWords && isset($safeWords[strtolower($part)])) {
+ continue;
+ }
+
+ switch ($part) {
+ case ' ':
+ case '.':
+ case '*':
+ case 'AS':
+ case 'As':
+ case 'aS':
+ case 'as':
+ break;
+ default:
+ $parts[$i] = '"' . str_replace('"', '\\' . '"', $part) . '"';
+ }
+ }
+
+ return implode('', $parts);
+ }
+}
diff --git a/library/Zend/Db/Adapter/Platform/SqlServer.php b/library/Zend/Db/Adapter/Platform/SqlServer.php
new file mode 100755
index 0000000000..74a9acb9cc
--- /dev/null
+++ b/library/Zend/Db/Adapter/Platform/SqlServer.php
@@ -0,0 +1,203 @@
+setDriver($driver);
+ }
+ }
+
+ /**
+ * @param \Zend\Db\Adapter\Driver\Sqlsrv\Sqlsrv|\Zend\Db\Adapter\Driver\Pdo\Pdo||resource|\PDO $driver
+ * @throws \Zend\Db\Adapter\Exception\InvalidArgumentException
+ * @return $this
+ */
+ public function setDriver($driver)
+ {
+ // handle Zend\Db drivers
+ if (($driver instanceof Pdo\Pdo && in_array($driver->getDatabasePlatformName(), array('SqlServer', 'Dblib')))
+ || (($driver instanceof \PDO && in_array($driver->getAttribute(\PDO::ATTR_DRIVER_NAME), array('sqlsrv', 'dblib'))))
+ ) {
+ $this->resource = $driver;
+ return $this;
+ }
+
+ throw new Exception\InvalidArgumentException('$driver must be a Sqlsrv PDO Zend\Db\Adapter\Driver or Sqlsrv PDO instance');
+ }
+
+ /**
+ * Get name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return 'SQLServer';
+ }
+
+ /**
+ * Get quote identifier symbol
+ *
+ * @return string
+ */
+ public function getQuoteIdentifierSymbol()
+ {
+ return array('[', ']');
+ }
+
+ /**
+ * Quote identifier
+ *
+ * @param string $identifier
+ * @return string
+ */
+ public function quoteIdentifier($identifier)
+ {
+ return '[' . $identifier . ']';
+ }
+
+ /**
+ * Quote identifier chain
+ *
+ * @param string|string[] $identifierChain
+ * @return string
+ */
+ public function quoteIdentifierChain($identifierChain)
+ {
+ if (is_array($identifierChain)) {
+ $identifierChain = implode('].[', $identifierChain);
+ }
+ return '[' . $identifierChain . ']';
+ }
+
+ /**
+ * Get quote value symbol
+ *
+ * @return string
+ */
+ public function getQuoteValueSymbol()
+ {
+ return '\'';
+ }
+
+ /**
+ * Quote value
+ *
+ * @param string $value
+ * @return string
+ */
+ public function quoteValue($value)
+ {
+ if ($this->resource instanceof DriverInterface) {
+ $this->resource = $this->resource->getConnection()->getResource();
+ }
+ if ($this->resource instanceof \PDO) {
+ return $this->resource->quote($value);
+ }
+ trigger_error(
+ 'Attempting to quote a value in ' . __CLASS__ . ' without extension/driver support '
+ . 'can introduce security vulnerabilities in a production environment.'
+ );
+ $value = addcslashes($value, "\000\032");
+ return '\'' . str_replace('\'', '\'\'', $value) . '\'';
+ }
+
+ /**
+ * Quote Trusted Value
+ *
+ * The ability to quote values without notices
+ *
+ * @param $value
+ * @return mixed
+ */
+ public function quoteTrustedValue($value)
+ {
+ if ($this->resource instanceof DriverInterface) {
+ $this->resource = $this->resource->getConnection()->getResource();
+ }
+ if ($this->resource instanceof \PDO) {
+ return $this->resource->quote($value);
+ }
+ return '\'' . str_replace('\'', '\'\'', $value) . '\'';
+ }
+
+ /**
+ * Quote value list
+ *
+ * @param string|string[] $valueList
+ * @return string
+ */
+ public function quoteValueList($valueList)
+ {
+ if (!is_array($valueList)) {
+ return $this->quoteValue($valueList);
+ }
+ $value = reset($valueList);
+ do {
+ $valueList[key($valueList)] = $this->quoteValue($value);
+ } while ($value = next($valueList));
+ return implode(', ', $valueList);
+ }
+
+ /**
+ * Get identifier separator
+ *
+ * @return string
+ */
+ public function getIdentifierSeparator()
+ {
+ return '.';
+ }
+
+ /**
+ * Quote identifier in fragment
+ *
+ * @param string $identifier
+ * @param array $safeWords
+ * @return string
+ */
+ public function quoteIdentifierInFragment($identifier, array $safeWords = array())
+ {
+ $parts = preg_split('#([\.\s\W])#', $identifier, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+ if ($safeWords) {
+ $safeWords = array_flip($safeWords);
+ $safeWords = array_change_key_case($safeWords, CASE_LOWER);
+ }
+ foreach ($parts as $i => $part) {
+ if ($safeWords && isset($safeWords[strtolower($part)])) {
+ continue;
+ }
+ switch ($part) {
+ case ' ':
+ case '.':
+ case '*':
+ case 'AS':
+ case 'As':
+ case 'aS':
+ case 'as':
+ break;
+ default:
+ $parts[$i] = '[' . $part . ']';
+ }
+ }
+ return implode('', $parts);
+ }
+}
diff --git a/library/Zend/Db/Adapter/Platform/Sqlite.php b/library/Zend/Db/Adapter/Platform/Sqlite.php
new file mode 100755
index 0000000000..340c247e34
--- /dev/null
+++ b/library/Zend/Db/Adapter/Platform/Sqlite.php
@@ -0,0 +1,210 @@
+setDriver($driver);
+ }
+ }
+
+ /**
+ * @param \Zend\Db\Adapter\Driver\Pdo\Pdo||\PDO $driver
+ * @throws \Zend\Db\Adapter\Exception\InvalidArgumentException
+ * @return $this
+ */
+ public function setDriver($driver)
+ {
+ if (($driver instanceof \PDO && $driver->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'sqlite')
+ || ($driver instanceof Pdo\Pdo && $driver->getDatabasePlatformName() == 'Sqlite')
+ ) {
+ $this->resource = $driver;
+ return $this;
+ }
+
+ throw new Exception\InvalidArgumentException('$driver must be a Sqlite PDO Zend\Db\Adapter\Driver, Sqlite PDO instance');
+ }
+
+ /**
+ * Get name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return 'SQLite';
+ }
+
+ /**
+ * Get quote identifier symbol
+ *
+ * @return string
+ */
+ public function getQuoteIdentifierSymbol()
+ {
+ return '"';
+ }
+
+ /**
+ * Quote identifier
+ *
+ * @param string $identifier
+ * @return string
+ */
+ public function quoteIdentifier($identifier)
+ {
+ return '"' . str_replace('"', '\\' . '"', $identifier) . '"';
+ }
+
+ /**
+ * Quote identifier chain
+ *
+ * @param string|string[] $identifierChain
+ * @return string
+ */
+ public function quoteIdentifierChain($identifierChain)
+ {
+ $identifierChain = str_replace('"', '\\"', $identifierChain);
+ if (is_array($identifierChain)) {
+ $identifierChain = implode('"."', $identifierChain);
+ }
+ return '"' . $identifierChain . '"';
+ }
+
+ /**
+ * Get quote value symbol
+ *
+ * @return string
+ */
+ public function getQuoteValueSymbol()
+ {
+ return '\'';
+ }
+
+ /**
+ * Quote value
+ *
+ * @param string $value
+ * @return string
+ */
+ public function quoteValue($value)
+ {
+ $resource = $this->resource;
+
+ if ($resource instanceof DriverInterface) {
+ $resource = $resource->getConnection()->getResource();
+ }
+
+ if ($resource instanceof \PDO) {
+ return $resource->quote($value);
+ }
+
+ trigger_error(
+ 'Attempting to quote a value in ' . __CLASS__ . ' without extension/driver support '
+ . 'can introduce security vulnerabilities in a production environment.'
+ );
+ return '\'' . addcslashes($value, "\x00\n\r\\'\"\x1a") . '\'';
+ }
+
+ /**
+ * Quote Trusted Value
+ *
+ * The ability to quote values without notices
+ *
+ * @param $value
+ * @return mixed
+ */
+ public function quoteTrustedValue($value)
+ {
+ $resource = $this->resource;
+
+ if ($resource instanceof DriverInterface) {
+ $resource = $resource->getConnection()->getResource();
+ }
+
+ if ($resource instanceof \PDO) {
+ return $resource->quote($value);
+ }
+
+ return '\'' . addcslashes($value, "\x00\n\r\\'\"\x1a") . '\'';
+ }
+
+ /**
+ * Quote value list
+ *
+ * @param string|string[] $valueList
+ * @return string
+ */
+ public function quoteValueList($valueList)
+ {
+ if (!is_array($valueList)) {
+ return $this->quoteValue($valueList);
+ }
+ $value = reset($valueList);
+ do {
+ $valueList[key($valueList)] = $this->quoteValue($value);
+ } while ($value = next($valueList));
+ return implode(', ', $valueList);
+ }
+
+ /**
+ * Get identifier separator
+ *
+ * @return string
+ */
+ public function getIdentifierSeparator()
+ {
+ return '.';
+ }
+
+ /**
+ * Quote identifier in fragment
+ *
+ * @param string $identifier
+ * @param array $safeWords
+ * @return string
+ */
+ public function quoteIdentifierInFragment($identifier, array $safeWords = array())
+ {
+ $parts = preg_split('#([\.\s\W])#', $identifier, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+ if ($safeWords) {
+ $safeWords = array_flip($safeWords);
+ $safeWords = array_change_key_case($safeWords, CASE_LOWER);
+ }
+ foreach ($parts as $i => $part) {
+ if ($safeWords && isset($safeWords[strtolower($part)])) {
+ continue;
+ }
+ switch ($part) {
+ case ' ':
+ case '.':
+ case '*':
+ case 'AS':
+ case 'As':
+ case 'aS':
+ case 'as':
+ break;
+ default:
+ $parts[$i] = '"' . str_replace('"', '\\' . '"', $part) . '"';
+ }
+ }
+ return implode('', $parts);
+ }
+}
diff --git a/library/Zend/Db/Adapter/Profiler/Profiler.php b/library/Zend/Db/Adapter/Profiler/Profiler.php
new file mode 100755
index 0000000000..5115e3f3f1
--- /dev/null
+++ b/library/Zend/Db/Adapter/Profiler/Profiler.php
@@ -0,0 +1,85 @@
+ '',
+ 'parameters' => null,
+ 'start' => microtime(true),
+ 'end' => null,
+ 'elapse' => null
+ );
+ if ($target instanceof StatementContainerInterface) {
+ $profileInformation['sql'] = $target->getSql();
+ $profileInformation['parameters'] = clone $target->getParameterContainer();
+ } elseif (is_string($target)) {
+ $profileInformation['sql'] = $target;
+ } else {
+ throw new Exception\InvalidArgumentException(__FUNCTION__ . ' takes either a StatementContainer or a string');
+ }
+
+ $this->profiles[$this->currentIndex] = $profileInformation;
+
+ return $this;
+ }
+
+ /**
+ * @return Profiler
+ */
+ public function profilerFinish()
+ {
+ if (!isset($this->profiles[$this->currentIndex])) {
+ throw new Exception\RuntimeException('A profile must be started before ' . __FUNCTION__ . ' can be called.');
+ }
+ $current = &$this->profiles[$this->currentIndex];
+ $current['end'] = microtime(true);
+ $current['elapse'] = $current['end'] - $current['start'];
+ $this->currentIndex++;
+ return $this;
+ }
+
+ /**
+ * @return array|null
+ */
+ public function getLastProfile()
+ {
+ return end($this->profiles);
+ }
+
+ /**
+ * @return array
+ */
+ public function getProfiles()
+ {
+ return $this->profiles;
+ }
+}
diff --git a/library/Zend/Db/Adapter/Profiler/ProfilerAwareInterface.php b/library/Zend/Db/Adapter/Profiler/ProfilerAwareInterface.php
new file mode 100755
index 0000000000..a0b631d94b
--- /dev/null
+++ b/library/Zend/Db/Adapter/Profiler/ProfilerAwareInterface.php
@@ -0,0 +1,15 @@
+setSql($sql);
+ }
+ $this->parameterContainer = ($parameterContainer) ?: new ParameterContainer;
+ }
+
+ /**
+ * @param $sql
+ * @return StatementContainer
+ */
+ public function setSql($sql)
+ {
+ $this->sql = $sql;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSql()
+ {
+ return $this->sql;
+ }
+
+ /**
+ * @param ParameterContainer $parameterContainer
+ * @return StatementContainer
+ */
+ public function setParameterContainer(ParameterContainer $parameterContainer)
+ {
+ $this->parameterContainer = $parameterContainer;
+ return $this;
+ }
+
+ /**
+ * @return null|ParameterContainer
+ */
+ public function getParameterContainer()
+ {
+ return $this->parameterContainer;
+ }
+}
diff --git a/library/Zend/Db/Adapter/StatementContainerInterface.php b/library/Zend/Db/Adapter/StatementContainerInterface.php
new file mode 100755
index 0000000000..098d6a6fde
--- /dev/null
+++ b/library/Zend/Db/Adapter/StatementContainerInterface.php
@@ -0,0 +1,43 @@
+adapter = $adapter;
+ $this->source = $this->createSourceFromAdapter($adapter);
+ }
+
+ /**
+ * Create source from adapter
+ *
+ * @param Adapter $adapter
+ * @return Source\AbstractSource
+ */
+ protected function createSourceFromAdapter(Adapter $adapter)
+ {
+ switch ($adapter->getPlatform()->getName()) {
+ case 'MySQL':
+ return new Source\MysqlMetadata($adapter);
+ case 'SQLServer':
+ return new Source\SqlServerMetadata($adapter);
+ case 'SQLite':
+ return new Source\SqliteMetadata($adapter);
+ case 'PostgreSQL':
+ return new Source\PostgresqlMetadata($adapter);
+ case 'Oracle':
+ return new Source\OracleMetadata($adapter);
+ }
+
+ throw new \Exception('cannot create source from adapter');
+ }
+
+ // @todo methods
+
+ /**
+ * Get base tables and views
+ *
+ * @param string $schema
+ * @param bool $includeViews
+ * @return Object\TableObject[]
+ */
+ public function getTables($schema = null, $includeViews = false)
+ {
+ return $this->source->getTables($schema, $includeViews);
+ }
+
+ /**
+ * Get base tables and views
+ *
+ * @param string $schema
+ * @return Object\TableObject[]
+ */
+ public function getViews($schema = null)
+ {
+ return $this->source->getViews($schema);
+ }
+
+ /**
+ * Get triggers
+ *
+ * @param string $schema
+ * @return array
+ */
+ public function getTriggers($schema = null)
+ {
+ return $this->source->getTriggers($schema);
+ }
+
+ /**
+ * Get constraints
+ *
+ * @param string $table
+ * @param string $schema
+ * @return array
+ */
+ public function getConstraints($table, $schema = null)
+ {
+ return $this->source->getConstraints($table, $schema);
+ }
+
+ /**
+ * Get columns
+ *
+ * @param string $table
+ * @param string $schema
+ * @return array
+ */
+ public function getColumns($table, $schema = null)
+ {
+ return $this->source->getColumns($table, $schema);
+ }
+
+ /**
+ * Get constraint keys
+ *
+ * @param string $constraint
+ * @param string $table
+ * @param string $schema
+ * @return array
+ */
+ public function getConstraintKeys($constraint, $table, $schema = null)
+ {
+ return $this->source->getConstraintKeys($constraint, $table, $schema);
+ }
+
+ /**
+ * Get constraints
+ *
+ * @param string $constraintName
+ * @param string $table
+ * @param string $schema
+ * @return Object\ConstraintObject
+ */
+ public function getConstraint($constraintName, $table, $schema = null)
+ {
+ return $this->source->getConstraint($constraintName, $table, $schema);
+ }
+
+ /**
+ * Get schemas
+ */
+ public function getSchemas()
+ {
+ return $this->source->getSchemas();
+ }
+
+ /**
+ * Get table names
+ *
+ * @param string $schema
+ * @param bool $includeViews
+ * @return array
+ */
+ public function getTableNames($schema = null, $includeViews = false)
+ {
+ return $this->source->getTableNames($schema, $includeViews);
+ }
+
+ /**
+ * Get table
+ *
+ * @param string $tableName
+ * @param string $schema
+ * @return Object\TableObject
+ */
+ public function getTable($tableName, $schema = null)
+ {
+ return $this->source->getTable($tableName, $schema);
+ }
+
+ /**
+ * Get views names
+ *
+ * @param string $schema
+ * @return \Zend\Db\Metadata\Object\TableObject
+ */
+ public function getViewNames($schema = null)
+ {
+ return $this->source->getViewNames($schema);
+ }
+
+ /**
+ * Get view
+ *
+ * @param string $viewName
+ * @param string $schema
+ * @return \Zend\Db\Metadata\Object\TableObject
+ */
+ public function getView($viewName, $schema = null)
+ {
+ return $this->source->getView($viewName, $schema);
+ }
+
+ /**
+ * Get trigger names
+ *
+ * @param string $schema
+ * @return array
+ */
+ public function getTriggerNames($schema = null)
+ {
+ return $this->source->getTriggerNames($schema);
+ }
+
+ /**
+ * Get trigger
+ *
+ * @param string $triggerName
+ * @param string $schema
+ * @return \Zend\Db\Metadata\Object\TriggerObject
+ */
+ public function getTrigger($triggerName, $schema = null)
+ {
+ return $this->source->getTrigger($triggerName, $schema);
+ }
+
+ /**
+ * Get column names
+ *
+ * @param string $table
+ * @param string $schema
+ * @return array
+ */
+ public function getColumnNames($table, $schema = null)
+ {
+ return $this->source->getColumnNames($table, $schema);
+ }
+
+ /**
+ * Get column
+ *
+ * @param string $columnName
+ * @param string $table
+ * @param string $schema
+ * @return \Zend\Db\Metadata\Object\ColumnObject
+ */
+ public function getColumn($columnName, $table, $schema = null)
+ {
+ return $this->source->getColumn($columnName, $table, $schema);
+ }
+}
diff --git a/library/Zend/Db/Metadata/MetadataInterface.php b/library/Zend/Db/Metadata/MetadataInterface.php
new file mode 100755
index 0000000000..f0f58eb30b
--- /dev/null
+++ b/library/Zend/Db/Metadata/MetadataInterface.php
@@ -0,0 +1,35 @@
+setName($name);
+ }
+ }
+
+ /**
+ * Set columns
+ *
+ * @param array $columns
+ */
+ public function setColumns(array $columns)
+ {
+ $this->columns = $columns;
+ }
+
+ /**
+ * Get columns
+ *
+ * @return array
+ */
+ public function getColumns()
+ {
+ return $this->columns;
+ }
+
+ /**
+ * Set constraints
+ *
+ * @param array $constraints
+ */
+ public function setConstraints($constraints)
+ {
+ $this->constraints = $constraints;
+ }
+
+ /**
+ * Get constraints
+ *
+ * @return array
+ */
+ public function getConstraints()
+ {
+ return $this->constraints;
+ }
+
+ /**
+ * Set name
+ *
+ * @param string $name
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * Get name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+}
diff --git a/library/Zend/Db/Metadata/Object/ColumnObject.php b/library/Zend/Db/Metadata/Object/ColumnObject.php
new file mode 100755
index 0000000000..e76a91a996
--- /dev/null
+++ b/library/Zend/Db/Metadata/Object/ColumnObject.php
@@ -0,0 +1,388 @@
+setName($name);
+ $this->setTableName($tableName);
+ $this->setSchemaName($schemaName);
+ }
+
+ /**
+ * Set name
+ *
+ * @param string $name
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * Get name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Get table name
+ *
+ * @return string
+ */
+ public function getTableName()
+ {
+ return $this->tableName;
+ }
+
+ /**
+ * Set table name
+ *
+ * @param string $tableName
+ * @return ColumnObject
+ */
+ public function setTableName($tableName)
+ {
+ $this->tableName = $tableName;
+ return $this;
+ }
+
+ /**
+ * Set schema name
+ *
+ * @param string $schemaName
+ */
+ public function setSchemaName($schemaName)
+ {
+ $this->schemaName = $schemaName;
+ }
+
+ /**
+ * Get schema name
+ *
+ * @return string
+ */
+ public function getSchemaName()
+ {
+ return $this->schemaName;
+ }
+
+ /**
+ * @return int $ordinalPosition
+ */
+ public function getOrdinalPosition()
+ {
+ return $this->ordinalPosition;
+ }
+
+ /**
+ * @param int $ordinalPosition to set
+ * @return ColumnObject
+ */
+ public function setOrdinalPosition($ordinalPosition)
+ {
+ $this->ordinalPosition = $ordinalPosition;
+ return $this;
+ }
+
+ /**
+ * @return null|string the $columnDefault
+ */
+ public function getColumnDefault()
+ {
+ return $this->columnDefault;
+ }
+
+ /**
+ * @param mixed $columnDefault to set
+ * @return ColumnObject
+ */
+ public function setColumnDefault($columnDefault)
+ {
+ $this->columnDefault = $columnDefault;
+ return $this;
+ }
+
+ /**
+ * @return bool $isNullable
+ */
+ public function getIsNullable()
+ {
+ return $this->isNullable;
+ }
+
+ /**
+ * @param bool $isNullable to set
+ * @return ColumnObject
+ */
+ public function setIsNullable($isNullable)
+ {
+ $this->isNullable = $isNullable;
+ return $this;
+ }
+
+ /**
+ * @return bool $isNullable
+ */
+ public function isNullable()
+ {
+ return $this->isNullable;
+ }
+
+ /**
+ * @return null|string the $dataType
+ */
+ public function getDataType()
+ {
+ return $this->dataType;
+ }
+
+ /**
+ * @param string $dataType the $dataType to set
+ * @return ColumnObject
+ */
+ public function setDataType($dataType)
+ {
+ $this->dataType = $dataType;
+ return $this;
+ }
+
+ /**
+ * @return int|null the $characterMaximumLength
+ */
+ public function getCharacterMaximumLength()
+ {
+ return $this->characterMaximumLength;
+ }
+
+ /**
+ * @param int $characterMaximumLength the $characterMaximumLength to set
+ * @return ColumnObject
+ */
+ public function setCharacterMaximumLength($characterMaximumLength)
+ {
+ $this->characterMaximumLength = $characterMaximumLength;
+ return $this;
+ }
+
+ /**
+ * @return int|null the $characterOctetLength
+ */
+ public function getCharacterOctetLength()
+ {
+ return $this->characterOctetLength;
+ }
+
+ /**
+ * @param int $characterOctetLength the $characterOctetLength to set
+ * @return ColumnObject
+ */
+ public function setCharacterOctetLength($characterOctetLength)
+ {
+ $this->characterOctetLength = $characterOctetLength;
+ return $this;
+ }
+
+ /**
+ * @return int the $numericPrecision
+ */
+ public function getNumericPrecision()
+ {
+ return $this->numericPrecision;
+ }
+
+ /**
+ * @param int $numericPrecision the $numericPrevision to set
+ * @return ColumnObject
+ */
+ public function setNumericPrecision($numericPrecision)
+ {
+ $this->numericPrecision = $numericPrecision;
+ return $this;
+ }
+
+ /**
+ * @return int the $numericScale
+ */
+ public function getNumericScale()
+ {
+ return $this->numericScale;
+ }
+
+ /**
+ * @param int $numericScale the $numericScale to set
+ * @return ColumnObject
+ */
+ public function setNumericScale($numericScale)
+ {
+ $this->numericScale = $numericScale;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getNumericUnsigned()
+ {
+ return $this->numericUnsigned;
+ }
+
+ /**
+ * @param bool $numericUnsigned
+ * @return ColumnObject
+ */
+ public function setNumericUnsigned($numericUnsigned)
+ {
+ $this->numericUnsigned = $numericUnsigned;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isNumericUnsigned()
+ {
+ return $this->numericUnsigned;
+ }
+
+ /**
+ * @return array the $errata
+ */
+ public function getErratas()
+ {
+ return $this->errata;
+ }
+
+ /**
+ * @param array $erratas
+ * @return ColumnObject
+ */
+ public function setErratas(array $erratas)
+ {
+ foreach ($erratas as $name => $value) {
+ $this->setErrata($name, $value);
+ }
+ return $this;
+ }
+
+ /**
+ * @param string $errataName
+ * @return mixed
+ */
+ public function getErrata($errataName)
+ {
+ if (array_key_exists($errataName, $this->errata)) {
+ return $this->errata[$errataName];
+ }
+ return null;
+ }
+
+ /**
+ * @param string $errataName
+ * @param mixed $errataValue
+ * @return ColumnObject
+ */
+ public function setErrata($errataName, $errataValue)
+ {
+ $this->errata[$errataName] = $errataValue;
+ return $this;
+ }
+}
diff --git a/library/Zend/Db/Metadata/Object/ConstraintKeyObject.php b/library/Zend/Db/Metadata/Object/ConstraintKeyObject.php
new file mode 100755
index 0000000000..5683688057
--- /dev/null
+++ b/library/Zend/Db/Metadata/Object/ConstraintKeyObject.php
@@ -0,0 +1,249 @@
+setColumnName($column);
+ }
+
+ /**
+ * Get column name
+ *
+ * @return string
+ */
+ public function getColumnName()
+ {
+ return $this->columnName;
+ }
+
+ /**
+ * Set column name
+ *
+ * @param string $columnName
+ * @return ConstraintKeyObject
+ */
+ public function setColumnName($columnName)
+ {
+ $this->columnName = $columnName;
+ return $this;
+ }
+
+ /**
+ * Get ordinal position
+ *
+ * @return int
+ */
+ public function getOrdinalPosition()
+ {
+ return $this->ordinalPosition;
+ }
+
+ /**
+ * Set ordinal position
+ *
+ * @param int $ordinalPosition
+ * @return ConstraintKeyObject
+ */
+ public function setOrdinalPosition($ordinalPosition)
+ {
+ $this->ordinalPosition = $ordinalPosition;
+ return $this;
+ }
+
+ /**
+ * Get position in unique constraint
+ *
+ * @return bool
+ */
+ public function getPositionInUniqueConstraint()
+ {
+ return $this->positionInUniqueConstraint;
+ }
+
+ /**
+ * Set position in unique constraint
+ *
+ * @param bool $positionInUniqueConstraint
+ * @return ConstraintKeyObject
+ */
+ public function setPositionInUniqueConstraint($positionInUniqueConstraint)
+ {
+ $this->positionInUniqueConstraint = $positionInUniqueConstraint;
+ return $this;
+ }
+
+ /**
+ * Get referencred table schema
+ *
+ * @return string
+ */
+ public function getReferencedTableSchema()
+ {
+ return $this->referencedTableSchema;
+ }
+
+ /**
+ * Set referenced table schema
+ *
+ * @param string $referencedTableSchema
+ * @return ConstraintKeyObject
+ */
+ public function setReferencedTableSchema($referencedTableSchema)
+ {
+ $this->referencedTableSchema = $referencedTableSchema;
+ return $this;
+ }
+
+ /**
+ * Get referenced table name
+ *
+ * @return string
+ */
+ public function getReferencedTableName()
+ {
+ return $this->referencedTableName;
+ }
+
+ /**
+ * Set Referenced table name
+ *
+ * @param string $referencedTableName
+ * @return ConstraintKeyObject
+ */
+ public function setReferencedTableName($referencedTableName)
+ {
+ $this->referencedTableName = $referencedTableName;
+ return $this;
+ }
+
+ /**
+ * Get referenced column name
+ *
+ * @return string
+ */
+ public function getReferencedColumnName()
+ {
+ return $this->referencedColumnName;
+ }
+
+ /**
+ * Set referenced column name
+ *
+ * @param string $referencedColumnName
+ * @return ConstraintKeyObject
+ */
+ public function setReferencedColumnName($referencedColumnName)
+ {
+ $this->referencedColumnName = $referencedColumnName;
+ return $this;
+ }
+
+ /**
+ * set foreign key update rule
+ *
+ * @param string $foreignKeyUpdateRule
+ */
+ public function setForeignKeyUpdateRule($foreignKeyUpdateRule)
+ {
+ $this->foreignKeyUpdateRule = $foreignKeyUpdateRule;
+ }
+
+ /**
+ * Get foreign key update rule
+ *
+ * @return string
+ */
+ public function getForeignKeyUpdateRule()
+ {
+ return $this->foreignKeyUpdateRule;
+ }
+
+ /**
+ * Set foreign key delete rule
+ *
+ * @param string $foreignKeyDeleteRule
+ */
+ public function setForeignKeyDeleteRule($foreignKeyDeleteRule)
+ {
+ $this->foreignKeyDeleteRule = $foreignKeyDeleteRule;
+ }
+
+ /**
+ * get foreign key delete rule
+ *
+ * @return string
+ */
+ public function getForeignKeyDeleteRule()
+ {
+ return $this->foreignKeyDeleteRule;
+ }
+}
diff --git a/library/Zend/Db/Metadata/Object/ConstraintObject.php b/library/Zend/Db/Metadata/Object/ConstraintObject.php
new file mode 100755
index 0000000000..089c5ea1fb
--- /dev/null
+++ b/library/Zend/Db/Metadata/Object/ConstraintObject.php
@@ -0,0 +1,411 @@
+setName($name);
+ $this->setTableName($tableName);
+ $this->setSchemaName($schemaName);
+ }
+
+ /**
+ * Set name
+ *
+ * @param string $name
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * Get name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Set schema name
+ *
+ * @param string $schemaName
+ */
+ public function setSchemaName($schemaName)
+ {
+ $this->schemaName = $schemaName;
+ }
+
+ /**
+ * Get schema name
+ *
+ * @return string
+ */
+ public function getSchemaName()
+ {
+ return $this->schemaName;
+ }
+
+ /**
+ * Get table name
+ *
+ * @return string
+ */
+ public function getTableName()
+ {
+ return $this->tableName;
+ }
+
+ /**
+ * Set table name
+ *
+ * @param string $tableName
+ * @return ConstraintObject
+ */
+ public function setTableName($tableName)
+ {
+ $this->tableName = $tableName;
+ return $this;
+ }
+
+ /**
+ * Set type
+ *
+ * @param string $type
+ */
+ public function setType($type)
+ {
+ $this->type = $type;
+ }
+
+ /**
+ * Get type
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ public function hasColumns()
+ {
+ return (!empty($this->columns));
+ }
+
+ /**
+ * Get Columns.
+ *
+ * @return string[]
+ */
+ public function getColumns()
+ {
+ return $this->columns;
+ }
+
+ /**
+ * Set Columns.
+ *
+ * @param string[] $columns
+ * @return ConstraintObject
+ */
+ public function setColumns(array $columns)
+ {
+ $this->columns = $columns;
+ return $this;
+ }
+
+ /**
+ * Get Referenced Table Schema.
+ *
+ * @return string
+ */
+ public function getReferencedTableSchema()
+ {
+ return $this->referencedTableSchema;
+ }
+
+ /**
+ * Set Referenced Table Schema.
+ *
+ * @param string $referencedTableSchema
+ * @return ConstraintObject
+ */
+ public function setReferencedTableSchema($referencedTableSchema)
+ {
+ $this->referencedTableSchema = $referencedTableSchema;
+ return $this;
+ }
+
+ /**
+ * Get Referenced Table Name.
+ *
+ * @return string
+ */
+ public function getReferencedTableName()
+ {
+ return $this->referencedTableName;
+ }
+
+ /**
+ * Set Referenced Table Name.
+ *
+ * @param string $referencedTableName
+ * @return ConstraintObject
+ */
+ public function setReferencedTableName($referencedTableName)
+ {
+ $this->referencedTableName = $referencedTableName;
+ return $this;
+ }
+
+ /**
+ * Get Referenced Columns.
+ *
+ * @return string[]
+ */
+ public function getReferencedColumns()
+ {
+ return $this->referencedColumns;
+ }
+
+ /**
+ * Set Referenced Columns.
+ *
+ * @param string[] $referencedColumns
+ * @return ConstraintObject
+ */
+ public function setReferencedColumns(array $referencedColumns)
+ {
+ $this->referencedColumns = $referencedColumns;
+ return $this;
+ }
+
+ /**
+ * Get Match Option.
+ *
+ * @return string
+ */
+ public function getMatchOption()
+ {
+ return $this->matchOption;
+ }
+
+ /**
+ * Set Match Option.
+ *
+ * @param string $matchOption
+ * @return ConstraintObject
+ */
+ public function setMatchOption($matchOption)
+ {
+ $this->matchOption = $matchOption;
+ return $this;
+ }
+
+ /**
+ * Get Update Rule.
+ *
+ * @return string
+ */
+ public function getUpdateRule()
+ {
+ return $this->updateRule;
+ }
+
+ /**
+ * Set Update Rule.
+ *
+ * @param string $updateRule
+ * @return ConstraintObject
+ */
+ public function setUpdateRule($updateRule)
+ {
+ $this->updateRule = $updateRule;
+ return $this;
+ }
+
+ /**
+ * Get Delete Rule.
+ *
+ * @return string
+ */
+ public function getDeleteRule()
+ {
+ return $this->deleteRule;
+ }
+
+ /**
+ * Set Delete Rule.
+ *
+ * @param string $deleteRule
+ * @return ConstraintObject
+ */
+ public function setDeleteRule($deleteRule)
+ {
+ $this->deleteRule = $deleteRule;
+ return $this;
+ }
+
+ /**
+ * Get Check Clause.
+ *
+ * @return string
+ */
+ public function getCheckClause()
+ {
+ return $this->checkClause;
+ }
+
+ /**
+ * Set Check Clause.
+ *
+ * @param string $checkClause
+ * @return ConstraintObject
+ */
+ public function setCheckClause($checkClause)
+ {
+ $this->checkClause = $checkClause;
+ return $this;
+ }
+
+ /**
+ * Is primary key
+ *
+ * @return bool
+ */
+ public function isPrimaryKey()
+ {
+ return ('PRIMARY KEY' == $this->type);
+ }
+
+ /**
+ * Is unique key
+ *
+ * @return bool
+ */
+ public function isUnique()
+ {
+ return ('UNIQUE' == $this->type);
+ }
+
+ /**
+ * Is foreign key
+ *
+ * @return bool
+ */
+ public function isForeignKey()
+ {
+ return ('FOREIGN KEY' == $this->type);
+ }
+
+ /**
+ * Is foreign key
+ *
+ * @return bool
+ */
+ public function isCheck()
+ {
+ return ('CHECK' == $this->type);
+ }
+}
diff --git a/library/Zend/Db/Metadata/Object/TableObject.php b/library/Zend/Db/Metadata/Object/TableObject.php
new file mode 100755
index 0000000000..8735fbfc8c
--- /dev/null
+++ b/library/Zend/Db/Metadata/Object/TableObject.php
@@ -0,0 +1,14 @@
+name;
+ }
+
+ /**
+ * Set Name.
+ *
+ * @param string $name
+ * @return TriggerObject
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * Get Event Manipulation.
+ *
+ * @return string
+ */
+ public function getEventManipulation()
+ {
+ return $this->eventManipulation;
+ }
+
+ /**
+ * Set Event Manipulation.
+ *
+ * @param string $eventManipulation
+ * @return TriggerObject
+ */
+ public function setEventManipulation($eventManipulation)
+ {
+ $this->eventManipulation = $eventManipulation;
+ return $this;
+ }
+
+ /**
+ * Get Event Object Catalog.
+ *
+ * @return string
+ */
+ public function getEventObjectCatalog()
+ {
+ return $this->eventObjectCatalog;
+ }
+
+ /**
+ * Set Event Object Catalog.
+ *
+ * @param string $eventObjectCatalog
+ * @return TriggerObject
+ */
+ public function setEventObjectCatalog($eventObjectCatalog)
+ {
+ $this->eventObjectCatalog = $eventObjectCatalog;
+ return $this;
+ }
+
+ /**
+ * Get Event Object Schema.
+ *
+ * @return string
+ */
+ public function getEventObjectSchema()
+ {
+ return $this->eventObjectSchema;
+ }
+
+ /**
+ * Set Event Object Schema.
+ *
+ * @param string $eventObjectSchema
+ * @return TriggerObject
+ */
+ public function setEventObjectSchema($eventObjectSchema)
+ {
+ $this->eventObjectSchema = $eventObjectSchema;
+ return $this;
+ }
+
+ /**
+ * Get Event Object Table.
+ *
+ * @return string
+ */
+ public function getEventObjectTable()
+ {
+ return $this->eventObjectTable;
+ }
+
+ /**
+ * Set Event Object Table.
+ *
+ * @param string $eventObjectTable
+ * @return TriggerObject
+ */
+ public function setEventObjectTable($eventObjectTable)
+ {
+ $this->eventObjectTable = $eventObjectTable;
+ return $this;
+ }
+
+ /**
+ * Get Action Order.
+ *
+ * @return string
+ */
+ public function getActionOrder()
+ {
+ return $this->actionOrder;
+ }
+
+ /**
+ * Set Action Order.
+ *
+ * @param string $actionOrder
+ * @return TriggerObject
+ */
+ public function setActionOrder($actionOrder)
+ {
+ $this->actionOrder = $actionOrder;
+ return $this;
+ }
+
+ /**
+ * Get Action Condition.
+ *
+ * @return string
+ */
+ public function getActionCondition()
+ {
+ return $this->actionCondition;
+ }
+
+ /**
+ * Set Action Condition.
+ *
+ * @param string $actionCondition
+ * @return TriggerObject
+ */
+ public function setActionCondition($actionCondition)
+ {
+ $this->actionCondition = $actionCondition;
+ return $this;
+ }
+
+ /**
+ * Get Action Statement.
+ *
+ * @return string
+ */
+ public function getActionStatement()
+ {
+ return $this->actionStatement;
+ }
+
+ /**
+ * Set Action Statement.
+ *
+ * @param string $actionStatement
+ * @return TriggerObject
+ */
+ public function setActionStatement($actionStatement)
+ {
+ $this->actionStatement = $actionStatement;
+ return $this;
+ }
+
+ /**
+ * Get Action Orientation.
+ *
+ * @return string
+ */
+ public function getActionOrientation()
+ {
+ return $this->actionOrientation;
+ }
+
+ /**
+ * Set Action Orientation.
+ *
+ * @param string $actionOrientation
+ * @return TriggerObject
+ */
+ public function setActionOrientation($actionOrientation)
+ {
+ $this->actionOrientation = $actionOrientation;
+ return $this;
+ }
+
+ /**
+ * Get Action Timing.
+ *
+ * @return string
+ */
+ public function getActionTiming()
+ {
+ return $this->actionTiming;
+ }
+
+ /**
+ * Set Action Timing.
+ *
+ * @param string $actionTiming
+ * @return TriggerObject
+ */
+ public function setActionTiming($actionTiming)
+ {
+ $this->actionTiming = $actionTiming;
+ return $this;
+ }
+
+ /**
+ * Get Action Reference Old Table.
+ *
+ * @return string
+ */
+ public function getActionReferenceOldTable()
+ {
+ return $this->actionReferenceOldTable;
+ }
+
+ /**
+ * Set Action Reference Old Table.
+ *
+ * @param string $actionReferenceOldTable
+ * @return TriggerObject
+ */
+ public function setActionReferenceOldTable($actionReferenceOldTable)
+ {
+ $this->actionReferenceOldTable = $actionReferenceOldTable;
+ return $this;
+ }
+
+ /**
+ * Get Action Reference New Table.
+ *
+ * @return string
+ */
+ public function getActionReferenceNewTable()
+ {
+ return $this->actionReferenceNewTable;
+ }
+
+ /**
+ * Set Action Reference New Table.
+ *
+ * @param string $actionReferenceNewTable
+ * @return TriggerObject
+ */
+ public function setActionReferenceNewTable($actionReferenceNewTable)
+ {
+ $this->actionReferenceNewTable = $actionReferenceNewTable;
+ return $this;
+ }
+
+ /**
+ * Get Action Reference Old Row.
+ *
+ * @return string
+ */
+ public function getActionReferenceOldRow()
+ {
+ return $this->actionReferenceOldRow;
+ }
+
+ /**
+ * Set Action Reference Old Row.
+ *
+ * @param string $actionReferenceOldRow
+ * @return TriggerObject
+ */
+ public function setActionReferenceOldRow($actionReferenceOldRow)
+ {
+ $this->actionReferenceOldRow = $actionReferenceOldRow;
+ return $this;
+ }
+
+ /**
+ * Get Action Reference New Row.
+ *
+ * @return string
+ */
+ public function getActionReferenceNewRow()
+ {
+ return $this->actionReferenceNewRow;
+ }
+
+ /**
+ * Set Action Reference New Row.
+ *
+ * @param string $actionReferenceNewRow
+ * @return TriggerObject
+ */
+ public function setActionReferenceNewRow($actionReferenceNewRow)
+ {
+ $this->actionReferenceNewRow = $actionReferenceNewRow;
+ return $this;
+ }
+
+ /**
+ * Get Created.
+ *
+ * @return \DateTime
+ */
+ public function getCreated()
+ {
+ return $this->created;
+ }
+
+ /**
+ * Set Created.
+ *
+ * @param \DateTime $created
+ * @return TriggerObject
+ */
+ public function setCreated($created)
+ {
+ $this->created = $created;
+ return $this;
+ }
+}
diff --git a/library/Zend/Db/Metadata/Object/ViewObject.php b/library/Zend/Db/Metadata/Object/ViewObject.php
new file mode 100755
index 0000000000..5130e9ecc6
--- /dev/null
+++ b/library/Zend/Db/Metadata/Object/ViewObject.php
@@ -0,0 +1,76 @@
+viewDefinition;
+ }
+
+ /**
+ * @param string $viewDefinition to set
+ * @return ViewObject
+ */
+ public function setViewDefinition($viewDefinition)
+ {
+ $this->viewDefinition = $viewDefinition;
+ return $this;
+ }
+
+ /**
+ * @return string $checkOption
+ */
+ public function getCheckOption()
+ {
+ return $this->checkOption;
+ }
+
+ /**
+ * @param string $checkOption to set
+ * @return ViewObject
+ */
+ public function setCheckOption($checkOption)
+ {
+ $this->checkOption = $checkOption;
+ return $this;
+ }
+
+ /**
+ * @return bool $isUpdatable
+ */
+ public function getIsUpdatable()
+ {
+ return $this->isUpdatable;
+ }
+
+ /**
+ * @param bool $isUpdatable to set
+ * @return ViewObject
+ */
+ public function setIsUpdatable($isUpdatable)
+ {
+ $this->isUpdatable = $isUpdatable;
+ return $this;
+ }
+
+ public function isUpdatable()
+ {
+ return $this->isUpdatable;
+ }
+}
diff --git a/library/Zend/Db/Metadata/Source/AbstractSource.php b/library/Zend/Db/Metadata/Source/AbstractSource.php
new file mode 100755
index 0000000000..63d63a928e
--- /dev/null
+++ b/library/Zend/Db/Metadata/Source/AbstractSource.php
@@ -0,0 +1,601 @@
+adapter = $adapter;
+ $this->defaultSchema = ($adapter->getCurrentSchema()) ?: self::DEFAULT_SCHEMA;
+ }
+
+ /**
+ * Get schemas
+ *
+ */
+ public function getSchemas()
+ {
+ $this->loadSchemaData();
+
+ return $this->data['schemas'];
+ }
+
+ /**
+ * Get table names
+ *
+ * @param string $schema
+ * @param bool $includeViews
+ * @return string[]
+ */
+ public function getTableNames($schema = null, $includeViews = false)
+ {
+ if ($schema === null) {
+ $schema = $this->defaultSchema;
+ }
+
+ $this->loadTableNameData($schema);
+
+ if ($includeViews) {
+ return array_keys($this->data['table_names'][$schema]);
+ }
+
+ $tableNames = array();
+ foreach ($this->data['table_names'][$schema] as $tableName => $data) {
+ if ('BASE TABLE' == $data['table_type']) {
+ $tableNames[] = $tableName;
+ }
+ }
+ return $tableNames;
+ }
+
+ /**
+ * Get tables
+ *
+ * @param string $schema
+ * @param bool $includeViews
+ * @return Object\TableObject[]
+ */
+ public function getTables($schema = null, $includeViews = false)
+ {
+ if ($schema === null) {
+ $schema = $this->defaultSchema;
+ }
+
+ $tables = array();
+ foreach ($this->getTableNames($schema, $includeViews) as $tableName) {
+ $tables[] = $this->getTable($tableName, $schema);
+ }
+ return $tables;
+ }
+
+ /**
+ * Get table
+ *
+ * @param string $tableName
+ * @param string $schema
+ * @return Object\TableObject
+ */
+ public function getTable($tableName, $schema = null)
+ {
+ if ($schema === null) {
+ $schema = $this->defaultSchema;
+ }
+
+ $this->loadTableNameData($schema);
+
+ if (!isset($this->data['table_names'][$schema][$tableName])) {
+ throw new \Exception('Table "' . $tableName . '" does not exist');
+ }
+
+ $data = $this->data['table_names'][$schema][$tableName];
+ switch ($data['table_type']) {
+ case 'BASE TABLE':
+ $table = new Object\TableObject($tableName);
+ break;
+ case 'VIEW':
+ $table = new Object\ViewObject($tableName);
+ $table->setViewDefinition($data['view_definition']);
+ $table->setCheckOption($data['check_option']);
+ $table->setIsUpdatable($data['is_updatable']);
+ break;
+ default:
+ throw new \Exception('Table "' . $tableName . '" is of an unsupported type "' . $data['table_type'] . '"');
+ }
+ $table->setColumns($this->getColumns($tableName, $schema));
+ $table->setConstraints($this->getConstraints($tableName, $schema));
+ return $table;
+ }
+
+ /**
+ * Get view names
+ *
+ * @param string $schema
+ * @return array
+ */
+ public function getViewNames($schema = null)
+ {
+ if ($schema === null) {
+ $schema = $this->defaultSchema;
+ }
+
+ $this->loadTableNameData($schema);
+
+ $viewNames = array();
+ foreach ($this->data['table_names'][$schema] as $tableName => $data) {
+ if ('VIEW' == $data['table_type']) {
+ $viewNames[] = $tableName;
+ }
+ }
+ return $viewNames;
+ }
+
+ /**
+ * Get views
+ *
+ * @param string $schema
+ * @return array
+ */
+ public function getViews($schema = null)
+ {
+ if ($schema === null) {
+ $schema = $this->defaultSchema;
+ }
+
+ $views = array();
+ foreach ($this->getViewNames($schema) as $tableName) {
+ $views[] = $this->getTable($tableName, $schema);
+ }
+ return $views;
+ }
+
+ /**
+ * Get view
+ *
+ * @param string $viewName
+ * @param string $schema
+ * @return \Zend\Db\Metadata\Object\TableObject
+ */
+ public function getView($viewName, $schema = null)
+ {
+ if ($schema === null) {
+ $schema = $this->defaultSchema;
+ }
+
+ $this->loadTableNameData($schema);
+
+ $tableNames = $this->data['table_names'][$schema];
+ if (isset($tableNames[$viewName]) && 'VIEW' == $tableNames[$viewName]['table_type']) {
+ return $this->getTable($viewName, $schema);
+ }
+ throw new \Exception('View "' . $viewName . '" does not exist');
+ }
+
+ /**
+ * Gt column names
+ *
+ * @param string $table
+ * @param string $schema
+ * @return array
+ */
+ public function getColumnNames($table, $schema = null)
+ {
+ if ($schema === null) {
+ $schema = $this->defaultSchema;
+ }
+
+ $this->loadColumnData($table, $schema);
+
+ if (!isset($this->data['columns'][$schema][$table])) {
+ throw new \Exception('"' . $table . '" does not exist');
+ }
+
+ return array_keys($this->data['columns'][$schema][$table]);
+ }
+
+ /**
+ * Get columns
+ *
+ * @param string $table
+ * @param string $schema
+ * @return array
+ */
+ public function getColumns($table, $schema = null)
+ {
+ if ($schema === null) {
+ $schema = $this->defaultSchema;
+ }
+
+ $this->loadColumnData($table, $schema);
+
+ $columns = array();
+ foreach ($this->getColumnNames($table, $schema) as $columnName) {
+ $columns[] = $this->getColumn($columnName, $table, $schema);
+ }
+ return $columns;
+ }
+
+ /**
+ * Get column
+ *
+ * @param string $columnName
+ * @param string $table
+ * @param string $schema
+ * @return Object\ColumnObject
+ */
+ public function getColumn($columnName, $table, $schema = null)
+ {
+ if ($schema === null) {
+ $schema = $this->defaultSchema;
+ }
+
+ $this->loadColumnData($table, $schema);
+
+ if (!isset($this->data['columns'][$schema][$table][$columnName])) {
+ throw new \Exception('A column by that name was not found.');
+ }
+
+ $info = $this->data['columns'][$schema][$table][$columnName];
+
+ $column = new Object\ColumnObject($columnName, $table, $schema);
+ $props = array(
+ 'ordinal_position', 'column_default', 'is_nullable',
+ 'data_type', 'character_maximum_length', 'character_octet_length',
+ 'numeric_precision', 'numeric_scale', 'numeric_unsigned',
+ 'erratas'
+ );
+ foreach ($props as $prop) {
+ if (isset($info[$prop])) {
+ $column->{'set' . str_replace('_', '', $prop)}($info[$prop]);
+ }
+ }
+
+ $column->setOrdinalPosition($info['ordinal_position']);
+ $column->setColumnDefault($info['column_default']);
+ $column->setIsNullable($info['is_nullable']);
+ $column->setDataType($info['data_type']);
+ $column->setCharacterMaximumLength($info['character_maximum_length']);
+ $column->setCharacterOctetLength($info['character_octet_length']);
+ $column->setNumericPrecision($info['numeric_precision']);
+ $column->setNumericScale($info['numeric_scale']);
+ $column->setNumericUnsigned($info['numeric_unsigned']);
+ $column->setErratas($info['erratas']);
+
+ return $column;
+ }
+
+ /**
+ * Get constraints
+ *
+ * @param string $table
+ * @param string $schema
+ * @return array
+ */
+ public function getConstraints($table, $schema = null)
+ {
+ if ($schema === null) {
+ $schema = $this->defaultSchema;
+ }
+
+ $this->loadConstraintData($table, $schema);
+
+ $constraints = array();
+ foreach (array_keys($this->data['constraints'][$schema][$table]) as $constraintName) {
+ $constraints[] = $this->getConstraint($constraintName, $table, $schema);
+ }
+
+ return $constraints;
+ }
+
+ /**
+ * Get constraint
+ *
+ * @param string $constraintName
+ * @param string $table
+ * @param string $schema
+ * @return Object\ConstraintObject
+ */
+ public function getConstraint($constraintName, $table, $schema = null)
+ {
+ if ($schema === null) {
+ $schema = $this->defaultSchema;
+ }
+
+ $this->loadConstraintData($table, $schema);
+
+ if (!isset($this->data['constraints'][$schema][$table][$constraintName])) {
+ throw new \Exception('Cannot find a constraint by that name in this table');
+ }
+
+ $info = $this->data['constraints'][$schema][$table][$constraintName];
+ $constraint = new Object\ConstraintObject($constraintName, $table, $schema);
+
+ foreach (array(
+ 'constraint_type' => 'setType',
+ 'match_option' => 'setMatchOption',
+ 'update_rule' => 'setUpdateRule',
+ 'delete_rule' => 'setDeleteRule',
+ 'columns' => 'setColumns',
+ 'referenced_table_schema' => 'setReferencedTableSchema',
+ 'referenced_table_name' => 'setReferencedTableName',
+ 'referenced_columns' => 'setReferencedColumns',
+ 'check_clause' => 'setCheckClause',
+ ) as $key => $setMethod) {
+ if (isset($info[$key])) {
+ $constraint->{$setMethod}($info[$key]);
+ }
+ }
+
+ return $constraint;
+ }
+
+ /**
+ * Get constraint keys
+ *
+ * @param string $constraint
+ * @param string $table
+ * @param string $schema
+ * @return array
+ */
+ public function getConstraintKeys($constraint, $table, $schema = null)
+ {
+ if ($schema === null) {
+ $schema = $this->defaultSchema;
+ }
+
+ $this->loadConstraintReferences($table, $schema);
+
+ // organize references first
+ $references = array();
+ foreach ($this->data['constraint_references'][$schema] as $refKeyInfo) {
+ if ($refKeyInfo['constraint_name'] == $constraint) {
+ $references[$refKeyInfo['constraint_name']] = $refKeyInfo;
+ }
+ }
+
+ $this->loadConstraintDataKeys($schema);
+
+ $keys = array();
+ foreach ($this->data['constraint_keys'][$schema] as $constraintKeyInfo) {
+ if ($constraintKeyInfo['table_name'] == $table && $constraintKeyInfo['constraint_name'] === $constraint) {
+ $keys[] = $key = new Object\ConstraintKeyObject($constraintKeyInfo['column_name']);
+ $key->setOrdinalPosition($constraintKeyInfo['ordinal_position']);
+ if (isset($references[$constraint])) {
+ //$key->setReferencedTableSchema($constraintKeyInfo['referenced_table_schema']);
+ $key->setForeignKeyUpdateRule($references[$constraint]['update_rule']);
+ $key->setForeignKeyDeleteRule($references[$constraint]['delete_rule']);
+ //$key->setReferencedTableSchema($references[$constraint]['referenced_table_schema']);
+ $key->setReferencedTableName($references[$constraint]['referenced_table_name']);
+ $key->setReferencedColumnName($references[$constraint]['referenced_column_name']);
+ }
+ }
+ }
+
+ return $keys;
+ }
+
+ /**
+ * Get trigger names
+ *
+ * @param string $schema
+ * @return array
+ */
+ public function getTriggerNames($schema = null)
+ {
+ if ($schema === null) {
+ $schema = $this->defaultSchema;
+ }
+
+ $this->loadTriggerData($schema);
+
+ return array_keys($this->data['triggers'][$schema]);
+ }
+
+ /**
+ * Get triggers
+ *
+ * @param string $schema
+ * @return array
+ */
+ public function getTriggers($schema = null)
+ {
+ if ($schema === null) {
+ $schema = $this->defaultSchema;
+ }
+
+ $triggers = array();
+ foreach ($this->getTriggerNames($schema) as $triggerName) {
+ $triggers[] = $this->getTrigger($triggerName, $schema);
+ }
+ return $triggers;
+ }
+
+ /**
+ * Get trigger
+ *
+ * @param string $triggerName
+ * @param string $schema
+ * @return Object\TriggerObject
+ */
+ public function getTrigger($triggerName, $schema = null)
+ {
+ if ($schema === null) {
+ $schema = $this->defaultSchema;
+ }
+
+ $this->loadTriggerData($schema);
+
+ if (!isset($this->data['triggers'][$schema][$triggerName])) {
+ throw new \Exception('Trigger "' . $triggerName . '" does not exist');
+ }
+
+ $info = $this->data['triggers'][$schema][$triggerName];
+
+ $trigger = new Object\TriggerObject();
+
+ $trigger->setName($triggerName);
+ $trigger->setEventManipulation($info['event_manipulation']);
+ $trigger->setEventObjectCatalog($info['event_object_catalog']);
+ $trigger->setEventObjectSchema($info['event_object_schema']);
+ $trigger->setEventObjectTable($info['event_object_table']);
+ $trigger->setActionOrder($info['action_order']);
+ $trigger->setActionCondition($info['action_condition']);
+ $trigger->setActionStatement($info['action_statement']);
+ $trigger->setActionOrientation($info['action_orientation']);
+ $trigger->setActionTiming($info['action_timing']);
+ $trigger->setActionReferenceOldTable($info['action_reference_old_table']);
+ $trigger->setActionReferenceNewTable($info['action_reference_new_table']);
+ $trigger->setActionReferenceOldRow($info['action_reference_old_row']);
+ $trigger->setActionReferenceNewRow($info['action_reference_new_row']);
+ $trigger->setCreated($info['created']);
+
+ return $trigger;
+ }
+
+ /**
+ * Prepare data hierarchy
+ *
+ * @param string $type
+ * @param string $key ...
+ */
+ protected function prepareDataHierarchy($type)
+ {
+ $data = &$this->data;
+ foreach (func_get_args() as $key) {
+ if (!isset($data[$key])) {
+ $data[$key] = array();
+ }
+ $data = &$data[$key];
+ }
+ }
+
+ /**
+ * Load schema data
+ */
+ protected function loadSchemaData()
+ {
+ }
+
+ /**
+ * Load table name data
+ *
+ * @param string $schema
+ */
+ protected function loadTableNameData($schema)
+ {
+ if (isset($this->data['table_names'][$schema])) {
+ return;
+ }
+
+ $this->prepareDataHierarchy('table_names', $schema);
+ }
+
+ /**
+ * Load column data
+ *
+ * @param string $table
+ * @param string $schema
+ */
+ protected function loadColumnData($table, $schema)
+ {
+ if (isset($this->data['columns'][$schema][$table])) {
+ return;
+ }
+
+ $this->prepareDataHierarchy('columns', $schema, $table);
+ }
+
+ /**
+ * Load constraint data
+ *
+ * @param string $table
+ * @param string $schema
+ */
+ protected function loadConstraintData($table, $schema)
+ {
+ if (isset($this->data['constraints'][$schema])) {
+ return;
+ }
+
+ $this->prepareDataHierarchy('constraints', $schema);
+ }
+
+ /**
+ * Load constraint data keys
+ *
+ * @param string $schema
+ */
+ protected function loadConstraintDataKeys($schema)
+ {
+ if (isset($this->data['constraint_keys'][$schema])) {
+ return;
+ }
+
+ $this->prepareDataHierarchy('constraint_keys', $schema);
+ }
+
+ /**
+ * Load constraint references
+ *
+ * @param string $table
+ * @param string $schema
+ */
+ protected function loadConstraintReferences($table, $schema)
+ {
+ if (isset($this->data['constraint_references'][$schema])) {
+ return;
+ }
+
+ $this->prepareDataHierarchy('constraint_references', $schema);
+ }
+
+ /**
+ * Load trigger data
+ *
+ * @param string $schema
+ */
+ protected function loadTriggerData($schema)
+ {
+ if (isset($this->data['triggers'][$schema])) {
+ return;
+ }
+
+ $this->prepareDataHierarchy('triggers', $schema);
+ }
+}
diff --git a/library/Zend/Db/Metadata/Source/MysqlMetadata.php b/library/Zend/Db/Metadata/Source/MysqlMetadata.php
new file mode 100755
index 0000000000..ac9642f9c1
--- /dev/null
+++ b/library/Zend/Db/Metadata/Source/MysqlMetadata.php
@@ -0,0 +1,493 @@
+data['schemas'])) {
+ return;
+ }
+ $this->prepareDataHierarchy('schemas');
+
+ $p = $this->adapter->getPlatform();
+
+ $sql = 'SELECT ' . $p->quoteIdentifier('SCHEMA_NAME')
+ . ' FROM ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'SCHEMATA'))
+ . ' WHERE ' . $p->quoteIdentifier('SCHEMA_NAME')
+ . ' != \'INFORMATION_SCHEMA\'';
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+
+ $schemas = array();
+ foreach ($results->toArray() as $row) {
+ $schemas[] = $row['SCHEMA_NAME'];
+ }
+
+ $this->data['schemas'] = $schemas;
+ }
+
+ protected function loadTableNameData($schema)
+ {
+ if (isset($this->data['table_names'][$schema])) {
+ return;
+ }
+ $this->prepareDataHierarchy('table_names', $schema);
+
+ $p = $this->adapter->getPlatform();
+
+ $isColumns = array(
+ array('T', 'TABLE_NAME'),
+ array('T', 'TABLE_TYPE'),
+ array('V', 'VIEW_DEFINITION'),
+ array('V', 'CHECK_OPTION'),
+ array('V', 'IS_UPDATABLE'),
+ );
+
+ array_walk($isColumns, function (&$c) use ($p) { $c = $p->quoteIdentifierChain($c); });
+
+ $sql = 'SELECT ' . implode(', ', $isColumns)
+ . ' FROM ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'TABLES')) . 'T'
+
+ . ' LEFT JOIN ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'VIEWS')) . ' V'
+ . ' ON ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' = ' . $p->quoteIdentifierChain(array('V', 'TABLE_SCHEMA'))
+ . ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_NAME'))
+ . ' = ' . $p->quoteIdentifierChain(array('V', 'TABLE_NAME'))
+
+ . ' WHERE ' . $p->quoteIdentifierChain(array('T', 'TABLE_TYPE'))
+ . ' IN (\'BASE TABLE\', \'VIEW\')';
+
+ if ($schema != self::DEFAULT_SCHEMA) {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' = ' . $p->quoteTrustedValue($schema);
+ } else {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' != \'INFORMATION_SCHEMA\'';
+ }
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+
+ $tables = array();
+ foreach ($results->toArray() as $row) {
+ $tables[$row['TABLE_NAME']] = array(
+ 'table_type' => $row['TABLE_TYPE'],
+ 'view_definition' => $row['VIEW_DEFINITION'],
+ 'check_option' => $row['CHECK_OPTION'],
+ 'is_updatable' => ('YES' == $row['IS_UPDATABLE']),
+ );
+ }
+
+ $this->data['table_names'][$schema] = $tables;
+ }
+
+ protected function loadColumnData($table, $schema)
+ {
+ if (isset($this->data['columns'][$schema][$table])) {
+ return;
+ }
+ $this->prepareDataHierarchy('columns', $schema, $table);
+ $p = $this->adapter->getPlatform();
+
+ $isColumns = array(
+ array('C', 'ORDINAL_POSITION'),
+ array('C', 'COLUMN_DEFAULT'),
+ array('C', 'IS_NULLABLE'),
+ array('C', 'DATA_TYPE'),
+ array('C', 'CHARACTER_MAXIMUM_LENGTH'),
+ array('C', 'CHARACTER_OCTET_LENGTH'),
+ array('C', 'NUMERIC_PRECISION'),
+ array('C', 'NUMERIC_SCALE'),
+ array('C', 'COLUMN_NAME'),
+ array('C', 'COLUMN_TYPE'),
+ );
+
+ array_walk($isColumns, function (&$c) use ($p) { $c = $p->quoteIdentifierChain($c); });
+
+ $sql = 'SELECT ' . implode(', ', $isColumns)
+ . ' FROM ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'TABLES')) . 'T'
+ . ' INNER JOIN ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'COLUMNS')) . 'C'
+ . ' ON ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' = ' . $p->quoteIdentifierChain(array('C', 'TABLE_SCHEMA'))
+ . ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_NAME'))
+ . ' = ' . $p->quoteIdentifierChain(array('C', 'TABLE_NAME'))
+ . ' WHERE ' . $p->quoteIdentifierChain(array('T', 'TABLE_TYPE'))
+ . ' IN (\'BASE TABLE\', \'VIEW\')'
+ . ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_NAME'))
+ . ' = ' . $p->quoteTrustedValue($table);
+
+ if ($schema != self::DEFAULT_SCHEMA) {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' = ' . $p->quoteTrustedValue($schema);
+ } else {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' != \'INFORMATION_SCHEMA\'';
+ }
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+ $columns = array();
+ foreach ($results->toArray() as $row) {
+ $erratas = array();
+ $matches = array();
+ if (preg_match('/^(?:enum|set)\((.+)\)$/i', $row['COLUMN_TYPE'], $matches)) {
+ $permittedValues = $matches[1];
+ if (preg_match_all("/\\s*'((?:[^']++|'')*+)'\\s*(?:,|\$)/", $permittedValues, $matches, PREG_PATTERN_ORDER)) {
+ $permittedValues = str_replace("''", "'", $matches[1]);
+ } else {
+ $permittedValues = array($permittedValues);
+ }
+ $erratas['permitted_values'] = $permittedValues;
+ }
+ $columns[$row['COLUMN_NAME']] = array(
+ 'ordinal_position' => $row['ORDINAL_POSITION'],
+ 'column_default' => $row['COLUMN_DEFAULT'],
+ 'is_nullable' => ('YES' == $row['IS_NULLABLE']),
+ 'data_type' => $row['DATA_TYPE'],
+ 'character_maximum_length' => $row['CHARACTER_MAXIMUM_LENGTH'],
+ 'character_octet_length' => $row['CHARACTER_OCTET_LENGTH'],
+ 'numeric_precision' => $row['NUMERIC_PRECISION'],
+ 'numeric_scale' => $row['NUMERIC_SCALE'],
+ 'numeric_unsigned' => (false !== strpos($row['COLUMN_TYPE'], 'unsigned')),
+ 'erratas' => $erratas,
+ );
+ }
+
+ $this->data['columns'][$schema][$table] = $columns;
+ }
+
+ protected function loadConstraintData($table, $schema)
+ {
+ if (isset($this->data['constraints'][$schema][$table])) {
+ return;
+ }
+
+ $this->prepareDataHierarchy('constraints', $schema, $table);
+
+ $isColumns = array(
+ array('T', 'TABLE_NAME'),
+ array('TC', 'CONSTRAINT_NAME'),
+ array('TC', 'CONSTRAINT_TYPE'),
+ array('KCU', 'COLUMN_NAME'),
+ array('RC', 'MATCH_OPTION'),
+ array('RC', 'UPDATE_RULE'),
+ array('RC', 'DELETE_RULE'),
+ array('KCU', 'REFERENCED_TABLE_SCHEMA'),
+ array('KCU', 'REFERENCED_TABLE_NAME'),
+ array('KCU', 'REFERENCED_COLUMN_NAME'),
+ );
+
+ $p = $this->adapter->getPlatform();
+
+ array_walk($isColumns, function (&$c) use ($p) {
+ $c = $p->quoteIdentifierChain($c);
+ });
+
+ $sql = 'SELECT ' . implode(', ', $isColumns)
+ . ' FROM ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'TABLES')) . ' T'
+
+ . ' INNER JOIN ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'TABLE_CONSTRAINTS')) . ' TC'
+ . ' ON ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' = ' . $p->quoteIdentifierChain(array('TC', 'TABLE_SCHEMA'))
+ . ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_NAME'))
+ . ' = ' . $p->quoteIdentifierChain(array('TC', 'TABLE_NAME'))
+
+ . ' LEFT JOIN ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'KEY_COLUMN_USAGE')) . ' KCU'
+ . ' ON ' . $p->quoteIdentifierChain(array('TC', 'TABLE_SCHEMA'))
+ . ' = ' . $p->quoteIdentifierChain(array('KCU', 'TABLE_SCHEMA'))
+ . ' AND ' . $p->quoteIdentifierChain(array('TC', 'TABLE_NAME'))
+ . ' = ' . $p->quoteIdentifierChain(array('KCU', 'TABLE_NAME'))
+ . ' AND ' . $p->quoteIdentifierChain(array('TC', 'CONSTRAINT_NAME'))
+ . ' = ' . $p->quoteIdentifierChain(array('KCU', 'CONSTRAINT_NAME'))
+
+ . ' LEFT JOIN ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'REFERENTIAL_CONSTRAINTS')) . ' RC'
+ . ' ON ' . $p->quoteIdentifierChain(array('TC', 'CONSTRAINT_SCHEMA'))
+ . ' = ' . $p->quoteIdentifierChain(array('RC', 'CONSTRAINT_SCHEMA'))
+ . ' AND ' . $p->quoteIdentifierChain(array('TC', 'CONSTRAINT_NAME'))
+ . ' = ' . $p->quoteIdentifierChain(array('RC', 'CONSTRAINT_NAME'))
+
+ . ' WHERE ' . $p->quoteIdentifierChain(array('T', 'TABLE_NAME'))
+ . ' = ' . $p->quoteTrustedValue($table)
+ . ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_TYPE'))
+ . ' IN (\'BASE TABLE\', \'VIEW\')';
+
+ if ($schema != self::DEFAULT_SCHEMA) {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' = ' . $p->quoteTrustedValue($schema);
+ } else {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' != \'INFORMATION_SCHEMA\'';
+ }
+
+ $sql .= ' ORDER BY CASE ' . $p->quoteIdentifierChain(array('TC', 'CONSTRAINT_TYPE'))
+ . " WHEN 'PRIMARY KEY' THEN 1"
+ . " WHEN 'UNIQUE' THEN 2"
+ . " WHEN 'FOREIGN KEY' THEN 3"
+ . " ELSE 4 END"
+
+ . ', ' . $p->quoteIdentifierChain(array('TC', 'CONSTRAINT_NAME'))
+ . ', ' . $p->quoteIdentifierChain(array('KCU', 'ORDINAL_POSITION'));
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+
+ $realName = null;
+ $constraints = array();
+ foreach ($results->toArray() as $row) {
+ if ($row['CONSTRAINT_NAME'] !== $realName) {
+ $realName = $row['CONSTRAINT_NAME'];
+ $isFK = ('FOREIGN KEY' == $row['CONSTRAINT_TYPE']);
+ if ($isFK) {
+ $name = $realName;
+ } else {
+ $name = '_zf_' . $row['TABLE_NAME'] . '_' . $realName;
+ }
+ $constraints[$name] = array(
+ 'constraint_name' => $name,
+ 'constraint_type' => $row['CONSTRAINT_TYPE'],
+ 'table_name' => $row['TABLE_NAME'],
+ 'columns' => array(),
+ );
+ if ($isFK) {
+ $constraints[$name]['referenced_table_schema'] = $row['REFERENCED_TABLE_SCHEMA'];
+ $constraints[$name]['referenced_table_name'] = $row['REFERENCED_TABLE_NAME'];
+ $constraints[$name]['referenced_columns'] = array();
+ $constraints[$name]['match_option'] = $row['MATCH_OPTION'];
+ $constraints[$name]['update_rule'] = $row['UPDATE_RULE'];
+ $constraints[$name]['delete_rule'] = $row['DELETE_RULE'];
+ }
+ }
+ $constraints[$name]['columns'][] = $row['COLUMN_NAME'];
+ if ($isFK) {
+ $constraints[$name]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME'];
+ }
+ }
+
+ $this->data['constraints'][$schema][$table] = $constraints;
+ }
+
+ protected function loadConstraintDataNames($schema)
+ {
+ if (isset($this->data['constraint_names'][$schema])) {
+ return;
+ }
+
+ $this->prepareDataHierarchy('constraint_names', $schema);
+
+ $p = $this->adapter->getPlatform();
+
+ $isColumns = array(
+ array('TC', 'TABLE_NAME'),
+ array('TC', 'CONSTRAINT_NAME'),
+ array('TC', 'CONSTRAINT_TYPE'),
+ );
+
+ array_walk($isColumns, function (&$c) use ($p) {
+ $c = $p->quoteIdentifierChain($c);
+ });
+
+ $sql = 'SELECT ' . implode(', ', $isColumns)
+ . ' FROM ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'TABLES')) . 'T'
+ . ' INNER JOIN ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'TABLE_CONSTRAINTS')) . 'TC'
+ . ' ON ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' = ' . $p->quoteIdentifierChain(array('TC', 'TABLE_SCHEMA'))
+ . ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_NAME'))
+ . ' = ' . $p->quoteIdentifierChain(array('TC', 'TABLE_NAME'))
+ . ' WHERE ' . $p->quoteIdentifierChain(array('T', 'TABLE_TYPE'))
+ . ' IN (\'BASE TABLE\', \'VIEW\')';
+
+ if ($schema != self::DEFAULT_SCHEMA) {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' = ' . $p->quoteTrustedValue($schema);
+ } else {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' != \'INFORMATION_SCHEMA\'';
+ }
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+
+ $data = array();
+ foreach ($results->toArray() as $row) {
+ $data[] = array_change_key_case($row, CASE_LOWER);
+ }
+
+ $this->data['constraint_names'][$schema] = $data;
+ }
+
+ protected function loadConstraintDataKeys($schema)
+ {
+ if (isset($this->data['constraint_keys'][$schema])) {
+ return;
+ }
+
+ $this->prepareDataHierarchy('constraint_keys', $schema);
+
+ $p = $this->adapter->getPlatform();
+
+ $isColumns = array(
+ array('T', 'TABLE_NAME'),
+ array('KCU', 'CONSTRAINT_NAME'),
+ array('KCU', 'COLUMN_NAME'),
+ array('KCU', 'ORDINAL_POSITION'),
+ );
+
+ array_walk($isColumns, function (&$c) use ($p) {
+ $c = $p->quoteIdentifierChain($c);
+ });
+
+ $sql = 'SELECT ' . implode(', ', $isColumns)
+ . ' FROM ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'TABLES')) . 'T'
+
+ . ' INNER JOIN ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'KEY_COLUMN_USAGE')) . 'KCU'
+ . ' ON ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' = ' . $p->quoteIdentifierChain(array('KCU', 'TABLE_SCHEMA'))
+ . ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_NAME'))
+ . ' = ' . $p->quoteIdentifierChain(array('KCU', 'TABLE_NAME'))
+
+ . ' WHERE ' . $p->quoteIdentifierChain(array('T', 'TABLE_TYPE'))
+ . ' IN (\'BASE TABLE\', \'VIEW\')';
+
+ if ($schema != self::DEFAULT_SCHEMA) {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' = ' . $p->quoteTrustedValue($schema);
+ } else {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' != \'INFORMATION_SCHEMA\'';
+ }
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+
+ $data = array();
+ foreach ($results->toArray() as $row) {
+ $data[] = array_change_key_case($row, CASE_LOWER);
+ }
+
+ $this->data['constraint_keys'][$schema] = $data;
+ }
+
+ protected function loadConstraintReferences($table, $schema)
+ {
+ parent::loadConstraintReferences($table, $schema);
+
+ $p = $this->adapter->getPlatform();
+
+ $isColumns = array(
+ array('RC', 'TABLE_NAME'),
+ array('RC', 'CONSTRAINT_NAME'),
+ array('RC', 'UPDATE_RULE'),
+ array('RC', 'DELETE_RULE'),
+ array('KCU', 'REFERENCED_TABLE_SCHEMA'),
+ array('KCU', 'REFERENCED_TABLE_NAME'),
+ array('KCU', 'REFERENCED_COLUMN_NAME'),
+ );
+
+ array_walk($isColumns, function (&$c) use ($p) {
+ $c = $p->quoteIdentifierChain($c);
+ });
+
+ $sql = 'SELECT ' . implode(', ', $isColumns)
+ . 'FROM ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'TABLES')) . 'T'
+
+ . ' INNER JOIN ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'REFERENTIAL_CONSTRAINTS')) . 'RC'
+ . ' ON ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' = ' . $p->quoteIdentifierChain(array('RC', 'CONSTRAINT_SCHEMA'))
+ . ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_NAME'))
+ . ' = ' . $p->quoteIdentifierChain(array('RC', 'TABLE_NAME'))
+
+ . ' INNER JOIN ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'KEY_COLUMN_USAGE')) . 'KCU'
+ . ' ON ' . $p->quoteIdentifierChain(array('RC', 'CONSTRAINT_SCHEMA'))
+ . ' = ' . $p->quoteIdentifierChain(array('KCU', 'TABLE_SCHEMA'))
+ . ' AND ' . $p->quoteIdentifierChain(array('RC', 'TABLE_NAME'))
+ . ' = ' . $p->quoteIdentifierChain(array('KCU', 'TABLE_NAME'))
+ . ' AND ' . $p->quoteIdentifierChain(array('RC', 'CONSTRAINT_NAME'))
+ . ' = ' . $p->quoteIdentifierChain(array('KCU', 'CONSTRAINT_NAME'))
+
+ . 'WHERE ' . $p->quoteIdentifierChain(array('T', 'TABLE_TYPE'))
+ . ' IN (\'BASE TABLE\', \'VIEW\')';
+
+ if ($schema != self::DEFAULT_SCHEMA) {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' = ' . $p->quoteTrustedValue($schema);
+ } else {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' != \'INFORMATION_SCHEMA\'';
+ }
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+
+ $data = array();
+ foreach ($results->toArray() as $row) {
+ $data[] = array_change_key_case($row, CASE_LOWER);
+ }
+
+ $this->data['constraint_references'][$schema] = $data;
+ }
+
+ protected function loadTriggerData($schema)
+ {
+ if (isset($this->data['triggers'][$schema])) {
+ return;
+ }
+
+ $this->prepareDataHierarchy('triggers', $schema);
+
+ $p = $this->adapter->getPlatform();
+
+ $isColumns = array(
+// 'TRIGGER_CATALOG',
+// 'TRIGGER_SCHEMA',
+ 'TRIGGER_NAME',
+ 'EVENT_MANIPULATION',
+ 'EVENT_OBJECT_CATALOG',
+ 'EVENT_OBJECT_SCHEMA',
+ 'EVENT_OBJECT_TABLE',
+ 'ACTION_ORDER',
+ 'ACTION_CONDITION',
+ 'ACTION_STATEMENT',
+ 'ACTION_ORIENTATION',
+ 'ACTION_TIMING',
+ 'ACTION_REFERENCE_OLD_TABLE',
+ 'ACTION_REFERENCE_NEW_TABLE',
+ 'ACTION_REFERENCE_OLD_ROW',
+ 'ACTION_REFERENCE_NEW_ROW',
+ 'CREATED',
+ );
+
+ array_walk($isColumns, function (&$c) use ($p) {
+ $c = $p->quoteIdentifier($c);
+ });
+
+ $sql = 'SELECT ' . implode(', ', $isColumns)
+ . ' FROM ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'TRIGGERS'))
+ . ' WHERE ';
+
+ if ($schema != self::DEFAULT_SCHEMA) {
+ $sql .= $p->quoteIdentifier('TRIGGER_SCHEMA')
+ . ' = ' . $p->quoteTrustedValue($schema);
+ } else {
+ $sql .= $p->quoteIdentifier('TRIGGER_SCHEMA')
+ . ' != \'INFORMATION_SCHEMA\'';
+ }
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+
+ $data = array();
+ foreach ($results->toArray() as $row) {
+ $row = array_change_key_case($row, CASE_LOWER);
+ if (null !== $row['created']) {
+ $row['created'] = new \DateTime($row['created']);
+ }
+ $data[$row['trigger_name']] = $row;
+ }
+
+ $this->data['triggers'][$schema] = $data;
+ }
+}
diff --git a/library/Zend/Db/Metadata/Source/OracleMetadata.php b/library/Zend/Db/Metadata/Source/OracleMetadata.php
new file mode 100755
index 0000000000..44deac13d2
--- /dev/null
+++ b/library/Zend/Db/Metadata/Source/OracleMetadata.php
@@ -0,0 +1,256 @@
+ 'CHECK',
+ 'P' => 'PRIMARY KEY',
+ 'R' => 'FOREIGN_KEY'
+ );
+
+ /**
+ * {@inheritdoc}
+ * @see \Zend\Db\Metadata\Source\AbstractSource::loadColumnData()
+ */
+ protected function loadColumnData($table, $schema)
+ {
+ if (isset($this->data['columns'][$schema][$table])) {
+ return;
+ }
+
+ $isColumns = array(
+ 'COLUMN_ID',
+ 'COLUMN_NAME',
+ 'DATA_DEFAULT',
+ 'NULLABLE',
+ 'DATA_TYPE',
+ 'DATA_LENGTH',
+ 'DATA_PRECISION',
+ 'DATA_SCALE'
+ );
+
+ $this->prepareDataHierarchy('columns', $schema, $table);
+ $parameters = array(
+ ':ownername' => $schema,
+ ':tablename' => $table
+ );
+
+ $sql = 'SELECT ' . implode(', ', $isColumns)
+ . ' FROM all_tab_columns'
+ . ' WHERE owner = :ownername AND table_name = :tablename';
+
+ $result = $this->adapter->query($sql)->execute($parameters);
+ $columns = array();
+
+ foreach ($result as $row) {
+ $columns[$row['COLUMN_NAME']] = array(
+ 'ordinal_position' => $row['COLUMN_ID'],
+ 'column_default' => $row['DATA_DEFAULT'],
+ 'is_nullable' => ('Y' == $row['NULLABLE']),
+ 'data_type' => $row['DATA_TYPE'],
+ 'character_maximum_length' => $row['DATA_LENGTH'],
+ 'character_octet_length' => null,
+ 'numeric_precision' => $row['DATA_PRECISION'],
+ 'numeric_scale' => $row['DATA_SCALE'],
+ 'numeric_unsigned' => false,
+ 'erratas' => array(),
+ );
+ }
+
+ $this->data['columns'][$schema][$table] = $columns;
+ return $this;
+ }
+
+ /**
+ * Constraint type
+ *
+ * @param string $type
+ * @return string
+ */
+ protected function getConstraintType($type)
+ {
+ if (isset($this->constraintTypeMap[$type])) {
+ return $this->constraintTypeMap[$type];
+ }
+
+ return $type;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see \Zend\Db\Metadata\Source\AbstractSource::loadConstraintData()
+ */
+ protected function loadConstraintData($table, $schema)
+ {
+ if (isset($this->data['constraints'][$schema][$table])) {
+ return;
+ }
+
+ $this->prepareDataHierarchy('constraints', $schema, $table);
+ $sql = '
+ SELECT
+ ac.owner,
+ ac.constraint_name,
+ ac.constraint_type,
+ ac.search_condition check_clause,
+ ac.table_name,
+ ac.delete_rule,
+ cc1.column_name,
+ cc2.table_name as ref_table,
+ cc2.column_name as ref_column,
+ cc2.owner as ref_owner
+ FROM all_constraints ac
+ INNER JOIN all_cons_columns cc1
+ ON cc1.constraint_name = ac.constraint_name
+ LEFT JOIN all_cons_columns cc2
+ ON cc2.constraint_name = ac.r_constraint_name
+ AND cc2.position = cc1.position
+
+ WHERE
+ ac.owner = :schema AND ac.table_name = :table
+
+ ORDER BY ac.constraint_name;
+ ';
+
+ $parameters = array(
+ ':schema' => $schema,
+ ':table' => $table
+ );
+
+ $results = $this->adapter->query($sql)->execute($parameters);
+ $isFK = false;
+ $name = null;
+ $constraints = array();
+
+ foreach ($results as $row) {
+ if ($row['CONSTRAINT_NAME'] !== $name) {
+ $name = $row['CONSTRAINT_NAME'];
+ $constraints[$name] = array(
+ 'constraint_name' => $name,
+ 'constraint_type' => $this->getConstraintType($row['CONSTRAINT_TYPE']),
+ 'table_name' => $row['TABLE_NAME'],
+ );
+
+ if ('C' == $row['CONSTRAINT_TYPE']) {
+ $constraints[$name]['CHECK_CLAUSE'] = $row['CHECK_CLAUSE'];
+ continue;
+ }
+
+ $constraints[$name]['columns'] = array();
+
+ $isFK = ('R' == $row['CONSTRAINT_TYPE']);
+ if ($isFK) {
+ $constraints[$name]['referenced_table_schema'] = $row['REF_OWNER'];
+ $constraints[$name]['referenced_table_name'] = $row['REF_TABLE'];
+ $constraints[$name]['referenced_columns'] = array();
+ $constraints[$name]['match_option'] = 'NONE';
+ $constraints[$name]['update_rule'] = null;
+ $constraints[$name]['delete_rule'] = $row['DELETE_RULE'];
+ }
+ }
+
+ $constraints[$name]['columns'][] = $row['COLUMN_NAME'];
+ if ($isFK) {
+ $constraints[$name]['referenced_columns'][] = $row['REF_COLUMN'];
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see \Zend\Db\Metadata\Source\AbstractSource::loadSchemaData()
+ */
+ protected function loadSchemaData()
+ {
+ if (isset($this->data['schemas'])) {
+ return;
+ }
+
+ $this->prepareDataHierarchy('schemas');
+ $sql = 'SELECT USERNAME FROM ALL_USERS';
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+
+ $schemas = array();
+ foreach ($results->toArray() as $row) {
+ $schemas[] = $row['USERNAME'];
+ }
+
+ $this->data['schemas'] = $schemas;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see \Zend\Db\Metadata\Source\AbstractSource::loadTableNameData()
+ */
+ protected function loadTableNameData($schema)
+ {
+ if (isset($this->data['table_names'][$schema])) {
+ return $this;
+ }
+
+ $this->prepareDataHierarchy('table_names', $schema);
+ $tables = array();
+
+ // Tables
+ $bind = array(':OWNER' => strtoupper($schema));
+ $result = $this->adapter->query('SELECT TABLE_NAME FROM ALL_TABLES WHERE OWNER=:OWNER')->execute($bind);
+
+ foreach ($result as $row) {
+ $tables[$row['TABLE_NAME']] = array(
+ 'table_type' => 'BASE TABLE',
+ 'view_definition' => null,
+ 'check_option' => null,
+ 'is_updatable' => false,
+ );
+ }
+
+ // Views
+ $result = $this->adapter->query('SELECT VIEW_NAME, TEXT FROM ALL_VIEWS WHERE OWNER=:OWNER', $bind);
+ foreach ($result as $row) {
+ $tables[$row['VIEW_NAME']] = array(
+ 'table_type' => 'VIEW',
+ 'view_definition' => null,
+ 'check_option' => 'NONE',
+ 'is_updatable' => false,
+ );
+ }
+
+ $this->data['table_names'][$schema] = $tables;
+ return $this;
+ }
+
+ /**
+ * FIXME: load trigger data
+ *
+ * {@inheritdoc}
+ *
+ * @see \Zend\Db\Metadata\Source\AbstractSource::loadTriggerData()
+ */
+ protected function loadTriggerData($schema)
+ {
+ if (isset($this->data['triggers'][$schema])) {
+ return;
+ }
+
+ $this->prepareDataHierarchy('triggers', $schema);
+ }
+}
diff --git a/library/Zend/Db/Metadata/Source/PostgresqlMetadata.php b/library/Zend/Db/Metadata/Source/PostgresqlMetadata.php
new file mode 100755
index 0000000000..bb274487db
--- /dev/null
+++ b/library/Zend/Db/Metadata/Source/PostgresqlMetadata.php
@@ -0,0 +1,345 @@
+data['schemas'])) {
+ return;
+ }
+ $this->prepareDataHierarchy('schemas');
+
+ $p = $this->adapter->getPlatform();
+
+ $sql = 'SELECT ' . $p->quoteIdentifier('schema_name')
+ . ' FROM ' . $p->quoteIdentifierChain(array('information_schema', 'schemata'))
+ . ' WHERE ' . $p->quoteIdentifier('schema_name')
+ . ' != \'information_schema\''
+ . ' AND ' . $p->quoteIdentifier('schema_name') . " NOT LIKE 'pg_%'";
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+
+ $schemas = array();
+ foreach ($results->toArray() as $row) {
+ $schemas[] = $row['schema_name'];
+ }
+
+ $this->data['schemas'] = $schemas;
+ }
+
+ protected function loadTableNameData($schema)
+ {
+ if (isset($this->data['table_names'][$schema])) {
+ return;
+ }
+ $this->prepareDataHierarchy('table_names', $schema);
+
+ $p = $this->adapter->getPlatform();
+
+ $isColumns = array(
+ array('t', 'table_name'),
+ array('t', 'table_type'),
+ array('v', 'view_definition'),
+ array('v', 'check_option'),
+ array('v', 'is_updatable'),
+ );
+
+ array_walk($isColumns, function (&$c) use ($p) { $c = $p->quoteIdentifierChain($c); });
+
+ $sql = 'SELECT ' . implode(', ', $isColumns)
+ . ' FROM ' . $p->quoteIdentifierChain(array('information_schema', 'tables')) . ' t'
+
+ . ' LEFT JOIN ' . $p->quoteIdentifierChain(array('information_schema', 'views')) . ' v'
+ . ' ON ' . $p->quoteIdentifierChain(array('t', 'table_schema'))
+ . ' = ' . $p->quoteIdentifierChain(array('v', 'table_schema'))
+ . ' AND ' . $p->quoteIdentifierChain(array('t', 'table_name'))
+ . ' = ' . $p->quoteIdentifierChain(array('v', 'table_name'))
+
+ . ' WHERE ' . $p->quoteIdentifierChain(array('t', 'table_type'))
+ . ' IN (\'BASE TABLE\', \'VIEW\')';
+
+ if ($schema != self::DEFAULT_SCHEMA) {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('t', 'table_schema'))
+ . ' = ' . $p->quoteTrustedValue($schema);
+ } else {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('t', 'table_schema'))
+ . ' != \'information_schema\'';
+ }
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+
+ $tables = array();
+ foreach ($results->toArray() as $row) {
+ $tables[$row['table_name']] = array(
+ 'table_type' => $row['table_type'],
+ 'view_definition' => $row['view_definition'],
+ 'check_option' => $row['check_option'],
+ 'is_updatable' => ('YES' == $row['is_updatable']),
+ );
+ }
+
+ $this->data['table_names'][$schema] = $tables;
+ }
+
+ protected function loadColumnData($table, $schema)
+ {
+ if (isset($this->data['columns'][$schema][$table])) {
+ return;
+ }
+
+ $this->prepareDataHierarchy('columns', $schema, $table);
+
+ $platform = $this->adapter->getPlatform();
+
+ $isColumns = array(
+ 'table_name',
+ 'column_name',
+ 'ordinal_position',
+ 'column_default',
+ 'is_nullable',
+ 'data_type',
+ 'character_maximum_length',
+ 'character_octet_length',
+ 'numeric_precision',
+ 'numeric_scale',
+ );
+
+ array_walk($isColumns, function (&$c) use ($platform) { $c = $platform->quoteIdentifier($c); });
+
+ $sql = 'SELECT ' . implode(', ', $isColumns)
+ . ' FROM ' . $platform->quoteIdentifier('information_schema')
+ . $platform->getIdentifierSeparator() . $platform->quoteIdentifier('columns')
+ . ' WHERE ' . $platform->quoteIdentifier('table_schema')
+ . ' != \'information\''
+ . ' AND ' . $platform->quoteIdentifier('table_name')
+ . ' = ' . $platform->quoteTrustedValue($table);
+
+ if ($schema != '__DEFAULT_SCHEMA__') {
+ $sql .= ' AND ' . $platform->quoteIdentifier('table_schema')
+ . ' = ' . $platform->quoteTrustedValue($schema);
+ }
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+ $columns = array();
+ foreach ($results->toArray() as $row) {
+ $columns[$row['column_name']] = array(
+ 'ordinal_position' => $row['ordinal_position'],
+ 'column_default' => $row['column_default'],
+ 'is_nullable' => ('YES' == $row['is_nullable']),
+ 'data_type' => $row['data_type'],
+ 'character_maximum_length' => $row['character_maximum_length'],
+ 'character_octet_length' => $row['character_octet_length'],
+ 'numeric_precision' => $row['numeric_precision'],
+ 'numeric_scale' => $row['numeric_scale'],
+ 'numeric_unsigned' => null,
+ 'erratas' => array(),
+ );
+ }
+
+ $this->data['columns'][$schema][$table] = $columns;
+ }
+
+ protected function loadConstraintData($table, $schema)
+ {
+ if (isset($this->data['constraints'][$schema][$table])) {
+ return;
+ }
+
+ $this->prepareDataHierarchy('constraints', $schema, $table);
+
+ $isColumns = array(
+ array('t', 'table_name'),
+ array('tc', 'constraint_name'),
+ array('tc', 'constraint_type'),
+ array('kcu', 'column_name'),
+ array('cc', 'check_clause'),
+ array('rc', 'match_option'),
+ array('rc', 'update_rule'),
+ array('rc', 'delete_rule'),
+ array('referenced_table_schema' => 'kcu2', 'table_schema'),
+ array('referenced_table_name' => 'kcu2', 'table_name'),
+ array('referenced_column_name' => 'kcu2', 'column_name'),
+ );
+
+ $p = $this->adapter->getPlatform();
+
+ array_walk($isColumns, function (&$c) use ($p) {
+ $alias = key($c);
+ $c = $p->quoteIdentifierChain($c);
+ if (is_string($alias)) {
+ $c .= ' ' . $p->quoteIdentifier($alias);
+ }
+ });
+
+ $sql = 'SELECT ' . implode(', ', $isColumns)
+ . ' FROM ' . $p->quoteIdentifierChain(array('information_schema', 'tables')) . ' t'
+
+ . ' INNER JOIN ' . $p->quoteIdentifierChain(array('information_schema', 'table_constraints')) . ' tc'
+ . ' ON ' . $p->quoteIdentifierChain(array('t', 'table_schema'))
+ . ' = ' . $p->quoteIdentifierChain(array('tc', 'table_schema'))
+ . ' AND ' . $p->quoteIdentifierChain(array('t', 'table_name'))
+ . ' = ' . $p->quoteIdentifierChain(array('tc', 'table_name'))
+
+ . ' LEFT JOIN ' . $p->quoteIdentifierChain(array('information_schema', 'key_column_usage')) . ' kcu'
+ . ' ON ' . $p->quoteIdentifierChain(array('tc', 'table_schema'))
+ . ' = ' . $p->quoteIdentifierChain(array('kcu', 'table_schema'))
+ . ' AND ' . $p->quoteIdentifierChain(array('tc', 'table_name'))
+ . ' = ' . $p->quoteIdentifierChain(array('kcu', 'table_name'))
+ . ' AND ' . $p->quoteIdentifierChain(array('tc', 'constraint_name'))
+ . ' = ' . $p->quoteIdentifierChain(array('kcu', 'constraint_name'))
+
+ . ' LEFT JOIN ' . $p->quoteIdentifierChain(array('information_schema', 'check_constraints')) . ' cc'
+ . ' ON ' . $p->quoteIdentifierChain(array('tc', 'constraint_schema'))
+ . ' = ' . $p->quoteIdentifierChain(array('cc', 'constraint_schema'))
+ . ' AND ' . $p->quoteIdentifierChain(array('tc', 'constraint_name'))
+ . ' = ' . $p->quoteIdentifierChain(array('cc', 'constraint_name'))
+
+ . ' LEFT JOIN ' . $p->quoteIdentifierChain(array('information_schema', 'referential_constraints')) . ' rc'
+ . ' ON ' . $p->quoteIdentifierChain(array('tc', 'constraint_schema'))
+ . ' = ' . $p->quoteIdentifierChain(array('rc', 'constraint_schema'))
+ . ' AND ' . $p->quoteIdentifierChain(array('tc', 'constraint_name'))
+ . ' = ' . $p->quoteIdentifierChain(array('rc', 'constraint_name'))
+
+ . ' LEFT JOIN ' . $p->quoteIdentifierChain(array('information_schema', 'key_column_usage')) . ' kcu2'
+ . ' ON ' . $p->quoteIdentifierChain(array('rc', 'unique_constraint_schema'))
+ . ' = ' . $p->quoteIdentifierChain(array('kcu2', 'constraint_schema'))
+ . ' AND ' . $p->quoteIdentifierChain(array('rc', 'unique_constraint_name'))
+ . ' = ' . $p->quoteIdentifierChain(array('kcu2', 'constraint_name'))
+ . ' AND ' . $p->quoteIdentifierChain(array('kcu', 'position_in_unique_constraint'))
+ . ' = ' . $p->quoteIdentifierChain(array('kcu2', 'ordinal_position'))
+
+ . ' WHERE ' . $p->quoteIdentifierChain(array('t', 'table_name'))
+ . ' = ' . $p->quoteTrustedValue($table)
+ . ' AND ' . $p->quoteIdentifierChain(array('t', 'table_type'))
+ . ' IN (\'BASE TABLE\', \'VIEW\')';
+
+ if ($schema != self::DEFAULT_SCHEMA) {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('t', 'table_schema'))
+ . ' = ' . $p->quoteTrustedValue($schema);
+ } else {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('t', 'table_schema'))
+ . ' != \'information_schema\'';
+ }
+
+ $sql .= ' ORDER BY CASE ' . $p->quoteIdentifierChain(array('tc', 'constraint_type'))
+ . " WHEN 'PRIMARY KEY' THEN 1"
+ . " WHEN 'UNIQUE' THEN 2"
+ . " WHEN 'FOREIGN KEY' THEN 3"
+ . " WHEN 'CHECK' THEN 4"
+ . " ELSE 5 END"
+ . ', ' . $p->quoteIdentifierChain(array('tc', 'constraint_name'))
+ . ', ' . $p->quoteIdentifierChain(array('kcu', 'ordinal_position'));
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+
+ $name = null;
+ $constraints = array();
+ foreach ($results->toArray() as $row) {
+ if ($row['constraint_name'] !== $name) {
+ $name = $row['constraint_name'];
+ $constraints[$name] = array(
+ 'constraint_name' => $name,
+ 'constraint_type' => $row['constraint_type'],
+ 'table_name' => $row['table_name'],
+ );
+ if ('CHECK' == $row['constraint_type']) {
+ $constraints[$name]['check_clause'] = $row['check_clause'];
+ continue;
+ }
+ $constraints[$name]['columns'] = array();
+ $isFK = ('FOREIGN KEY' == $row['constraint_type']);
+ if ($isFK) {
+ $constraints[$name]['referenced_table_schema'] = $row['referenced_table_schema'];
+ $constraints[$name]['referenced_table_name'] = $row['referenced_table_name'];
+ $constraints[$name]['referenced_columns'] = array();
+ $constraints[$name]['match_option'] = $row['match_option'];
+ $constraints[$name]['update_rule'] = $row['update_rule'];
+ $constraints[$name]['delete_rule'] = $row['delete_rule'];
+ }
+ }
+ $constraints[$name]['columns'][] = $row['column_name'];
+ if ($isFK) {
+ $constraints[$name]['referenced_columns'][] = $row['referenced_column_name'];
+ }
+ }
+
+ $this->data['constraints'][$schema][$table] = $constraints;
+ }
+
+ protected function loadTriggerData($schema)
+ {
+ if (isset($this->data['triggers'][$schema])) {
+ return;
+ }
+
+ $this->prepareDataHierarchy('triggers', $schema);
+
+ $p = $this->adapter->getPlatform();
+
+ $isColumns = array(
+ 'trigger_name',
+ 'event_manipulation',
+ 'event_object_catalog',
+ 'event_object_schema',
+ 'event_object_table',
+ 'action_order',
+ 'action_condition',
+ 'action_statement',
+ 'action_orientation',
+ array('action_timing' => 'condition_timing'),
+ array('action_reference_old_table' => 'condition_reference_old_table'),
+ array('action_reference_new_table' => 'condition_reference_new_table'),
+ 'created',
+ );
+
+ array_walk($isColumns, function (&$c) use ($p) {
+ if (is_array($c)) {
+ $alias = key($c);
+ $c = $p->quoteIdentifierChain($c);
+ if (is_string($alias)) {
+ $c .= ' ' . $p->quoteIdentifier($alias);
+ }
+ } else {
+ $c = $p->quoteIdentifier($c);
+ }
+ });
+
+ $sql = 'SELECT ' . implode(', ', $isColumns)
+ . ' FROM ' . $p->quoteIdentifierChain(array('information_schema', 'triggers'))
+ . ' WHERE ';
+
+ if ($schema != self::DEFAULT_SCHEMA) {
+ $sql .= $p->quoteIdentifier('trigger_schema')
+ . ' = ' . $p->quoteTrustedValue($schema);
+ } else {
+ $sql .= $p->quoteIdentifier('trigger_schema')
+ . ' != \'information_schema\'';
+ }
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+
+ $data = array();
+ foreach ($results->toArray() as $row) {
+ $row = array_change_key_case($row, CASE_LOWER);
+ $row['action_reference_old_row'] = 'OLD';
+ $row['action_reference_new_row'] = 'NEW';
+ if (null !== $row['created']) {
+ $row['created'] = new \DateTime($row['created']);
+ }
+ $data[$row['trigger_name']] = $row;
+ }
+
+ $this->data['triggers'][$schema] = $data;
+ }
+}
diff --git a/library/Zend/Db/Metadata/Source/SqlServerMetadata.php b/library/Zend/Db/Metadata/Source/SqlServerMetadata.php
new file mode 100755
index 0000000000..b2b3e76fab
--- /dev/null
+++ b/library/Zend/Db/Metadata/Source/SqlServerMetadata.php
@@ -0,0 +1,341 @@
+data['schemas'])) {
+ return;
+ }
+ $this->prepareDataHierarchy('schemas');
+
+ $p = $this->adapter->getPlatform();
+
+ $sql = 'SELECT ' . $p->quoteIdentifier('SCHEMA_NAME')
+ . ' FROM ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'SCHEMATA'))
+ . ' WHERE ' . $p->quoteIdentifier('SCHEMA_NAME')
+ . ' != \'INFORMATION_SCHEMA\'';
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+
+ $schemas = array();
+ foreach ($results->toArray() as $row) {
+ $schemas[] = $row['SCHEMA_NAME'];
+ }
+
+ $this->data['schemas'] = $schemas;
+ }
+
+ protected function loadTableNameData($schema)
+ {
+ if (isset($this->data['table_names'][$schema])) {
+ return;
+ }
+ $this->prepareDataHierarchy('table_names', $schema);
+
+ $p = $this->adapter->getPlatform();
+
+ $isColumns = array(
+ array('T', 'TABLE_NAME'),
+ array('T', 'TABLE_TYPE'),
+ array('V', 'VIEW_DEFINITION'),
+ array('V', 'CHECK_OPTION'),
+ array('V', 'IS_UPDATABLE'),
+ );
+
+ array_walk($isColumns, function (&$c) use ($p) { $c = $p->quoteIdentifierChain($c); });
+
+ $sql = 'SELECT ' . implode(', ', $isColumns)
+ . ' FROM ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'TABLES')) . ' t'
+
+ . ' LEFT JOIN ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'VIEWS')) . ' v'
+ . ' ON ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' = ' . $p->quoteIdentifierChain(array('V', 'TABLE_SCHEMA'))
+ . ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_NAME'))
+ . ' = ' . $p->quoteIdentifierChain(array('V', 'TABLE_NAME'))
+
+ . ' WHERE ' . $p->quoteIdentifierChain(array('T', 'TABLE_TYPE'))
+ . ' IN (\'BASE TABLE\', \'VIEW\')';
+
+ if ($schema != self::DEFAULT_SCHEMA) {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' = ' . $p->quoteTrustedValue($schema);
+ } else {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' != \'INFORMATION_SCHEMA\'';
+ }
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+
+ $tables = array();
+ foreach ($results->toArray() as $row) {
+ $tables[$row['TABLE_NAME']] = array(
+ 'table_type' => $row['TABLE_TYPE'],
+ 'view_definition' => $row['VIEW_DEFINITION'],
+ 'check_option' => $row['CHECK_OPTION'],
+ 'is_updatable' => ('YES' == $row['IS_UPDATABLE']),
+ );
+ }
+
+ $this->data['table_names'][$schema] = $tables;
+ }
+
+ protected function loadColumnData($table, $schema)
+ {
+ if (isset($this->data['columns'][$schema][$table])) {
+ return;
+ }
+ $this->prepareDataHierarchy('columns', $schema, $table);
+ $p = $this->adapter->getPlatform();
+
+ $isColumns = array(
+ array('C', 'ORDINAL_POSITION'),
+ array('C', 'COLUMN_DEFAULT'),
+ array('C', 'IS_NULLABLE'),
+ array('C', 'DATA_TYPE'),
+ array('C', 'CHARACTER_MAXIMUM_LENGTH'),
+ array('C', 'CHARACTER_OCTET_LENGTH'),
+ array('C', 'NUMERIC_PRECISION'),
+ array('C', 'NUMERIC_SCALE'),
+ array('C', 'COLUMN_NAME'),
+ );
+
+ array_walk($isColumns, function (&$c) use ($p) { $c = $p->quoteIdentifierChain($c); });
+
+ $sql = 'SELECT ' . implode(', ', $isColumns)
+ . ' FROM ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'TABLES')) . 'T'
+ . ' INNER JOIN ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'COLUMNS')) . 'C'
+ . ' ON ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' = ' . $p->quoteIdentifierChain(array('C', 'TABLE_SCHEMA'))
+ . ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_NAME'))
+ . ' = ' . $p->quoteIdentifierChain(array('C', 'TABLE_NAME'))
+ . ' WHERE ' . $p->quoteIdentifierChain(array('T', 'TABLE_TYPE'))
+ . ' IN (\'BASE TABLE\', \'VIEW\')'
+ . ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_NAME'))
+ . ' = ' . $p->quoteTrustedValue($table);
+
+ if ($schema != self::DEFAULT_SCHEMA) {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' = ' . $p->quoteTrustedValue($schema);
+ } else {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' != \'INFORMATION_SCHEMA\'';
+ }
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+ $columns = array();
+ foreach ($results->toArray() as $row) {
+ $columns[$row['COLUMN_NAME']] = array(
+ 'ordinal_position' => $row['ORDINAL_POSITION'],
+ 'column_default' => $row['COLUMN_DEFAULT'],
+ 'is_nullable' => ('YES' == $row['IS_NULLABLE']),
+ 'data_type' => $row['DATA_TYPE'],
+ 'character_maximum_length' => $row['CHARACTER_MAXIMUM_LENGTH'],
+ 'character_octet_length' => $row['CHARACTER_OCTET_LENGTH'],
+ 'numeric_precision' => $row['NUMERIC_PRECISION'],
+ 'numeric_scale' => $row['NUMERIC_SCALE'],
+ 'numeric_unsigned' => null,
+ 'erratas' => array(),
+ );
+ }
+
+ $this->data['columns'][$schema][$table] = $columns;
+ }
+
+ protected function loadConstraintData($table, $schema)
+ {
+ if (isset($this->data['constraints'][$schema][$table])) {
+ return;
+ }
+
+ $this->prepareDataHierarchy('constraints', $schema, $table);
+
+ $isColumns = array(
+ array('T', 'TABLE_NAME'),
+ array('TC', 'CONSTRAINT_NAME'),
+ array('TC', 'CONSTRAINT_TYPE'),
+ array('KCU', 'COLUMN_NAME'),
+ array('CC', 'CHECK_CLAUSE'),
+ array('RC', 'MATCH_OPTION'),
+ array('RC', 'UPDATE_RULE'),
+ array('RC', 'DELETE_RULE'),
+ array('REFERENCED_TABLE_SCHEMA' => 'KCU2', 'TABLE_SCHEMA'),
+ array('REFERENCED_TABLE_NAME' => 'KCU2', 'TABLE_NAME'),
+ array('REFERENCED_COLUMN_NAME' => 'KCU2', 'COLUMN_NAME'),
+ );
+
+ $p = $this->adapter->getPlatform();
+
+ array_walk($isColumns, function (&$c) use ($p) {
+ $alias = key($c);
+ $c = $p->quoteIdentifierChain($c);
+ if (is_string($alias)) {
+ $c .= ' ' . $p->quoteIdentifier($alias);
+ }
+ });
+
+ $sql = 'SELECT ' . implode(', ', $isColumns)
+ . ' FROM ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'TABLES')) . ' T'
+
+ . ' INNER JOIN ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'TABLE_CONSTRAINTS')) . ' TC'
+ . ' ON ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' = ' . $p->quoteIdentifierChain(array('TC', 'TABLE_SCHEMA'))
+ . ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_NAME'))
+ . ' = ' . $p->quoteIdentifierChain(array('TC', 'TABLE_NAME'))
+
+ . ' LEFT JOIN ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'KEY_COLUMN_USAGE')) . ' KCU'
+ . ' ON ' . $p->quoteIdentifierChain(array('TC', 'TABLE_SCHEMA'))
+ . ' = ' . $p->quoteIdentifierChain(array('KCU', 'TABLE_SCHEMA'))
+ . ' AND ' . $p->quoteIdentifierChain(array('TC', 'TABLE_NAME'))
+ . ' = ' . $p->quoteIdentifierChain(array('KCU', 'TABLE_NAME'))
+ . ' AND ' . $p->quoteIdentifierChain(array('TC', 'CONSTRAINT_NAME'))
+ . ' = ' . $p->quoteIdentifierChain(array('KCU', 'CONSTRAINT_NAME'))
+
+ . ' LEFT JOIN ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'CHECK_CONSTRAINTS')) . ' CC'
+ . ' ON ' . $p->quoteIdentifierChain(array('TC', 'CONSTRAINT_SCHEMA'))
+ . ' = ' . $p->quoteIdentifierChain(array('CC', 'CONSTRAINT_SCHEMA'))
+ . ' AND ' . $p->quoteIdentifierChain(array('TC', 'CONSTRAINT_NAME'))
+ . ' = ' . $p->quoteIdentifierChain(array('CC', 'CONSTRAINT_NAME'))
+
+ . ' LEFT JOIN ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'REFERENTIAL_CONSTRAINTS')) . ' RC'
+ . ' ON ' . $p->quoteIdentifierChain(array('TC', 'CONSTRAINT_SCHEMA'))
+ . ' = ' . $p->quoteIdentifierChain(array('RC', 'CONSTRAINT_SCHEMA'))
+ . ' AND ' . $p->quoteIdentifierChain(array('TC', 'CONSTRAINT_NAME'))
+ . ' = ' . $p->quoteIdentifierChain(array('RC', 'CONSTRAINT_NAME'))
+
+ . ' LEFT JOIN ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'KEY_COLUMN_USAGE')) . ' KCU2'
+ . ' ON ' . $p->quoteIdentifierChain(array('RC', 'UNIQUE_CONSTRAINT_SCHEMA'))
+ . ' = ' . $p->quoteIdentifierChain(array('KCU2', 'CONSTRAINT_SCHEMA'))
+ . ' AND ' . $p->quoteIdentifierChain(array('RC', 'UNIQUE_CONSTRAINT_NAME'))
+ . ' = ' . $p->quoteIdentifierChain(array('KCU2', 'CONSTRAINT_NAME'))
+ . ' AND ' . $p->quoteIdentifierChain(array('KCU', 'ORDINAL_POSITION'))
+ . ' = ' . $p->quoteIdentifierChain(array('KCU2', 'ORDINAL_POSITION'))
+
+ . ' WHERE ' . $p->quoteIdentifierChain(array('T', 'TABLE_NAME'))
+ . ' = ' . $p->quoteTrustedValue($table)
+ . ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_TYPE'))
+ . ' IN (\'BASE TABLE\', \'VIEW\')';
+
+ if ($schema != self::DEFAULT_SCHEMA) {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' = ' . $p->quoteTrustedValue($schema);
+ } else {
+ $sql .= ' AND ' . $p->quoteIdentifierChain(array('T', 'TABLE_SCHEMA'))
+ . ' != \'INFORMATION_SCHEMA\'';
+ }
+
+ $sql .= ' ORDER BY CASE ' . $p->quoteIdentifierChain(array('TC', 'CONSTRAINT_TYPE'))
+ . " WHEN 'PRIMARY KEY' THEN 1"
+ . " WHEN 'UNIQUE' THEN 2"
+ . " WHEN 'FOREIGN KEY' THEN 3"
+ . " WHEN 'CHECK' THEN 4"
+ . " ELSE 5 END"
+ . ', ' . $p->quoteIdentifierChain(array('TC', 'CONSTRAINT_NAME'))
+ . ', ' . $p->quoteIdentifierChain(array('KCU', 'ORDINAL_POSITION'));
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+
+ $name = null;
+ $constraints = array();
+ $isFK = false;
+ foreach ($results->toArray() as $row) {
+ if ($row['CONSTRAINT_NAME'] !== $name) {
+ $name = $row['CONSTRAINT_NAME'];
+ $constraints[$name] = array(
+ 'constraint_name' => $name,
+ 'constraint_type' => $row['CONSTRAINT_TYPE'],
+ 'table_name' => $row['TABLE_NAME'],
+ );
+ if ('CHECK' == $row['CONSTRAINT_TYPE']) {
+ $constraints[$name]['check_clause'] = $row['CHECK_CLAUSE'];
+ continue;
+ }
+ $constraints[$name]['columns'] = array();
+ $isFK = ('FOREIGN KEY' == $row['CONSTRAINT_TYPE']);
+ if ($isFK) {
+ $constraints[$name]['referenced_table_schema'] = $row['REFERENCED_TABLE_SCHEMA'];
+ $constraints[$name]['referenced_table_name'] = $row['REFERENCED_TABLE_NAME'];
+ $constraints[$name]['referenced_columns'] = array();
+ $constraints[$name]['match_option'] = $row['MATCH_OPTION'];
+ $constraints[$name]['update_rule'] = $row['UPDATE_RULE'];
+ $constraints[$name]['delete_rule'] = $row['DELETE_RULE'];
+ }
+ }
+ $constraints[$name]['columns'][] = $row['COLUMN_NAME'];
+ if ($isFK) {
+ $constraints[$name]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME'];
+ }
+ }
+
+ $this->data['constraints'][$schema][$table] = $constraints;
+ }
+
+ protected function loadTriggerData($schema)
+ {
+ if (isset($this->data['triggers'][$schema])) {
+ return;
+ }
+
+ $this->prepareDataHierarchy('triggers', $schema);
+
+ $p = $this->adapter->getPlatform();
+
+ $isColumns = array(
+ 'TRIGGER_NAME',
+ 'EVENT_MANIPULATION',
+ 'EVENT_OBJECT_CATALOG',
+ 'EVENT_OBJECT_SCHEMA',
+ 'EVENT_OBJECT_TABLE',
+ 'ACTION_ORDER',
+ 'ACTION_CONDITION',
+ 'ACTION_STATEMENT',
+ 'ACTION_ORIENTATION',
+ 'ACTION_TIMING',
+ 'ACTION_REFERENCE_OLD_TABLE',
+ 'ACTION_REFERENCE_NEW_TABLE',
+ 'ACTION_REFERENCE_OLD_ROW',
+ 'ACTION_REFERENCE_NEW_ROW',
+ 'CREATED',
+ );
+
+ array_walk($isColumns, function (&$c) use ($p) {
+ $c = $p->quoteIdentifier($c);
+ });
+
+ $sql = 'SELECT ' . implode(', ', $isColumns)
+ . ' FROM ' . $p->quoteIdentifierChain(array('INFORMATION_SCHEMA', 'TRIGGERS'))
+ . ' WHERE ';
+
+ if ($schema != self::DEFAULT_SCHEMA) {
+ $sql .= $p->quoteIdentifier('TRIGGER_SCHEMA')
+ . ' = ' . $p->quoteTrustedValue($schema);
+ } else {
+ $sql .= $p->quoteIdentifier('TRIGGER_SCHEMA')
+ . ' != \'INFORMATION_SCHEMA\'';
+ }
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+
+ $data = array();
+ foreach ($results->toArray() as $row) {
+ $row = array_change_key_case($row, CASE_LOWER);
+ if (null !== $row['created']) {
+ $row['created'] = new \DateTime($row['created']);
+ }
+ $data[$row['trigger_name']] = $row;
+ }
+
+ $this->data['triggers'][$schema] = $data;
+ }
+}
diff --git a/library/Zend/Db/Metadata/Source/SqliteMetadata.php b/library/Zend/Db/Metadata/Source/SqliteMetadata.php
new file mode 100755
index 0000000000..f3869af8ca
--- /dev/null
+++ b/library/Zend/Db/Metadata/Source/SqliteMetadata.php
@@ -0,0 +1,390 @@
+data['schemas'])) {
+ return;
+ }
+ $this->prepareDataHierarchy('schemas');
+
+ $results = $this->fetchPragma('database_list');
+ foreach ($results as $row) {
+ $schemas[] = $row['name'];
+ }
+ $this->data['schemas'] = $schemas;
+ }
+
+ protected function loadTableNameData($schema)
+ {
+ if (isset($this->data['table_names'][$schema])) {
+ return;
+ }
+ $this->prepareDataHierarchy('table_names', $schema);
+
+ // FEATURE: Filename?
+
+ $p = $this->adapter->getPlatform();
+
+ $sql = 'SELECT "name", "type", "sql" FROM ' . $p->quoteIdentifierChain(array($schema, 'sqlite_master'))
+ . ' WHERE "type" IN (\'table\',\'view\') AND "name" NOT LIKE \'sqlite_%\'';
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+ $tables = array();
+ foreach ($results->toArray() as $row) {
+ if ('table' == $row['type']) {
+ $table = array(
+ 'table_type' => 'BASE TABLE',
+ 'view_definition' => null, // VIEW only
+ 'check_option' => null, // VIEW only
+ 'is_updatable' => null, // VIEW only
+ );
+ } else {
+ $table = array(
+ 'table_type' => 'VIEW',
+ 'view_definition' => null,
+ 'check_option' => 'NONE',
+ 'is_updatable' => false,
+ );
+
+ // Parse out extra data
+ if (null !== ($data = $this->parseView($row['sql']))) {
+ $table = array_merge($table, $data);
+ }
+ }
+ $tables[$row['name']] = $table;
+ }
+ $this->data['table_names'][$schema] = $tables;
+ }
+
+ protected function loadColumnData($table, $schema)
+ {
+ if (isset($this->data['columns'][$schema][$table])) {
+ return;
+ }
+ $this->prepareDataHierarchy('columns', $schema, $table);
+ $this->prepareDataHierarchy('sqlite_columns', $schema, $table);
+
+ $p = $this->adapter->getPlatform();
+
+
+ $results = $this->fetchPragma('table_info', $table, $schema);
+
+ $columns = array();
+
+ foreach ($results as $row) {
+ $columns[$row['name']] = array(
+ // cid appears to be zero-based, ordinal position needs to be one-based
+ 'ordinal_position' => $row['cid'] + 1,
+ 'column_default' => $row['dflt_value'],
+ 'is_nullable' => !((bool) $row['notnull']),
+ 'data_type' => $row['type'],
+ 'character_maximum_length' => null,
+ 'character_octet_length' => null,
+ 'numeric_precision' => null,
+ 'numeric_scale' => null,
+ 'numeric_unsigned' => null,
+ 'erratas' => array(),
+ );
+ // TODO: populate character_ and numeric_values with correct info
+ }
+
+ $this->data['columns'][$schema][$table] = $columns;
+ $this->data['sqlite_columns'][$schema][$table] = $results;
+ }
+
+ protected function loadConstraintData($table, $schema)
+ {
+ if (isset($this->data['constraints'][$schema][$table])) {
+ return;
+ }
+
+ $this->prepareDataHierarchy('constraints', $schema, $table);
+
+ $this->loadColumnData($table, $schema);
+ $primaryKey = array();
+
+ foreach ($this->data['sqlite_columns'][$schema][$table] as $col) {
+ if ((bool) $col['pk']) {
+ $primaryKey[] = $col['name'];
+ }
+ }
+
+ if (empty($primaryKey)) {
+ $primaryKey = null;
+ }
+ $constraints = array();
+ $indexes = $this->fetchPragma('index_list', $table, $schema);
+ foreach ($indexes as $index) {
+ if (!((bool) $index['unique'])) {
+ continue;
+ }
+ $constraint = array(
+ 'constraint_name' => $index['name'],
+ 'constraint_type' => 'UNIQUE',
+ 'table_name' => $table,
+ 'columns' => array(),
+ );
+
+ $info = $this->fetchPragma('index_info', $index['name'], $schema);
+
+ foreach ($info as $column) {
+ $constraint['columns'][] = $column['name'];
+ }
+ if ($primaryKey === $constraint['columns']) {
+ $constraint['constraint_type'] = 'PRIMARY KEY';
+ $primaryKey = null;
+ }
+ $constraints[$constraint['constraint_name']] = $constraint;
+ }
+
+ if (null !== $primaryKey) {
+ $constraintName = '_zf_' . $table . '_PRIMARY';
+ $constraints[$constraintName] = array(
+ 'constraint_name' => $constraintName,
+ 'constraint_type' => 'PRIMARY KEY',
+ 'table_name' => $table,
+ 'columns' => $primaryKey,
+ );
+ }
+
+ $foreignKeys = $this->fetchPragma('foreign_key_list', $table, $schema);
+
+ $id = $name = null;
+ foreach ($foreignKeys as $fk) {
+ if ($id !== $fk['id']) {
+ $id = $fk['id'];
+ $name = '_zf_' . $table . '_FOREIGN_KEY_' . ($id + 1);
+ $constraints[$name] = array(
+ 'constraint_name' => $name,
+ 'constraint_type' => 'FOREIGN KEY',
+ 'table_name' => $table,
+ 'columns' => array(),
+ 'referenced_table_schema' => $schema,
+ 'referenced_table_name' => $fk['table'],
+ 'referenced_columns' => array(),
+ // TODO: Verify match, on_update, and on_delete values conform to SQL Standard
+ 'match_option' => strtoupper($fk['match']),
+ 'update_rule' => strtoupper($fk['on_update']),
+ 'delete_rule' => strtoupper($fk['on_delete']),
+ );
+ }
+ $constraints[$name]['columns'][] = $fk['from'];
+ $constraints[$name]['referenced_columns'][] = $fk['to'];
+ }
+
+ $this->data['constraints'][$schema][$table] = $constraints;
+ }
+
+ protected function loadTriggerData($schema)
+ {
+ if (isset($this->data['triggers'][$schema])) {
+ return;
+ }
+
+ $this->prepareDataHierarchy('triggers', $schema);
+
+ $p = $this->adapter->getPlatform();
+
+ $sql = 'SELECT "name", "tbl_name", "sql" FROM '
+ . $p->quoteIdentifierChain(array($schema, 'sqlite_master'))
+ . ' WHERE "type" = \'trigger\'';
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+ $triggers = array();
+ foreach ($results->toArray() as $row) {
+ $trigger = array(
+ 'trigger_name' => $row['name'],
+ 'event_manipulation' => null, // in $row['sql']
+ 'event_object_catalog' => null,
+ 'event_object_schema' => $schema,
+ 'event_object_table' => $row['tbl_name'],
+ 'action_order' => 0,
+ 'action_condition' => null, // in $row['sql']
+ 'action_statement' => null, // in $row['sql']
+ 'action_orientation' => 'ROW',
+ 'action_timing' => null, // in $row['sql']
+ 'action_reference_old_table' => null,
+ 'action_reference_new_table' => null,
+ 'action_reference_old_row' => 'OLD',
+ 'action_reference_new_row' => 'NEW',
+ 'created' => null,
+ );
+
+ // Parse out extra data
+ if (null !== ($data = $this->parseTrigger($row['sql']))) {
+ $trigger = array_merge($trigger, $data);
+ }
+ $triggers[$trigger['trigger_name']] = $trigger;
+ }
+
+ $this->data['triggers'][$schema] = $triggers;
+ }
+
+ protected function fetchPragma($name, $value = null, $schema = null)
+ {
+ $p = $this->adapter->getPlatform();
+
+ $sql = 'PRAGMA ';
+
+ if (null !== $schema) {
+ $sql .= $p->quoteIdentifier($schema) . '.';
+ }
+ $sql .= $name;
+
+ if (null !== $value) {
+ $sql .= '(' . $p->quoteTrustedValue($value) . ')';
+ }
+
+ $results = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE);
+ if ($results instanceof ResultSetInterface) {
+ return $results->toArray();
+ }
+ return array();
+ }
+
+ protected function parseView($sql)
+ {
+ static $re = null;
+ if (null === $re) {
+ $identifierChain = $this->getIdentifierChainRegularExpression();
+ $re = $this->buildRegularExpression(array(
+ 'CREATE',
+ array('TEMP|TEMPORARY'),
+ 'VIEW',
+ array('IF', 'NOT', 'EXISTS'),
+ $identifierChain,
+ 'AS',
+ '(?.+)',
+ array(';'),
+ ));
+ }
+
+ if (!preg_match($re, $sql, $matches)) {
+ return null;
+ }
+ return array(
+ 'view_definition' => $matches['view_definition'],
+ );
+ }
+
+ protected function parseTrigger($sql)
+ {
+ static $re = null;
+ if (null === $re) {
+ $identifier = $this->getIdentifierRegularExpression();
+ $identifierList = $this->getIdentifierListRegularExpression();
+ $identifierChain = $this->getIdentifierChainRegularExpression();
+ $re = $this->buildRegularExpression(array(
+ 'CREATE',
+ array('TEMP|TEMPORARY'),
+ 'TRIGGER',
+ array('IF', 'NOT', 'EXISTS'),
+ $identifierChain,
+ array('(?BEFORE|AFTER|INSTEAD\\s+OF)',),
+ '(?DELETE|INSERT|UPDATE)',
+ array('OF', '(?' . $identifierList . ')'),
+ 'ON',
+ '(?' . $identifier . ')',
+ array('FOR', 'EACH', 'ROW'),
+ array('WHEN', '(?.+)'),
+ '(?BEGIN',
+ '.+',
+ 'END)',
+ array(';'),
+ ));
+ }
+
+ if (!preg_match($re, $sql, $matches)) {
+ return null;
+ }
+ $data = array();
+
+ foreach ($matches as $key => $value) {
+ if (is_string($key)) {
+ $data[$key] = $value;
+ }
+ }
+
+ // Normalize data and populate defaults, if necessary
+
+ $data['event_manipulation'] = strtoupper($data['event_manipulation']);
+ if (empty($data['action_condition'])) {
+ $data['action_condition'] = null;
+ }
+ if (!empty($data['action_timing'])) {
+ $data['action_timing'] = strtoupper($data['action_timing']);
+ if ('I' == $data['action_timing'][0]) {
+ // normalize the white-space between the two words
+ $data['action_timing'] = 'INSTEAD OF';
+ }
+ } else {
+ $data['action_timing'] = 'AFTER';
+ }
+ unset($data['column_usage']);
+
+ return $data;
+ }
+
+ protected function buildRegularExpression(array $re)
+ {
+ foreach ($re as &$value) {
+ if (is_array($value)) {
+ $value = '(?:' . implode('\\s*+', $value) . '\\s*+)?';
+ } else {
+ $value .= '\\s*+';
+ }
+ }
+ unset($value);
+ $re = '/^' . implode('\\s*+', $re) . '$/';
+ return $re;
+ }
+
+ protected function getIdentifierRegularExpression()
+ {
+ static $re = null;
+ if (null === $re) {
+ $re = '(?:' . implode('|', array(
+ '"(?:[^"\\\\]++|\\\\.)*+"',
+ '`(?:[^`]++|``)*+`',
+ '\\[[^\\]]+\\]',
+ '[^\\s\\.]+',
+ )) . ')';
+ }
+
+ return $re;
+ }
+
+ protected function getIdentifierChainRegularExpression()
+ {
+ static $re = null;
+ if (null === $re) {
+ $identifier = $this->getIdentifierRegularExpression();
+ $re = $identifier . '(?:\\s*\\.\\s*' . $identifier . ')*+';
+ }
+ return $re;
+ }
+
+ protected function getIdentifierListRegularExpression()
+ {
+ static $re = null;
+ if (null === $re) {
+ $identifier = $this->getIdentifierRegularExpression();
+ $re = $identifier . '(?:\\s*,\\s*' . $identifier . ')*+';
+ }
+ return $re;
+ }
+}
diff --git a/library/Zend/Db/README.md b/library/Zend/Db/README.md
new file mode 100755
index 0000000000..5c67884071
--- /dev/null
+++ b/library/Zend/Db/README.md
@@ -0,0 +1,15 @@
+DB Component from ZF2
+=====================
+
+This is the DB component for ZF2.
+
+- File issues at https://github.com/zendframework/zf2/issues
+- Create pull requests against https://github.com/zendframework/zf2
+- Documentation is at http://framework.zend.com/docs
+
+LICENSE
+-------
+
+The files in this archive are released under the [Zend Framework
+license](http://framework.zend.com/license), which is a 3-clause BSD license.
+
diff --git a/library/Zend/Db/ResultSet/AbstractResultSet.php b/library/Zend/Db/ResultSet/AbstractResultSet.php
new file mode 100755
index 0000000000..0db4c2d385
--- /dev/null
+++ b/library/Zend/Db/ResultSet/AbstractResultSet.php
@@ -0,0 +1,280 @@
+buffer)) {
+ $this->buffer = array();
+ }
+
+ if ($dataSource instanceof ResultInterface) {
+ $this->count = $dataSource->count();
+ $this->fieldCount = $dataSource->getFieldCount();
+ $this->dataSource = $dataSource;
+ if ($dataSource->isBuffered()) {
+ $this->buffer = -1;
+ }
+ if (is_array($this->buffer)) {
+ $this->dataSource->rewind();
+ }
+ return $this;
+ }
+
+ if (is_array($dataSource)) {
+ // its safe to get numbers from an array
+ $first = current($dataSource);
+ reset($dataSource);
+ $this->count = count($dataSource);
+ $this->fieldCount = count($first);
+ $this->dataSource = new ArrayIterator($dataSource);
+ $this->buffer = -1; // array's are a natural buffer
+ } elseif ($dataSource instanceof IteratorAggregate) {
+ $this->dataSource = $dataSource->getIterator();
+ } elseif ($dataSource instanceof Iterator) {
+ $this->dataSource = $dataSource;
+ } else {
+ throw new Exception\InvalidArgumentException('DataSource provided is not an array, nor does it implement Iterator or IteratorAggregate');
+ }
+
+ if ($this->count == null && $this->dataSource instanceof Countable) {
+ $this->count = $this->dataSource->count();
+ }
+
+ return $this;
+ }
+
+ public function buffer()
+ {
+ if ($this->buffer === -2) {
+ throw new Exception\RuntimeException('Buffering must be enabled before iteration is started');
+ } elseif ($this->buffer === null) {
+ $this->buffer = array();
+ if ($this->dataSource instanceof ResultInterface) {
+ $this->dataSource->rewind();
+ }
+ }
+ return $this;
+ }
+
+ public function isBuffered()
+ {
+ if ($this->buffer === -1 || is_array($this->buffer)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get the data source used to create the result set
+ *
+ * @return null|Iterator
+ */
+ public function getDataSource()
+ {
+ return $this->dataSource;
+ }
+
+ /**
+ * Retrieve count of fields in individual rows of the result set
+ *
+ * @return int
+ */
+ public function getFieldCount()
+ {
+ if (null !== $this->fieldCount) {
+ return $this->fieldCount;
+ }
+
+ $dataSource = $this->getDataSource();
+ if (null === $dataSource) {
+ return 0;
+ }
+
+ $dataSource->rewind();
+ if (!$dataSource->valid()) {
+ $this->fieldCount = 0;
+ return 0;
+ }
+
+ $row = $dataSource->current();
+ if (is_object($row) && $row instanceof Countable) {
+ $this->fieldCount = $row->count();
+ return $this->fieldCount;
+ }
+
+ $row = (array) $row;
+ $this->fieldCount = count($row);
+ return $this->fieldCount;
+ }
+
+ /**
+ * Iterator: move pointer to next item
+ *
+ * @return void
+ */
+ public function next()
+ {
+ if ($this->buffer === null) {
+ $this->buffer = -2; // implicitly disable buffering from here on
+ }
+ $this->dataSource->next();
+ $this->position++;
+ }
+
+ /**
+ * Iterator: retrieve current key
+ *
+ * @return mixed
+ */
+ public function key()
+ {
+ return $this->position;
+ }
+
+ /**
+ * Iterator: get current item
+ *
+ * @return array
+ */
+ public function current()
+ {
+ if ($this->buffer === null) {
+ $this->buffer = -2; // implicitly disable buffering from here on
+ } elseif (is_array($this->buffer) && isset($this->buffer[$this->position])) {
+ return $this->buffer[$this->position];
+ }
+ $data = $this->dataSource->current();
+ if (is_array($this->buffer)) {
+ $this->buffer[$this->position] = $data;
+ }
+ return $data;
+ }
+
+ /**
+ * Iterator: is pointer valid?
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ if (is_array($this->buffer) && isset($this->buffer[$this->position])) {
+ return true;
+ }
+ if ($this->dataSource instanceof Iterator) {
+ return $this->dataSource->valid();
+ } else {
+ $key = key($this->dataSource);
+ return ($key !== null);
+ }
+ }
+
+ /**
+ * Iterator: rewind
+ *
+ * @return void
+ */
+ public function rewind()
+ {
+ if (!is_array($this->buffer)) {
+ if ($this->dataSource instanceof Iterator) {
+ $this->dataSource->rewind();
+ } else {
+ reset($this->dataSource);
+ }
+ }
+ $this->position = 0;
+ }
+
+ /**
+ * Countable: return count of rows
+ *
+ * @return int
+ */
+ public function count()
+ {
+ if ($this->count !== null) {
+ return $this->count;
+ }
+ $this->count = count($this->dataSource);
+ return $this->count;
+ }
+
+ /**
+ * Cast result set to array of arrays
+ *
+ * @return array
+ * @throws Exception\RuntimeException if any row is not castable to an array
+ */
+ public function toArray()
+ {
+ $return = array();
+ foreach ($this as $row) {
+ if (is_array($row)) {
+ $return[] = $row;
+ } elseif (method_exists($row, 'toArray')) {
+ $return[] = $row->toArray();
+ } elseif (method_exists($row, 'getArrayCopy')) {
+ $return[] = $row->getArrayCopy();
+ } else {
+ throw new Exception\RuntimeException(
+ 'Rows as part of this DataSource, with type ' . gettype($row) . ' cannot be cast to an array'
+ );
+ }
+ }
+ return $return;
+ }
+}
diff --git a/library/Zend/Db/ResultSet/Exception/ExceptionInterface.php b/library/Zend/Db/ResultSet/Exception/ExceptionInterface.php
new file mode 100755
index 0000000000..7f7648b33f
--- /dev/null
+++ b/library/Zend/Db/ResultSet/Exception/ExceptionInterface.php
@@ -0,0 +1,16 @@
+setHydrator(($hydrator) ?: new ArraySerializable);
+ $this->setObjectPrototype(($objectPrototype) ?: new ArrayObject);
+ }
+
+ /**
+ * Set the row object prototype
+ *
+ * @param object $objectPrototype
+ * @throws Exception\InvalidArgumentException
+ * @return ResultSet
+ */
+ public function setObjectPrototype($objectPrototype)
+ {
+ if (!is_object($objectPrototype)) {
+ throw new Exception\InvalidArgumentException(
+ 'An object must be set as the object prototype, a ' . gettype($objectPrototype) . ' was provided.'
+ );
+ }
+ $this->objectPrototype = $objectPrototype;
+ return $this;
+ }
+
+ /**
+ * Set the hydrator to use for each row object
+ *
+ * @param HydratorInterface $hydrator
+ * @return HydratingResultSet
+ */
+ public function setHydrator(HydratorInterface $hydrator)
+ {
+ $this->hydrator = $hydrator;
+ return $this;
+ }
+
+ /**
+ * Get the hydrator to use for each row object
+ *
+ * @return HydratorInterface
+ */
+ public function getHydrator()
+ {
+ return $this->hydrator;
+ }
+
+ /**
+ * Iterator: get current item
+ *
+ * @return object
+ */
+ public function current()
+ {
+ if ($this->buffer === null) {
+ $this->buffer = -2; // implicitly disable buffering from here on
+ } elseif (is_array($this->buffer) && isset($this->buffer[$this->position])) {
+ return $this->buffer[$this->position];
+ }
+ $data = $this->dataSource->current();
+ $object = is_array($data) ? $this->hydrator->hydrate($data, clone $this->objectPrototype) : false;
+
+ if (is_array($this->buffer)) {
+ $this->buffer[$this->position] = $object;
+ }
+
+ return $object;
+ }
+
+ /**
+ * Cast result set to array of arrays
+ *
+ * @return array
+ * @throws Exception\RuntimeException if any row is not castable to an array
+ */
+ public function toArray()
+ {
+ $return = array();
+ foreach ($this as $row) {
+ $return[] = $this->getHydrator()->extract($row);
+ }
+ return $return;
+ }
+}
diff --git a/library/Zend/Db/ResultSet/ResultSet.php b/library/Zend/Db/ResultSet/ResultSet.php
new file mode 100755
index 0000000000..2286410c65
--- /dev/null
+++ b/library/Zend/Db/ResultSet/ResultSet.php
@@ -0,0 +1,112 @@
+returnType = (in_array($returnType, array(self::TYPE_ARRAY, self::TYPE_ARRAYOBJECT))) ? $returnType : self::TYPE_ARRAYOBJECT;
+ if ($this->returnType === self::TYPE_ARRAYOBJECT) {
+ $this->setArrayObjectPrototype(($arrayObjectPrototype) ?: new ArrayObject(array(), ArrayObject::ARRAY_AS_PROPS));
+ }
+ }
+
+ /**
+ * Set the row object prototype
+ *
+ * @param ArrayObject $arrayObjectPrototype
+ * @throws Exception\InvalidArgumentException
+ * @return ResultSet
+ */
+ public function setArrayObjectPrototype($arrayObjectPrototype)
+ {
+ if (!is_object($arrayObjectPrototype)
+ || (!$arrayObjectPrototype instanceof ArrayObject && !method_exists($arrayObjectPrototype, 'exchangeArray'))
+
+ ) {
+ throw new Exception\InvalidArgumentException('Object must be of type ArrayObject, or at least implement exchangeArray');
+ }
+ $this->arrayObjectPrototype = $arrayObjectPrototype;
+ return $this;
+ }
+
+ /**
+ * Get the row object prototype
+ *
+ * @return ArrayObject
+ */
+ public function getArrayObjectPrototype()
+ {
+ return $this->arrayObjectPrototype;
+ }
+
+ /**
+ * Get the return type to use when returning objects from the set
+ *
+ * @return string
+ */
+ public function getReturnType()
+ {
+ return $this->returnType;
+ }
+
+ /**
+ * @return array|\ArrayObject|null
+ */
+ public function current()
+ {
+ $data = parent::current();
+
+ if ($this->returnType === self::TYPE_ARRAYOBJECT && is_array($data)) {
+ /** @var $ao ArrayObject */
+ $ao = clone $this->arrayObjectPrototype;
+ if ($ao instanceof ArrayObject || method_exists($ao, 'exchangeArray')) {
+ $ao->exchangeArray($data);
+ }
+ return $ao;
+ }
+
+ return $data;
+ }
+}
diff --git a/library/Zend/Db/ResultSet/ResultSetInterface.php b/library/Zend/Db/ResultSet/ResultSetInterface.php
new file mode 100755
index 0000000000..c2bbd73b27
--- /dev/null
+++ b/library/Zend/Db/ResultSet/ResultSetInterface.php
@@ -0,0 +1,33 @@
+isInitialized) {
+ return;
+ }
+
+ if (!$this->featureSet instanceof Feature\FeatureSet) {
+ $this->featureSet = new Feature\FeatureSet;
+ }
+
+ $this->featureSet->setRowGateway($this);
+ $this->featureSet->apply('preInitialize', array());
+
+ if (!is_string($this->table) && !$this->table instanceof TableIdentifier) {
+ throw new Exception\RuntimeException('This row object does not have a valid table set.');
+ }
+
+ if ($this->primaryKeyColumn == null) {
+ throw new Exception\RuntimeException('This row object does not have a primary key column set.');
+ } elseif (is_string($this->primaryKeyColumn)) {
+ $this->primaryKeyColumn = (array) $this->primaryKeyColumn;
+ }
+
+ if (!$this->sql instanceof Sql) {
+ throw new Exception\RuntimeException('This row object does not have a Sql object set.');
+ }
+
+ $this->featureSet->apply('postInitialize', array());
+
+ $this->isInitialized = true;
+ }
+
+ /**
+ * Populate Data
+ *
+ * @param array $rowData
+ * @param bool $rowExistsInDatabase
+ * @return AbstractRowGateway
+ */
+ public function populate(array $rowData, $rowExistsInDatabase = false)
+ {
+ $this->initialize();
+
+ $this->data = $rowData;
+ if ($rowExistsInDatabase == true) {
+ $this->processPrimaryKeyData();
+ } else {
+ $this->primaryKeyData = null;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param mixed $array
+ * @return array|void
+ */
+ public function exchangeArray($array)
+ {
+ return $this->populate($array, true);
+ }
+
+ /**
+ * Save
+ *
+ * @return int
+ */
+ public function save()
+ {
+ $this->initialize();
+
+ if ($this->rowExistsInDatabase()) {
+ // UPDATE
+
+ $data = $this->data;
+ $where = array();
+ $isPkModified = false;
+
+ // primary key is always an array even if its a single column
+ foreach ($this->primaryKeyColumn as $pkColumn) {
+ $where[$pkColumn] = $this->primaryKeyData[$pkColumn];
+ if ($data[$pkColumn] == $this->primaryKeyData[$pkColumn]) {
+ unset($data[$pkColumn]);
+ } else {
+ $isPkModified = true;
+ }
+ }
+
+ $statement = $this->sql->prepareStatementForSqlObject($this->sql->update()->set($data)->where($where));
+ $result = $statement->execute();
+ $rowsAffected = $result->getAffectedRows();
+ unset($statement, $result); // cleanup
+
+ // If one or more primary keys are modified, we update the where clause
+ if ($isPkModified) {
+ foreach ($this->primaryKeyColumn as $pkColumn) {
+ if ($data[$pkColumn] != $this->primaryKeyData[$pkColumn]) {
+ $where[$pkColumn] = $data[$pkColumn];
+ }
+ }
+ }
+ } else {
+ // INSERT
+ $insert = $this->sql->insert();
+ $insert->values($this->data);
+
+ $statement = $this->sql->prepareStatementForSqlObject($insert);
+
+ $result = $statement->execute();
+ if (($primaryKeyValue = $result->getGeneratedValue()) && count($this->primaryKeyColumn) == 1) {
+ $this->primaryKeyData = array($this->primaryKeyColumn[0] => $primaryKeyValue);
+ } else {
+ // make primary key data available so that $where can be complete
+ $this->processPrimaryKeyData();
+ }
+ $rowsAffected = $result->getAffectedRows();
+ unset($statement, $result); // cleanup
+
+ $where = array();
+ // primary key is always an array even if its a single column
+ foreach ($this->primaryKeyColumn as $pkColumn) {
+ $where[$pkColumn] = $this->primaryKeyData[$pkColumn];
+ }
+ }
+
+ // refresh data
+ $statement = $this->sql->prepareStatementForSqlObject($this->sql->select()->where($where));
+ $result = $statement->execute();
+ $rowData = $result->current();
+ unset($statement, $result); // cleanup
+
+ // make sure data and original data are in sync after save
+ $this->populate($rowData, true);
+
+ // return rows affected
+ return $rowsAffected;
+ }
+
+ /**
+ * Delete
+ *
+ * @return int
+ */
+ public function delete()
+ {
+ $this->initialize();
+
+ $where = array();
+ // primary key is always an array even if its a single column
+ foreach ($this->primaryKeyColumn as $pkColumn) {
+ $where[$pkColumn] = $this->primaryKeyData[$pkColumn];
+ }
+
+ // @todo determine if we need to do a select to ensure 1 row will be affected
+
+ $statement = $this->sql->prepareStatementForSqlObject($this->sql->delete()->where($where));
+ $result = $statement->execute();
+
+ $affectedRows = $result->getAffectedRows();
+ if ($affectedRows == 1) {
+ // detach from database
+ $this->primaryKeyData = null;
+ }
+
+ return $affectedRows;
+ }
+
+ /**
+ * Offset Exists
+ *
+ * @param string $offset
+ * @return bool
+ */
+ public function offsetExists($offset)
+ {
+ return array_key_exists($offset, $this->data);
+ }
+
+ /**
+ * Offset get
+ *
+ * @param string $offset
+ * @return mixed
+ */
+ public function offsetGet($offset)
+ {
+ return $this->data[$offset];
+ }
+
+ /**
+ * Offset set
+ *
+ * @param string $offset
+ * @param mixed $value
+ * @return RowGateway
+ */
+ public function offsetSet($offset, $value)
+ {
+ $this->data[$offset] = $value;
+ return $this;
+ }
+
+ /**
+ * Offset unset
+ *
+ * @param string $offset
+ * @return AbstractRowGateway
+ */
+ public function offsetUnset($offset)
+ {
+ $this->data[$offset] = null;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->data);
+ }
+
+ /**
+ * To array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return $this->data;
+ }
+
+ /**
+ * __get
+ *
+ * @param string $name
+ * @throws Exception\InvalidArgumentException
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ if (array_key_exists($name, $this->data)) {
+ return $this->data[$name];
+ } else {
+ throw new Exception\InvalidArgumentException('Not a valid column in this row: ' . $name);
+ }
+ }
+
+ /**
+ * __set
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return void
+ */
+ public function __set($name, $value)
+ {
+ $this->offsetSet($name, $value);
+ }
+
+ /**
+ * __isset
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ return $this->offsetExists($name);
+ }
+
+ /**
+ * __unset
+ *
+ * @param string $name
+ * @return void
+ */
+ public function __unset($name)
+ {
+ $this->offsetUnset($name);
+ }
+
+ /**
+ * @return bool
+ */
+ public function rowExistsInDatabase()
+ {
+ return ($this->primaryKeyData !== null);
+ }
+
+ /**
+ * @throws Exception\RuntimeException
+ */
+ protected function processPrimaryKeyData()
+ {
+ $this->primaryKeyData = array();
+ foreach ($this->primaryKeyColumn as $column) {
+ if (!isset($this->data[$column])) {
+ throw new Exception\RuntimeException('While processing primary key data, a known key ' . $column . ' was not found in the data array');
+ }
+ $this->primaryKeyData[$column] = $this->data[$column];
+ }
+ }
+}
diff --git a/library/Zend/Db/RowGateway/Exception/ExceptionInterface.php b/library/Zend/Db/RowGateway/Exception/ExceptionInterface.php
new file mode 100755
index 0000000000..7bb37fc982
--- /dev/null
+++ b/library/Zend/Db/RowGateway/Exception/ExceptionInterface.php
@@ -0,0 +1,16 @@
+rowGateway = $rowGateway;
+ }
+
+ /**
+ * @throws \Zend\Db\RowGateway\Exception\RuntimeException
+ */
+ public function initialize()
+ {
+ throw new Exception\RuntimeException('This method is not intended to be called on this object.');
+ }
+
+ /**
+ * @return array
+ */
+ public function getMagicMethodSpecifications()
+ {
+ return array();
+ }
+}
diff --git a/library/Zend/Db/RowGateway/Feature/FeatureSet.php b/library/Zend/Db/RowGateway/Feature/FeatureSet.php
new file mode 100755
index 0000000000..de3b2344fb
--- /dev/null
+++ b/library/Zend/Db/RowGateway/Feature/FeatureSet.php
@@ -0,0 +1,149 @@
+addFeatures($features);
+ }
+ }
+
+ public function setRowGateway(AbstractRowGateway $rowGateway)
+ {
+ $this->rowGateway = $rowGateway;
+ foreach ($this->features as $feature) {
+ $feature->setRowGateway($this->rowGateway);
+ }
+ return $this;
+ }
+
+ public function getFeatureByClassName($featureClassName)
+ {
+ $feature = false;
+ foreach ($this->features as $potentialFeature) {
+ if ($potentialFeature instanceof $featureClassName) {
+ $feature = $potentialFeature;
+ break;
+ }
+ }
+ return $feature;
+ }
+
+ public function addFeatures(array $features)
+ {
+ foreach ($features as $feature) {
+ $this->addFeature($feature);
+ }
+ return $this;
+ }
+
+ public function addFeature(AbstractFeature $feature)
+ {
+ $this->features[] = $feature;
+ $feature->setRowGateway($feature);
+ return $this;
+ }
+
+ public function apply($method, $args)
+ {
+ foreach ($this->features as $feature) {
+ if (method_exists($feature, $method)) {
+ $return = call_user_func_array(array($feature, $method), $args);
+ if ($return === self::APPLY_HALT) {
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * @param string $property
+ * @return bool
+ */
+ public function canCallMagicGet($property)
+ {
+ return false;
+ }
+
+ /**
+ * @param string $property
+ * @return mixed
+ */
+ public function callMagicGet($property)
+ {
+ $return = null;
+ return $return;
+ }
+
+ /**
+ * @param string $property
+ * @return bool
+ */
+ public function canCallMagicSet($property)
+ {
+ return false;
+ }
+
+ /**
+ * @param $property
+ * @param $value
+ * @return mixed
+ */
+ public function callMagicSet($property, $value)
+ {
+ $return = null;
+ return $return;
+ }
+
+ /**
+ * @param string $method
+ * @return bool
+ */
+ public function canCallMagicCall($method)
+ {
+ return false;
+ }
+
+ /**
+ * @param string $method
+ * @param array $arguments
+ * @return mixed
+ */
+ public function callMagicCall($method, $arguments)
+ {
+ $return = null;
+ return $return;
+ }
+}
diff --git a/library/Zend/Db/RowGateway/RowGateway.php b/library/Zend/Db/RowGateway/RowGateway.php
new file mode 100755
index 0000000000..df2295c14d
--- /dev/null
+++ b/library/Zend/Db/RowGateway/RowGateway.php
@@ -0,0 +1,48 @@
+primaryKeyColumn = empty($primaryKeyColumn) ? null : (array) $primaryKeyColumn;
+
+ // set table
+ $this->table = $table;
+
+ // set Sql object
+ if ($adapterOrSql instanceof Sql) {
+ $this->sql = $adapterOrSql;
+ } elseif ($adapterOrSql instanceof Adapter) {
+ $this->sql = new Sql($adapterOrSql, $this->table);
+ } else {
+ throw new Exception\InvalidArgumentException('A valid Sql object was not provided.');
+ }
+
+ if ($this->sql->getTable() !== $this->table) {
+ throw new Exception\InvalidArgumentException('The Sql object provided does not have a table that matches this row object');
+ }
+
+ $this->initialize();
+ }
+}
diff --git a/library/Zend/Db/RowGateway/RowGatewayInterface.php b/library/Zend/Db/RowGateway/RowGatewayInterface.php
new file mode 100755
index 0000000000..e0a20b554d
--- /dev/null
+++ b/library/Zend/Db/RowGateway/RowGatewayInterface.php
@@ -0,0 +1,16 @@
+ '', 'subselectCount' => 0);
+
+ /**
+ * @var array
+ */
+ protected $instanceParameterIndex = array();
+
+ protected function processExpression(ExpressionInterface $expression, PlatformInterface $platform, DriverInterface $driver = null, $namedParameterPrefix = null)
+ {
+ // static counter for the number of times this method was invoked across the PHP runtime
+ static $runtimeExpressionPrefix = 0;
+
+ if ($driver && ((!is_string($namedParameterPrefix) || $namedParameterPrefix == ''))) {
+ $namedParameterPrefix = sprintf('expr%04dParam', ++$runtimeExpressionPrefix);
+ }
+
+ $sql = '';
+ $statementContainer = new StatementContainer;
+ $parameterContainer = $statementContainer->getParameterContainer();
+
+ // initialize variables
+ $parts = $expression->getExpressionData();
+
+ if (!isset($this->instanceParameterIndex[$namedParameterPrefix])) {
+ $this->instanceParameterIndex[$namedParameterPrefix] = 1;
+ }
+
+ $expressionParamIndex = &$this->instanceParameterIndex[$namedParameterPrefix];
+
+ foreach ($parts as $part) {
+ // if it is a string, simply tack it onto the return sql "specification" string
+ if (is_string($part)) {
+ $sql .= $part;
+ continue;
+ }
+
+ if (!is_array($part)) {
+ throw new Exception\RuntimeException('Elements returned from getExpressionData() array must be a string or array.');
+ }
+
+ // process values and types (the middle and last position of the expression data)
+ $values = $part[1];
+ $types = (isset($part[2])) ? $part[2] : array();
+ foreach ($values as $vIndex => $value) {
+ if (isset($types[$vIndex]) && $types[$vIndex] == ExpressionInterface::TYPE_IDENTIFIER) {
+ $values[$vIndex] = $platform->quoteIdentifierInFragment($value);
+ } elseif (isset($types[$vIndex]) && $types[$vIndex] == ExpressionInterface::TYPE_VALUE && $value instanceof Select) {
+ // process sub-select
+ if ($driver) {
+ $values[$vIndex] = '(' . $this->processSubSelect($value, $platform, $driver, $parameterContainer) . ')';
+ } else {
+ $values[$vIndex] = '(' . $this->processSubSelect($value, $platform) . ')';
+ }
+ } elseif (isset($types[$vIndex]) && $types[$vIndex] == ExpressionInterface::TYPE_VALUE && $value instanceof ExpressionInterface) {
+ // recursive call to satisfy nested expressions
+ $innerStatementContainer = $this->processExpression($value, $platform, $driver, $namedParameterPrefix . $vIndex . 'subpart');
+ $values[$vIndex] = $innerStatementContainer->getSql();
+ if ($driver) {
+ $parameterContainer->merge($innerStatementContainer->getParameterContainer());
+ }
+ } elseif (isset($types[$vIndex]) && $types[$vIndex] == ExpressionInterface::TYPE_VALUE) {
+ // if prepareType is set, it means that this particular value must be
+ // passed back to the statement in a way it can be used as a placeholder value
+ if ($driver) {
+ $name = $namedParameterPrefix . $expressionParamIndex++;
+ $parameterContainer->offsetSet($name, $value);
+ $values[$vIndex] = $driver->formatParameterName($name);
+ continue;
+ }
+
+ // if not a preparable statement, simply quote the value and move on
+ $values[$vIndex] = $platform->quoteValue($value);
+ } elseif (isset($types[$vIndex]) && $types[$vIndex] == ExpressionInterface::TYPE_LITERAL) {
+ $values[$vIndex] = $value;
+ }
+ }
+
+ // after looping the values, interpolate them into the sql string (they might be placeholder names, or values)
+ $sql .= vsprintf($part[0], $values);
+ }
+
+ $statementContainer->setSql($sql);
+ return $statementContainer;
+ }
+
+ /**
+ * @param $specifications
+ * @param $parameters
+ * @return string
+ * @throws Exception\RuntimeException
+ */
+ protected function createSqlFromSpecificationAndParameters($specifications, $parameters)
+ {
+ if (is_string($specifications)) {
+ return vsprintf($specifications, $parameters);
+ }
+
+ $parametersCount = count($parameters);
+ foreach ($specifications as $specificationString => $paramSpecs) {
+ if ($parametersCount == count($paramSpecs)) {
+ break;
+ }
+ unset($specificationString, $paramSpecs);
+ }
+
+ if (!isset($specificationString)) {
+ throw new Exception\RuntimeException(
+ 'A number of parameters was found that is not supported by this specification'
+ );
+ }
+
+ $topParameters = array();
+ foreach ($parameters as $position => $paramsForPosition) {
+ if (isset($paramSpecs[$position]['combinedby'])) {
+ $multiParamValues = array();
+ foreach ($paramsForPosition as $multiParamsForPosition) {
+ $ppCount = count($multiParamsForPosition);
+ if (!isset($paramSpecs[$position][$ppCount])) {
+ throw new Exception\RuntimeException('A number of parameters (' . $ppCount . ') was found that is not supported by this specification');
+ }
+ $multiParamValues[] = vsprintf($paramSpecs[$position][$ppCount], $multiParamsForPosition);
+ }
+ $topParameters[] = implode($paramSpecs[$position]['combinedby'], $multiParamValues);
+ } elseif ($paramSpecs[$position] !== null) {
+ $ppCount = count($paramsForPosition);
+ if (!isset($paramSpecs[$position][$ppCount])) {
+ throw new Exception\RuntimeException('A number of parameters (' . $ppCount . ') was found that is not supported by this specification');
+ }
+ $topParameters[] = vsprintf($paramSpecs[$position][$ppCount], $paramsForPosition);
+ } else {
+ $topParameters[] = $paramsForPosition;
+ }
+ }
+ return vsprintf($specificationString, $topParameters);
+ }
+
+ protected function processSubSelect(Select $subselect, PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
+ {
+ if ($driver) {
+ $stmtContainer = new StatementContainer;
+
+ // Track subselect prefix and count for parameters
+ $this->processInfo['subselectCount']++;
+ $subselect->processInfo['subselectCount'] = $this->processInfo['subselectCount'];
+ $subselect->processInfo['paramPrefix'] = 'subselect' . $subselect->processInfo['subselectCount'];
+
+ // call subselect
+ if ($this instanceof PlatformDecoratorInterface) {
+ /** @var Select|PlatformDecoratorInterface $subselectDecorator */
+ $subselectDecorator = clone $this;
+ $subselectDecorator->setSubject($subselect);
+ $subselectDecorator->prepareStatement(new Adapter($driver, $platform), $stmtContainer);
+ } else {
+ $subselect->prepareStatement(new Adapter($driver, $platform), $stmtContainer);
+ }
+
+ // copy count
+ $this->processInfo['subselectCount'] = $subselect->processInfo['subselectCount'];
+
+ $parameterContainer->merge($stmtContainer->getParameterContainer()->getNamedArray());
+ $sql = $stmtContainer->getSql();
+ } else {
+ if ($this instanceof PlatformDecoratorInterface) {
+ $subselectDecorator = clone $this;
+ $subselectDecorator->setSubject($subselect);
+ $sql = $subselectDecorator->getSqlString($platform);
+ } else {
+ $sql = $subselect->getSqlString($platform);
+ }
+ }
+ return $sql;
+ }
+}
diff --git a/library/Zend/Db/Sql/Ddl/AlterTable.php b/library/Zend/Db/Sql/Ddl/AlterTable.php
new file mode 100755
index 0000000000..2721db5a23
--- /dev/null
+++ b/library/Zend/Db/Sql/Ddl/AlterTable.php
@@ -0,0 +1,268 @@
+ "ALTER TABLE %1\$s\n",
+ self::ADD_COLUMNS => array(
+ "%1\$s" => array(
+ array(1 => 'ADD COLUMN %1$s', 'combinedby' => ",\n")
+ )
+ ),
+ self::CHANGE_COLUMNS => array(
+ "%1\$s" => array(
+ array(2 => 'CHANGE COLUMN %1$s %2$s', 'combinedby' => ",\n"),
+ )
+ ),
+ self::DROP_COLUMNS => array(
+ "%1\$s" => array(
+ array(1 => 'DROP COLUMN %1$s', 'combinedby' => ",\n"),
+ )
+ ),
+ self::ADD_CONSTRAINTS => array(
+ "%1\$s" => array(
+ array(1 => 'ADD %1$s', 'combinedby' => ",\n"),
+ )
+ ),
+ self::DROP_CONSTRAINTS => array(
+ "%1\$s" => array(
+ array(1 => 'DROP CONSTRAINT %1$s', 'combinedby' => ",\n"),
+ )
+ )
+ );
+
+ /**
+ * @var string
+ */
+ protected $table = '';
+
+ /**
+ * @param string $table
+ */
+ public function __construct($table = '')
+ {
+ ($table) ? $this->setTable($table) : null;
+ }
+
+ /**
+ * @param string $name
+ * @return self
+ */
+ public function setTable($name)
+ {
+ $this->table = $name;
+
+ return $this;
+ }
+
+ /**
+ * @param Column\ColumnInterface $column
+ * @return self
+ */
+ public function addColumn(Column\ColumnInterface $column)
+ {
+ $this->addColumns[] = $column;
+
+ return $this;
+ }
+
+ /**
+ * @param string $name
+ * @param Column\ColumnInterface $column
+ * @return self
+ */
+ public function changeColumn($name, Column\ColumnInterface $column)
+ {
+ $this->changeColumns[$name] = $column;
+
+ return $this;
+ }
+
+ /**
+ * @param string $name
+ * @return self
+ */
+ public function dropColumn($name)
+ {
+ $this->dropColumns[] = $name;
+
+ return $this;
+ }
+
+ /**
+ * @param string $name
+ * @return self
+ */
+ public function dropConstraint($name)
+ {
+ $this->dropConstraints[] = $name;
+
+ return $this;
+ }
+
+ /**
+ * @param Constraint\ConstraintInterface $constraint
+ * @return self
+ */
+ public function addConstraint(Constraint\ConstraintInterface $constraint)
+ {
+ $this->addConstraints[] = $constraint;
+
+ return $this;
+ }
+
+ /**
+ * @param string|null $key
+ * @return array
+ */
+ public function getRawState($key = null)
+ {
+ $rawState = array(
+ self::TABLE => $this->table,
+ self::ADD_COLUMNS => $this->addColumns,
+ self::DROP_COLUMNS => $this->dropColumns,
+ self::CHANGE_COLUMNS => $this->changeColumns,
+ self::ADD_CONSTRAINTS => $this->addConstraints,
+ self::DROP_CONSTRAINTS => $this->dropConstraints,
+ );
+
+ return (isset($key) && array_key_exists($key, $rawState)) ? $rawState[$key] : $rawState;
+ }
+
+ /**
+ * @param PlatformInterface $adapterPlatform
+ * @return string
+ */
+ public function getSqlString(PlatformInterface $adapterPlatform = null)
+ {
+ // get platform, or create default
+ $adapterPlatform = ($adapterPlatform) ?: new AdapterSql92Platform;
+
+ $sqls = array();
+ $parameters = array();
+
+ foreach ($this->specifications as $name => $specification) {
+ $parameters[$name] = $this->{'process' . $name}($adapterPlatform, null, null, $sqls, $parameters);
+ if ($specification && is_array($parameters[$name]) && ($parameters[$name] != array(array()))) {
+ $sqls[$name] = $this->createSqlFromSpecificationAndParameters($specification, $parameters[$name]);
+ }
+ if (stripos($name, 'table') === false && $parameters[$name] !== array(array())) {
+ $sqls[] = ",\n";
+ }
+ }
+
+ // remove last ,\n
+ array_pop($sqls);
+
+ $sql = implode('', $sqls);
+
+ return $sql;
+ }
+
+ protected function processTable(PlatformInterface $adapterPlatform = null)
+ {
+ return array($adapterPlatform->quoteIdentifier($this->table));
+ }
+
+ protected function processAddColumns(PlatformInterface $adapterPlatform = null)
+ {
+ $sqls = array();
+ foreach ($this->addColumns as $column) {
+ $sqls[] = $this->processExpression($column, $adapterPlatform)->getSql();
+ }
+
+ return array($sqls);
+ }
+
+ protected function processChangeColumns(PlatformInterface $adapterPlatform = null)
+ {
+ $sqls = array();
+ foreach ($this->changeColumns as $name => $column) {
+ $sqls[] = array(
+ $adapterPlatform->quoteIdentifier($name),
+ $this->processExpression($column, $adapterPlatform)->getSql()
+ );
+ }
+
+ return array($sqls);
+ }
+
+ protected function processDropColumns(PlatformInterface $adapterPlatform = null)
+ {
+ $sqls = array();
+ foreach ($this->dropColumns as $column) {
+ $sqls[] = $adapterPlatform->quoteIdentifier($column);
+ }
+
+ return array($sqls);
+ }
+
+ protected function processAddConstraints(PlatformInterface $adapterPlatform = null)
+ {
+ $sqls = array();
+ foreach ($this->addConstraints as $constraint) {
+ $sqls[] = $this->processExpression($constraint, $adapterPlatform);
+ }
+
+ return array($sqls);
+ }
+
+ protected function processDropConstraints(PlatformInterface $adapterPlatform = null)
+ {
+ $sqls = array();
+ foreach ($this->dropConstraints as $constraint) {
+ $sqls[] = $adapterPlatform->quoteIdentifier($constraint);
+ }
+
+ return array($sqls);
+ }
+}
diff --git a/library/Zend/Db/Sql/Ddl/Column/BigInteger.php b/library/Zend/Db/Sql/Ddl/Column/BigInteger.php
new file mode 100755
index 0000000000..d915a948f3
--- /dev/null
+++ b/library/Zend/Db/Sql/Ddl/Column/BigInteger.php
@@ -0,0 +1,18 @@
+setName($name);
+ $this->setLength($length);
+ $this->setNullable($nullable);
+ $this->setDefault($default);
+ $this->setOptions($options);
+ }
+
+ /**
+ * @param int $length
+ * @return self
+ */
+ public function setLength($length)
+ {
+ $this->length = $length;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getLength()
+ {
+ return $this->length;
+ }
+
+ /**
+ * @return array
+ */
+ public function getExpressionData()
+ {
+ $spec = $this->specification;
+
+ $params = array();
+ $params[] = $this->name;
+ $params[] = $this->type;
+
+ if ($this->length) {
+ $params[1] .= ' ' . $this->length;
+ }
+
+ $types = array(self::TYPE_IDENTIFIER, self::TYPE_LITERAL);
+
+ if (!$this->isNullable) {
+ $params[1] .= ' NOT NULL';
+ }
+
+ if ($this->default !== null) {
+ $spec .= ' DEFAULT %s';
+ $params[] = $this->default;
+ $types[] = self::TYPE_VALUE;
+ }
+
+ return array(array(
+ $spec,
+ $params,
+ $types,
+ ));
+ }
+}
diff --git a/library/Zend/Db/Sql/Ddl/Column/Boolean.php b/library/Zend/Db/Sql/Ddl/Column/Boolean.php
new file mode 100755
index 0000000000..36c07187cb
--- /dev/null
+++ b/library/Zend/Db/Sql/Ddl/Column/Boolean.php
@@ -0,0 +1,42 @@
+name = $name;
+ }
+
+ /**
+ * @return array
+ */
+ public function getExpressionData()
+ {
+ $spec = $this->specification;
+ $params = array($this->name);
+ $types = array(self::TYPE_IDENTIFIER);
+
+ return array(array(
+ $spec,
+ $params,
+ $types,
+ ));
+ }
+}
diff --git a/library/Zend/Db/Sql/Ddl/Column/Char.php b/library/Zend/Db/Sql/Ddl/Column/Char.php
new file mode 100755
index 0000000000..507cfe2c60
--- /dev/null
+++ b/library/Zend/Db/Sql/Ddl/Column/Char.php
@@ -0,0 +1,58 @@
+name = $name;
+ $this->length = $length;
+ }
+
+ /**
+ * @return array
+ */
+ public function getExpressionData()
+ {
+ $spec = $this->specification;
+ $params = array();
+
+ $types = array(self::TYPE_IDENTIFIER, self::TYPE_LITERAL);
+ $params[] = $this->name;
+ $params[] = $this->length;
+
+ $types[] = self::TYPE_LITERAL;
+ $params[] = (!$this->isNullable) ? 'NOT NULL' : '';
+
+ $types[] = ($this->default !== null) ? self::TYPE_VALUE : self::TYPE_LITERAL;
+ $params[] = ($this->default !== null) ? $this->default : '';
+
+ return array(array(
+ $spec,
+ $params,
+ $types,
+ ));
+ }
+}
diff --git a/library/Zend/Db/Sql/Ddl/Column/Column.php b/library/Zend/Db/Sql/Ddl/Column/Column.php
new file mode 100755
index 0000000000..de2f852b0d
--- /dev/null
+++ b/library/Zend/Db/Sql/Ddl/Column/Column.php
@@ -0,0 +1,164 @@
+setName($name);
+ }
+
+ /**
+ * @param string $name
+ * @return self
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * @return null|string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @param bool $nullable
+ * @return self
+ */
+ public function setNullable($nullable)
+ {
+ $this->isNullable = (bool) $nullable;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isNullable()
+ {
+ return $this->isNullable;
+ }
+
+ /**
+ * @param null|string|int $default
+ * @return self
+ */
+ public function setDefault($default)
+ {
+ $this->default = $default;
+ return $this;
+ }
+
+ /**
+ * @return null|string|int
+ */
+ public function getDefault()
+ {
+ return $this->default;
+ }
+
+ /**
+ * @param array $options
+ * @return self
+ */
+ public function setOptions(array $options)
+ {
+ $this->options = $options;
+ return $this;
+ }
+
+ /**
+ * @param string $name
+ * @param string $value
+ * @return self
+ */
+ public function setOption($name, $value)
+ {
+ $this->options[$name] = $value;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * @return array
+ */
+ public function getExpressionData()
+ {
+ $spec = $this->specification;
+
+ $params = array();
+ $params[] = $this->name;
+ $params[] = $this->type;
+
+ $types = array(self::TYPE_IDENTIFIER, self::TYPE_LITERAL);
+
+ if (!$this->isNullable) {
+ $params[1] .= ' NOT NULL';
+ }
+
+ if ($this->default !== null) {
+ $spec .= ' DEFAULT %s';
+ $params[] = $this->default;
+ $types[] = self::TYPE_VALUE;
+ }
+
+ return array(array(
+ $spec,
+ $params,
+ $types,
+ ));
+ }
+}
diff --git a/library/Zend/Db/Sql/Ddl/Column/ColumnInterface.php b/library/Zend/Db/Sql/Ddl/Column/ColumnInterface.php
new file mode 100755
index 0000000000..331e5254f4
--- /dev/null
+++ b/library/Zend/Db/Sql/Ddl/Column/ColumnInterface.php
@@ -0,0 +1,20 @@
+name = $name;
+ }
+
+ /**
+ * @return array
+ */
+ public function getExpressionData()
+ {
+ $spec = $this->specification;
+ $params = array();
+
+ $types = array(self::TYPE_IDENTIFIER);
+ $params[] = $this->name;
+
+ $types[] = self::TYPE_LITERAL;
+ $params[] = (!$this->isNullable) ? 'NOT NULL' : '';
+
+ $types[] = ($this->default !== null) ? self::TYPE_VALUE : self::TYPE_LITERAL;
+ $params[] = ($this->default !== null) ? $this->default : '';
+
+ return array(array(
+ $spec,
+ $params,
+ $types,
+ ));
+ }
+}
diff --git a/library/Zend/Db/Sql/Ddl/Column/Decimal.php b/library/Zend/Db/Sql/Ddl/Column/Decimal.php
new file mode 100755
index 0000000000..8a0ff25e3c
--- /dev/null
+++ b/library/Zend/Db/Sql/Ddl/Column/Decimal.php
@@ -0,0 +1,69 @@
+name = $name;
+ $this->precision = $precision;
+ $this->scale = $scale;
+ }
+
+ /**
+ * @return array
+ */
+ public function getExpressionData()
+ {
+ $spec = $this->specification;
+ $params = array();
+
+ $types = array(self::TYPE_IDENTIFIER, self::TYPE_LITERAL);
+ $params[] = $this->name;
+ $params[] = $this->precision;
+
+ if ($this->scale !== null) {
+ $params[1] .= ', ' . $this->scale;
+ }
+
+ $types[] = self::TYPE_LITERAL;
+ $params[] = (!$this->isNullable) ? 'NOT NULL' : '';
+
+ $types[] = ($this->default !== null) ? self::TYPE_VALUE : self::TYPE_LITERAL;
+ $params[] = ($this->default !== null) ? $this->default : '';
+
+ return array(array(
+ $spec,
+ $params,
+ $types,
+ ));
+ }
+}
diff --git a/library/Zend/Db/Sql/Ddl/Column/Float.php b/library/Zend/Db/Sql/Ddl/Column/Float.php
new file mode 100755
index 0000000000..e866abcf55
--- /dev/null
+++ b/library/Zend/Db/Sql/Ddl/Column/Float.php
@@ -0,0 +1,66 @@
+name = $name;
+ $this->digits = $digits;
+ $this->decimal = $decimal;
+ }
+
+ /**
+ * @return array
+ */
+ public function getExpressionData()
+ {
+ $spec = $this->specification;
+ $params = array();
+
+ $types = array(self::TYPE_IDENTIFIER, self::TYPE_LITERAL);
+ $params[] = $this->name;
+ $params[] = $this->digits;
+ $params[1] .= ', ' . $this->decimal;
+
+ $types[] = self::TYPE_LITERAL;
+ $params[] = (!$this->isNullable) ? 'NOT NULL' : '';
+
+ $types[] = ($this->default !== null) ? self::TYPE_VALUE : self::TYPE_LITERAL;
+ $params[] = ($this->default !== null) ? $this->default : '';
+
+ return array(array(
+ $spec,
+ $params,
+ $types,
+ ));
+ }
+}
diff --git a/library/Zend/Db/Sql/Ddl/Column/Integer.php b/library/Zend/Db/Sql/Ddl/Column/Integer.php
new file mode 100755
index 0000000000..5e424285c0
--- /dev/null
+++ b/library/Zend/Db/Sql/Ddl/Column/Integer.php
@@ -0,0 +1,32 @@
+setName($name);
+ $this->setNullable($nullable);
+ $this->setDefault($default);
+ $this->setOptions($options);
+ }
+}
diff --git a/library/Zend/Db/Sql/Ddl/Column/Text.php b/library/Zend/Db/Sql/Ddl/Column/Text.php
new file mode 100755
index 0000000000..3e40709093
--- /dev/null
+++ b/library/Zend/Db/Sql/Ddl/Column/Text.php
@@ -0,0 +1,51 @@
+name = $name;
+ }
+
+ /**
+ * @return array
+ */
+ public function getExpressionData()
+ {
+ $spec = $this->specification;
+ $params = array();
+
+ $types = array(self::TYPE_IDENTIFIER, self::TYPE_LITERAL);
+ $params[] = $this->name;
+
+ $types[] = self::TYPE_LITERAL;
+ $params[] = (!$this->isNullable) ? 'NOT NULL' : '';
+
+ $types[] = ($this->default !== null) ? self::TYPE_VALUE : self::TYPE_LITERAL;
+ $params[] = ($this->default !== null) ? $this->default : '';
+
+ return array(array(
+ $spec,
+ $params,
+ $types,
+ ));
+ }
+}
diff --git a/library/Zend/Db/Sql/Ddl/Column/Time.php b/library/Zend/Db/Sql/Ddl/Column/Time.php
new file mode 100755
index 0000000000..68d3c66484
--- /dev/null
+++ b/library/Zend/Db/Sql/Ddl/Column/Time.php
@@ -0,0 +1,50 @@
+name = $name;
+ }
+
+ /**
+ * @return array
+ */
+ public function getExpressionData()
+ {
+ $spec = $this->specification;
+ $params = array();
+
+ $types = array(self::TYPE_IDENTIFIER);
+ $params[] = $this->name;
+
+ $types[] = self::TYPE_LITERAL;
+ $params[] = (!$this->isNullable) ? 'NOT NULL' : '';
+
+ $types[] = ($this->default !== null) ? self::TYPE_VALUE : self::TYPE_LITERAL;
+ $params[] = ($this->default !== null) ? $this->default : '';
+
+ return array(array(
+ $spec,
+ $params,
+ $types,
+ ));
+ }
+}
diff --git a/library/Zend/Db/Sql/Ddl/Column/Varchar.php b/library/Zend/Db/Sql/Ddl/Column/Varchar.php
new file mode 100755
index 0000000000..49a718c78c
--- /dev/null
+++ b/library/Zend/Db/Sql/Ddl/Column/Varchar.php
@@ -0,0 +1,58 @@
+name = $name;
+ $this->length = $length;
+ }
+
+ /**
+ * @return array
+ */
+ public function getExpressionData()
+ {
+ $spec = $this->specification;
+ $params = array();
+
+ $types = array(self::TYPE_IDENTIFIER, self::TYPE_LITERAL);
+ $params[] = $this->name;
+ $params[] = $this->length;
+
+ $types[] = self::TYPE_LITERAL;
+ $params[] = (!$this->isNullable) ? 'NOT NULL' : '';
+
+ $types[] = ($this->default !== null) ? self::TYPE_VALUE : self::TYPE_LITERAL;
+ $params[] = ($this->default !== null) ? $this->default : '';
+
+ return array(array(
+ $spec,
+ $params,
+ $types,
+ ));
+ }
+}
diff --git a/library/Zend/Db/Sql/Ddl/Constraint/AbstractConstraint.php b/library/Zend/Db/Sql/Ddl/Constraint/AbstractConstraint.php
new file mode 100755
index 0000000000..19909fadb2
--- /dev/null
+++ b/library/Zend/Db/Sql/Ddl/Constraint/AbstractConstraint.php
@@ -0,0 +1,58 @@
+setColumns($columns);
+ }
+
+ /**
+ * @param null|string|array $columns
+ * @return self
+ */
+ public function setColumns($columns)
+ {
+ if (!is_array($columns)) {
+ $columns = array($columns);
+ }
+
+ $this->columns = $columns;
+ return $this;
+ }
+
+ /**
+ * @param string $column
+ * @return self
+ */
+ public function addColumn($column)
+ {
+ $this->columns[] = $column;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getColumns()
+ {
+ return $this->columns;
+ }
+}
diff --git a/library/Zend/Db/Sql/Ddl/Constraint/Check.php b/library/Zend/Db/Sql/Ddl/Constraint/Check.php
new file mode 100755
index 0000000000..1afbeb39cb
--- /dev/null
+++ b/library/Zend/Db/Sql/Ddl/Constraint/Check.php
@@ -0,0 +1,45 @@
+expression = $expression;
+ $this->name = $name;
+ }
+
+ /**
+ * @return array
+ */
+ public function getExpressionData()
+ {
+ return array(array(
+ $this->specification,
+ array($this->name, $this->expression),
+ array(self::TYPE_IDENTIFIER, self::TYPE_LITERAL),
+ ));
+ }
+}
diff --git a/library/Zend/Db/Sql/Ddl/Constraint/ConstraintInterface.php b/library/Zend/Db/Sql/Ddl/Constraint/ConstraintInterface.php
new file mode 100755
index 0000000000..bcb9643943
--- /dev/null
+++ b/library/Zend/Db/Sql/Ddl/Constraint/ConstraintInterface.php
@@ -0,0 +1,17 @@
+setName($name);
+ $this->setColumns($column);
+ $this->setReferenceTable($referenceTable);
+ $this->setReferenceColumn($referenceColumn);
+ (!$onDeleteRule) ?: $this->setOnDeleteRule($onDeleteRule);
+ (!$onUpdateRule) ?: $this->setOnUpdateRule($onUpdateRule);
+ }
+
+ /**
+ * @param string $name
+ * @return self
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @param string $referenceTable
+ * @return self
+ */
+ public function setReferenceTable($referenceTable)
+ {
+ $this->referenceTable = $referenceTable;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getReferenceTable()
+ {
+ return $this->referenceTable;
+ }
+
+ /**
+ * @param string $referenceColumn
+ * @return self
+ */
+ public function setReferenceColumn($referenceColumn)
+ {
+ $this->referenceColumn = $referenceColumn;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getReferenceColumn()
+ {
+ return $this->referenceColumn;
+ }
+
+ /**
+ * @param string $onDeleteRule
+ * @return self
+ */
+ public function setOnDeleteRule($onDeleteRule)
+ {
+ $this->onDeleteRule = $onDeleteRule;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getOnDeleteRule()
+ {
+ return $this->onDeleteRule;
+ }
+
+ /**
+ * @param string $onUpdateRule
+ * @return self
+ */
+ public function setOnUpdateRule($onUpdateRule)
+ {
+ $this->onUpdateRule = $onUpdateRule;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getOnUpdateRule()
+ {
+ return $this->onUpdateRule;
+ }
+
+ /**
+ * @return array
+ */
+ public function getExpressionData()
+ {
+ return array(array(
+ $this->specification,
+ array(
+ $this->name,
+ $this->columns[0],
+ $this->referenceTable,
+ $this->referenceColumn,
+ $this->onDeleteRule,
+ $this->onUpdateRule,
+ ),
+ array(
+ self::TYPE_IDENTIFIER,
+ self::TYPE_IDENTIFIER,
+ self::TYPE_IDENTIFIER,
+ self::TYPE_IDENTIFIER,
+ self::TYPE_LITERAL,
+ self::TYPE_LITERAL,
+ ),
+ ));
+ }
+}
diff --git a/library/Zend/Db/Sql/Ddl/Constraint/PrimaryKey.php b/library/Zend/Db/Sql/Ddl/Constraint/PrimaryKey.php
new file mode 100755
index 0000000000..84124a4d0a
--- /dev/null
+++ b/library/Zend/Db/Sql/Ddl/Constraint/PrimaryKey.php
@@ -0,0 +1,36 @@
+columns);
+ $newSpecParts = array_fill(0, $colCount, '%s');
+ $newSpecTypes = array_fill(0, $colCount, self::TYPE_IDENTIFIER);
+
+ $newSpec = sprintf($this->specification, implode(', ', $newSpecParts));
+
+ return array(array(
+ $newSpec,
+ $this->columns,
+ $newSpecTypes,
+ ));
+ }
+}
diff --git a/library/Zend/Db/Sql/Ddl/Constraint/UniqueKey.php b/library/Zend/Db/Sql/Ddl/Constraint/UniqueKey.php
new file mode 100755
index 0000000000..8d871054e1
--- /dev/null
+++ b/library/Zend/Db/Sql/Ddl/Constraint/UniqueKey.php
@@ -0,0 +1,55 @@
+setColumns($column);
+ $this->name = $name;
+ }
+
+ /**
+ * @return array
+ */
+ public function getExpressionData()
+ {
+ $colCount = count($this->columns);
+
+ $values = array();
+ $values[] = ($this->name) ? $this->name : '';
+
+ $newSpecTypes = array(self::TYPE_IDENTIFIER);
+ $newSpecParts = array();
+
+ for ($i = 0; $i < $colCount; $i++) {
+ $newSpecParts[] = '%s';
+ $newSpecTypes[] = self::TYPE_IDENTIFIER;
+ }
+
+ $newSpec = str_replace('...', implode(', ', $newSpecParts), $this->specification);
+
+ return array(array(
+ $newSpec,
+ array_merge($values, $this->columns),
+ $newSpecTypes,
+ ));
+ }
+}
diff --git a/library/Zend/Db/Sql/Ddl/CreateTable.php b/library/Zend/Db/Sql/Ddl/CreateTable.php
new file mode 100755
index 0000000000..45bfd982d9
--- /dev/null
+++ b/library/Zend/Db/Sql/Ddl/CreateTable.php
@@ -0,0 +1,218 @@
+ 'CREATE %1$sTABLE %2$s (',
+ self::COLUMNS => array(
+ "\n %1\$s" => array(
+ array(1 => '%1$s', 'combinedby' => ",\n ")
+ )
+ ),
+ self::CONSTRAINTS => array(
+ "\n %1\$s" => array(
+ array(1 => '%1$s', 'combinedby' => ",\n ")
+ )
+ ),
+ );
+
+ /**
+ * @var string
+ */
+ protected $table = '';
+
+ /**
+ * @param string $table
+ * @param bool $isTemporary
+ */
+ public function __construct($table = '', $isTemporary = false)
+ {
+ $this->table = $table;
+ $this->setTemporary($isTemporary);
+ }
+
+ /**
+ * @param bool $temporary
+ * @return self
+ */
+ public function setTemporary($temporary)
+ {
+ $this->isTemporary = (bool) $temporary;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isTemporary()
+ {
+ return $this->isTemporary;
+ }
+
+ /**
+ * @param string $name
+ * @return self
+ */
+ public function setTable($name)
+ {
+ $this->table = $name;
+ return $this;
+ }
+
+ /**
+ * @param Column\ColumnInterface $column
+ * @return self
+ */
+ public function addColumn(Column\ColumnInterface $column)
+ {
+ $this->columns[] = $column;
+ return $this;
+ }
+
+ /**
+ * @param Constraint\ConstraintInterface $constraint
+ * @return self
+ */
+ public function addConstraint(Constraint\ConstraintInterface $constraint)
+ {
+ $this->constraints[] = $constraint;
+ return $this;
+ }
+
+ /**
+ * @param string|null $key
+ * @return array
+ */
+ public function getRawState($key = null)
+ {
+ $rawState = array(
+ self::COLUMNS => $this->columns,
+ self::CONSTRAINTS => $this->constraints,
+ self::TABLE => $this->table,
+ );
+
+ return (isset($key) && array_key_exists($key, $rawState)) ? $rawState[$key] : $rawState;
+ }
+
+ /**
+ * @param PlatformInterface $adapterPlatform
+ * @return string
+ */
+ public function getSqlString(PlatformInterface $adapterPlatform = null)
+ {
+ // get platform, or create default
+ $adapterPlatform = ($adapterPlatform) ?: new AdapterSql92Platform;
+
+ $sqls = array();
+ $parameters = array();
+
+ foreach ($this->specifications as $name => $specification) {
+ if (is_int($name)) {
+ $sqls[] = $specification;
+ continue;
+ }
+
+ $parameters[$name] = $this->{'process' . $name}(
+ $adapterPlatform,
+ null,
+ null,
+ $sqls,
+ $parameters
+ );
+
+
+ if ($specification
+ && is_array($parameters[$name])
+ && ($parameters[$name] != array(array()))
+ ) {
+ $sqls[$name] = $this->createSqlFromSpecificationAndParameters(
+ $specification,
+ $parameters[$name]
+ );
+ }
+
+ if (stripos($name, 'table') === false
+ && $parameters[$name] !== array(array())
+ ) {
+ $sqls[] = ",\n";
+ }
+ }
+
+
+ // remove last ,
+ if (count($sqls) > 2) {
+ array_pop($sqls);
+ }
+
+ $sql = implode('', $sqls) . "\n)";
+
+ return $sql;
+ }
+
+ protected function processTable(PlatformInterface $adapterPlatform = null)
+ {
+ $ret = array();
+ if ($this->isTemporary) {
+ $ret[] = 'TEMPORARY ';
+ } else {
+ $ret[] = '';
+ }
+
+ $ret[] = $adapterPlatform->quoteIdentifier($this->table);
+ return $ret;
+ }
+
+ protected function processColumns(PlatformInterface $adapterPlatform = null)
+ {
+ $sqls = array();
+ foreach ($this->columns as $column) {
+ $sqls[] = $this->processExpression($column, $adapterPlatform)->getSql();
+ }
+ return array($sqls);
+ }
+
+ protected function processConstraints(PlatformInterface $adapterPlatform = null)
+ {
+ $sqls = array();
+ foreach ($this->constraints as $constraint) {
+ $sqls[] = $this->processExpression($constraint, $adapterPlatform)->getSql();
+ }
+ return array($sqls);
+ }
+}
diff --git a/library/Zend/Db/Sql/Ddl/DropTable.php b/library/Zend/Db/Sql/Ddl/DropTable.php
new file mode 100755
index 0000000000..e38425c6bb
--- /dev/null
+++ b/library/Zend/Db/Sql/Ddl/DropTable.php
@@ -0,0 +1,77 @@
+ 'DROP TABLE %1$s'
+ );
+
+ /**
+ * @var string
+ */
+ protected $table = '';
+
+ /**
+ * @param string $table
+ */
+ public function __construct($table = '')
+ {
+ $this->table = $table;
+ }
+
+ /**
+ * @param null|PlatformInterface $adapterPlatform
+ * @return string
+ */
+ public function getSqlString(PlatformInterface $adapterPlatform = null)
+ {
+ // get platform, or create default
+ $adapterPlatform = ($adapterPlatform) ?: new AdapterSql92Platform;
+
+ $sqls = array();
+ $parameters = array();
+
+ foreach ($this->specifications as $name => $specification) {
+ $parameters[$name] = $this->{'process' . $name}(
+ $adapterPlatform,
+ null,
+ null,
+ $sqls,
+ $parameters
+ );
+
+ if ($specification && is_array($parameters[$name])) {
+ $sqls[$name] = $this->createSqlFromSpecificationAndParameters(
+ $specification,
+ $parameters[$name]
+ );
+ }
+ }
+
+ $sql = implode(' ', $sqls);
+ return $sql;
+ }
+
+ protected function processTable(PlatformInterface $adapterPlatform = null)
+ {
+ return array($adapterPlatform->quoteIdentifier($this->table));
+ }
+}
diff --git a/library/Zend/Db/Sql/Ddl/SqlInterface.php b/library/Zend/Db/Sql/Ddl/SqlInterface.php
new file mode 100755
index 0000000000..761312458a
--- /dev/null
+++ b/library/Zend/Db/Sql/Ddl/SqlInterface.php
@@ -0,0 +1,16 @@
+ 'DELETE FROM %1$s',
+ self::SPECIFICATION_WHERE => 'WHERE %1$s'
+ );
+
+ /**
+ * @var string|TableIdentifier
+ */
+ protected $table = '';
+
+ /**
+ * @var bool
+ */
+ protected $emptyWhereProtection = true;
+
+ /**
+ * @var array
+ */
+ protected $set = array();
+
+ /**
+ * @var null|string|Where
+ */
+ protected $where = null;
+
+ /**
+ * Constructor
+ *
+ * @param null|string|TableIdentifier $table
+ */
+ public function __construct($table = null)
+ {
+ if ($table) {
+ $this->from($table);
+ }
+ $this->where = new Where();
+ }
+
+ /**
+ * Create from statement
+ *
+ * @param string|TableIdentifier $table
+ * @return Delete
+ */
+ public function from($table)
+ {
+ $this->table = $table;
+ return $this;
+ }
+
+ public function getRawState($key = null)
+ {
+ $rawState = array(
+ 'emptyWhereProtection' => $this->emptyWhereProtection,
+ 'table' => $this->table,
+ 'set' => $this->set,
+ 'where' => $this->where
+ );
+ return (isset($key) && array_key_exists($key, $rawState)) ? $rawState[$key] : $rawState;
+ }
+
+ /**
+ * Create where clause
+ *
+ * @param Where|\Closure|string|array $predicate
+ * @param string $combination One of the OP_* constants from Predicate\PredicateSet
+ * @return Delete
+ */
+ public function where($predicate, $combination = Predicate\PredicateSet::OP_AND)
+ {
+ if ($predicate instanceof Where) {
+ $this->where = $predicate;
+ } else {
+ $this->where->addPredicates($predicate, $combination);
+ }
+ return $this;
+ }
+
+ /**
+ * Prepare the delete statement
+ *
+ * @param AdapterInterface $adapter
+ * @param StatementContainerInterface $statementContainer
+ * @return void
+ */
+ public function prepareStatement(AdapterInterface $adapter, StatementContainerInterface $statementContainer)
+ {
+ $driver = $adapter->getDriver();
+ $platform = $adapter->getPlatform();
+ $parameterContainer = $statementContainer->getParameterContainer();
+
+ if (!$parameterContainer instanceof ParameterContainer) {
+ $parameterContainer = new ParameterContainer();
+ $statementContainer->setParameterContainer($parameterContainer);
+ }
+
+ $table = $this->table;
+ $schema = null;
+
+ // create quoted table name to use in delete processing
+ if ($table instanceof TableIdentifier) {
+ list($table, $schema) = $table->getTableAndSchema();
+ }
+
+ $table = $platform->quoteIdentifier($table);
+
+ if ($schema) {
+ $table = $platform->quoteIdentifier($schema) . $platform->getIdentifierSeparator() . $table;
+ }
+
+ $sql = sprintf($this->specifications[static::SPECIFICATION_DELETE], $table);
+
+ // process where
+ if ($this->where->count() > 0) {
+ $whereParts = $this->processExpression($this->where, $platform, $driver, 'where');
+ $parameterContainer->merge($whereParts->getParameterContainer());
+ $sql .= ' ' . sprintf($this->specifications[static::SPECIFICATION_WHERE], $whereParts->getSql());
+ }
+ $statementContainer->setSql($sql);
+ }
+
+ /**
+ * Get the SQL string, based on the platform
+ *
+ * Platform defaults to Sql92 if none provided
+ *
+ * @param null|PlatformInterface $adapterPlatform
+ * @return string
+ */
+ public function getSqlString(PlatformInterface $adapterPlatform = null)
+ {
+ $adapterPlatform = ($adapterPlatform) ?: new Sql92;
+ $table = $this->table;
+ $schema = null;
+
+ // create quoted table name to use in delete processing
+ if ($table instanceof TableIdentifier) {
+ list($table, $schema) = $table->getTableAndSchema();
+ }
+
+ $table = $adapterPlatform->quoteIdentifier($table);
+
+ if ($schema) {
+ $table = $adapterPlatform->quoteIdentifier($schema) . $adapterPlatform->getIdentifierSeparator() . $table;
+ }
+
+ $sql = sprintf($this->specifications[static::SPECIFICATION_DELETE], $table);
+
+ if ($this->where->count() > 0) {
+ $whereParts = $this->processExpression($this->where, $adapterPlatform, null, 'where');
+ $sql .= ' ' . sprintf($this->specifications[static::SPECIFICATION_WHERE], $whereParts->getSql());
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Property overloading
+ *
+ * Overloads "where" only.
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ switch (strtolower($name)) {
+ case 'where':
+ return $this->where;
+ }
+ }
+}
diff --git a/library/Zend/Db/Sql/Exception/ExceptionInterface.php b/library/Zend/Db/Sql/Exception/ExceptionInterface.php
new file mode 100755
index 0000000000..337266de87
--- /dev/null
+++ b/library/Zend/Db/Sql/Exception/ExceptionInterface.php
@@ -0,0 +1,16 @@
+setExpression($expression);
+ }
+ if ($parameters) {
+ $this->setParameters($parameters);
+ }
+ if ($types) {
+ $this->setTypes($types);
+ }
+ }
+
+ /**
+ * @param $expression
+ * @return Expression
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setExpression($expression)
+ {
+ if (!is_string($expression) || $expression == '') {
+ throw new Exception\InvalidArgumentException('Supplied expression must be a string.');
+ }
+ $this->expression = $expression;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getExpression()
+ {
+ return $this->expression;
+ }
+
+ /**
+ * @param $parameters
+ * @return Expression
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setParameters($parameters)
+ {
+ if (!is_scalar($parameters) && !is_array($parameters)) {
+ throw new Exception\InvalidArgumentException('Expression parameters must be a scalar or array.');
+ }
+ $this->parameters = $parameters;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getParameters()
+ {
+ return $this->parameters;
+ }
+
+ /**
+ * @param array $types
+ * @return Expression
+ */
+ public function setTypes(array $types)
+ {
+ $this->types = $types;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getTypes()
+ {
+ return $this->types;
+ }
+
+ /**
+ * @return array
+ * @throws Exception\RuntimeException
+ */
+ public function getExpressionData()
+ {
+ $parameters = (is_scalar($this->parameters)) ? array($this->parameters) : $this->parameters;
+
+ $types = array();
+ $parametersCount = count($parameters);
+
+ if ($parametersCount == 0 && strpos($this->expression, self::PLACEHOLDER) !== false) {
+ // if there are no parameters, but there is a placeholder
+ $parametersCount = substr_count($this->expression, self::PLACEHOLDER);
+ $parameters = array_fill(0, $parametersCount, null);
+ }
+
+ for ($i = 0; $i < $parametersCount; $i++) {
+ $types[$i] = (isset($this->types[$i]) && ($this->types[$i] == self::TYPE_IDENTIFIER || $this->types[$i] == self::TYPE_LITERAL))
+ ? $this->types[$i] : self::TYPE_VALUE;
+ }
+
+ // assign locally, escaping % signs
+ $expression = str_replace('%', '%%', $this->expression);
+
+ if ($parametersCount > 0) {
+ $count = 0;
+ $expression = str_replace(self::PLACEHOLDER, '%s', $expression, $count);
+ if ($count !== $parametersCount) {
+ throw new Exception\RuntimeException('The number of replacements in the expression does not match the number of parameters');
+ }
+ }
+
+ return array(array(
+ $expression,
+ $parameters,
+ $types
+ ));
+ }
+}
diff --git a/library/Zend/Db/Sql/ExpressionInterface.php b/library/Zend/Db/Sql/ExpressionInterface.php
new file mode 100755
index 0000000000..99c9299392
--- /dev/null
+++ b/library/Zend/Db/Sql/ExpressionInterface.php
@@ -0,0 +1,36 @@
+ 'INSERT INTO %1$s (%2$s) VALUES (%3$s)',
+ self::SPECIFICATION_SELECT => 'INSERT INTO %1$s %2$s %3$s',
+ );
+
+ /**
+ * @var string|TableIdentifier
+ */
+ protected $table = null;
+ protected $columns = array();
+
+ /**
+ * @var array|Select
+ */
+ protected $values = null;
+
+ /**
+ * Constructor
+ *
+ * @param null|string|TableIdentifier $table
+ */
+ public function __construct($table = null)
+ {
+ if ($table) {
+ $this->into($table);
+ }
+ }
+
+ /**
+ * Create INTO clause
+ *
+ * @param string|TableIdentifier $table
+ * @return Insert
+ */
+ public function into($table)
+ {
+ $this->table = $table;
+ return $this;
+ }
+
+ /**
+ * Specify columns
+ *
+ * @param array $columns
+ * @return Insert
+ */
+ public function columns(array $columns)
+ {
+ $this->columns = $columns;
+ return $this;
+ }
+
+ /**
+ * Specify values to insert
+ *
+ * @param array|Select $values
+ * @param string $flag one of VALUES_MERGE or VALUES_SET; defaults to VALUES_SET
+ * @throws Exception\InvalidArgumentException
+ * @return Insert
+ */
+ public function values($values, $flag = self::VALUES_SET)
+ {
+ if (!is_array($values) && !$values instanceof Select) {
+ throw new Exception\InvalidArgumentException('values() expects an array of values or Zend\Db\Sql\Select instance');
+ }
+
+ if ($values instanceof Select) {
+ if ($flag == self::VALUES_MERGE && (is_array($this->values) && !empty($this->values))) {
+ throw new Exception\InvalidArgumentException(
+ 'A Zend\Db\Sql\Select instance cannot be provided with the merge flag when values already exist.'
+ );
+ }
+ $this->values = $values;
+ return $this;
+ }
+
+ // determine if this is assoc or a set of values
+ $keys = array_keys($values);
+ $firstKey = current($keys);
+
+ if ($flag == self::VALUES_SET) {
+ $this->columns = array();
+ $this->values = array();
+ } elseif ($this->values instanceof Select) {
+ throw new Exception\InvalidArgumentException(
+ 'An array of values cannot be provided with the merge flag when a Zend\Db\Sql\Select'
+ . ' instance already exists as the value source.'
+ );
+ }
+
+ if (is_string($firstKey)) {
+ foreach ($keys as $key) {
+ if (($index = array_search($key, $this->columns)) !== false) {
+ $this->values[$index] = $values[$key];
+ } else {
+ $this->columns[] = $key;
+ $this->values[] = $values[$key];
+ }
+ }
+ } elseif (is_int($firstKey)) {
+ // determine if count of columns should match count of values
+ $this->values = array_merge($this->values, array_values($values));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Create INTO SELECT clause
+ *
+ * @param Select $select
+ * @return self
+ */
+ public function select(Select $select)
+ {
+ return $this->values($select);
+ }
+
+ /**
+ * Get raw state
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function getRawState($key = null)
+ {
+ $rawState = array(
+ 'table' => $this->table,
+ 'columns' => $this->columns,
+ 'values' => $this->values
+ );
+ return (isset($key) && array_key_exists($key, $rawState)) ? $rawState[$key] : $rawState;
+ }
+
+ /**
+ * Prepare statement
+ *
+ * @param AdapterInterface $adapter
+ * @param StatementContainerInterface $statementContainer
+ * @return void
+ */
+ public function prepareStatement(AdapterInterface $adapter, StatementContainerInterface $statementContainer)
+ {
+ $driver = $adapter->getDriver();
+ $platform = $adapter->getPlatform();
+ $parameterContainer = $statementContainer->getParameterContainer();
+
+ if (!$parameterContainer instanceof ParameterContainer) {
+ $parameterContainer = new ParameterContainer();
+ $statementContainer->setParameterContainer($parameterContainer);
+ }
+
+ $table = $this->table;
+ $schema = null;
+
+ // create quoted table name to use in insert processing
+ if ($table instanceof TableIdentifier) {
+ list($table, $schema) = $table->getTableAndSchema();
+ }
+
+ $table = $platform->quoteIdentifier($table);
+
+ if ($schema) {
+ $table = $platform->quoteIdentifier($schema) . $platform->getIdentifierSeparator() . $table;
+ }
+
+ $columns = array();
+ $values = array();
+
+ if (is_array($this->values)) {
+ foreach ($this->columns as $cIndex => $column) {
+ $columns[$cIndex] = $platform->quoteIdentifier($column);
+ if (isset($this->values[$cIndex]) && $this->values[$cIndex] instanceof Expression) {
+ $exprData = $this->processExpression($this->values[$cIndex], $platform, $driver);
+ $values[$cIndex] = $exprData->getSql();
+ $parameterContainer->merge($exprData->getParameterContainer());
+ } else {
+ $values[$cIndex] = $driver->formatParameterName($column);
+ if (isset($this->values[$cIndex])) {
+ $parameterContainer->offsetSet($column, $this->values[$cIndex]);
+ } else {
+ $parameterContainer->offsetSet($column, null);
+ }
+ }
+ }
+ $sql = sprintf(
+ $this->specifications[static::SPECIFICATION_INSERT],
+ $table,
+ implode(', ', $columns),
+ implode(', ', $values)
+ );
+ } elseif ($this->values instanceof Select) {
+ $this->values->prepareStatement($adapter, $statementContainer);
+
+ $columns = array_map(array($platform, 'quoteIdentifier'), $this->columns);
+ $columns = implode(', ', $columns);
+
+ $sql = sprintf(
+ $this->specifications[static::SPECIFICATION_SELECT],
+ $table,
+ $columns ? "($columns)" : "",
+ $statementContainer->getSql()
+ );
+ } else {
+ throw new Exception\InvalidArgumentException('values or select should be present');
+ }
+ $statementContainer->setSql($sql);
+ }
+
+ /**
+ * Get SQL string for this statement
+ *
+ * @param null|PlatformInterface $adapterPlatform Defaults to Sql92 if none provided
+ * @return string
+ */
+ public function getSqlString(PlatformInterface $adapterPlatform = null)
+ {
+ $adapterPlatform = ($adapterPlatform) ?: new Sql92;
+ $table = $this->table;
+ $schema = null;
+
+ // create quoted table name to use in insert processing
+ if ($table instanceof TableIdentifier) {
+ list($table, $schema) = $table->getTableAndSchema();
+ }
+
+ $table = $adapterPlatform->quoteIdentifier($table);
+
+ if ($schema) {
+ $table = $adapterPlatform->quoteIdentifier($schema) . $adapterPlatform->getIdentifierSeparator() . $table;
+ }
+
+ $columns = array_map(array($adapterPlatform, 'quoteIdentifier'), $this->columns);
+ $columns = implode(', ', $columns);
+
+ if (is_array($this->values)) {
+ $values = array();
+ foreach ($this->values as $value) {
+ if ($value instanceof Expression) {
+ $exprData = $this->processExpression($value, $adapterPlatform);
+ $values[] = $exprData->getSql();
+ } elseif ($value === null) {
+ $values[] = 'NULL';
+ } else {
+ $values[] = $adapterPlatform->quoteValue($value);
+ }
+ }
+ return sprintf(
+ $this->specifications[static::SPECIFICATION_INSERT],
+ $table,
+ $columns,
+ implode(', ', $values)
+ );
+ } elseif ($this->values instanceof Select) {
+ $selectString = $this->values->getSqlString($adapterPlatform);
+ if ($columns) {
+ $columns = "($columns)";
+ }
+ return sprintf(
+ $this->specifications[static::SPECIFICATION_SELECT],
+ $table,
+ $columns,
+ $selectString
+ );
+ } else {
+ throw new Exception\InvalidArgumentException('values or select should be present');
+ }
+ }
+
+ /**
+ * Overloading: variable setting
+ *
+ * Proxies to values, using VALUES_MERGE strategy
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return Insert
+ */
+ public function __set($name, $value)
+ {
+ $values = array($name => $value);
+ $this->values($values, self::VALUES_MERGE);
+ return $this;
+ }
+
+ /**
+ * Overloading: variable unset
+ *
+ * Proxies to values and columns
+ *
+ * @param string $name
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function __unset($name)
+ {
+ if (($position = array_search($name, $this->columns)) === false) {
+ throw new Exception\InvalidArgumentException('The key ' . $name . ' was not found in this objects column list');
+ }
+
+ unset($this->columns[$position]);
+ if (is_array($this->values)) {
+ unset($this->values[$position]);
+ }
+ }
+
+ /**
+ * Overloading: variable isset
+ *
+ * Proxies to columns; does a column of that name exist?
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ return in_array($name, $this->columns);
+ }
+
+ /**
+ * Overloading: variable retrieval
+ *
+ * Retrieves value by column name
+ *
+ * @param string $name
+ * @throws Exception\InvalidArgumentException
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ if (!is_array($this->values)) {
+ return null;
+ }
+ if (($position = array_search($name, $this->columns)) === false) {
+ throw new Exception\InvalidArgumentException('The key ' . $name . ' was not found in this objects column list');
+ }
+ return $this->values[$position];
+ }
+}
diff --git a/library/Zend/Db/Sql/Literal.php b/library/Zend/Db/Sql/Literal.php
new file mode 100755
index 0000000000..ba67415a3d
--- /dev/null
+++ b/library/Zend/Db/Sql/Literal.php
@@ -0,0 +1,56 @@
+literal = $literal;
+ }
+
+ /**
+ * @param string $literal
+ * @return Literal
+ */
+ public function setLiteral($literal)
+ {
+ $this->literal = $literal;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getLiteral()
+ {
+ return $this->literal;
+ }
+
+ /**
+ * @return array
+ */
+ public function getExpressionData()
+ {
+ return array(array(
+ str_replace('%', '%%', $this->literal),
+ array(),
+ array()
+ ));
+ }
+}
diff --git a/library/Zend/Db/Sql/Platform/AbstractPlatform.php b/library/Zend/Db/Sql/Platform/AbstractPlatform.php
new file mode 100755
index 0000000000..c5ddd6ce60
--- /dev/null
+++ b/library/Zend/Db/Sql/Platform/AbstractPlatform.php
@@ -0,0 +1,110 @@
+subject = $subject;
+ }
+
+ /**
+ * @param $type
+ * @param PlatformDecoratorInterface $decorator
+ */
+ public function setTypeDecorator($type, PlatformDecoratorInterface $decorator)
+ {
+ $this->decorators[$type] = $decorator;
+ }
+
+ /**
+ * @return array|PlatformDecoratorInterface[]
+ */
+ public function getDecorators()
+ {
+ return $this->decorators;
+ }
+
+ /**
+ * @param AdapterInterface $adapter
+ * @param StatementContainerInterface $statementContainer
+ * @throws Exception\RuntimeException
+ * @return void
+ */
+ public function prepareStatement(AdapterInterface $adapter, StatementContainerInterface $statementContainer)
+ {
+ if (!$this->subject instanceof PreparableSqlInterface) {
+ throw new Exception\RuntimeException('The subject does not appear to implement Zend\Db\Sql\PreparableSqlInterface, thus calling prepareStatement() has no effect');
+ }
+
+ $decoratorForType = false;
+ foreach ($this->decorators as $type => $decorator) {
+ if ($this->subject instanceof $type && $decorator instanceof PreparableSqlInterface) {
+ /** @var $decoratorForType PreparableSqlInterface|PlatformDecoratorInterface */
+ $decoratorForType = $decorator;
+ break;
+ }
+ }
+ if ($decoratorForType) {
+ $decoratorForType->setSubject($this->subject);
+ $decoratorForType->prepareStatement($adapter, $statementContainer);
+ } else {
+ $this->subject->prepareStatement($adapter, $statementContainer);
+ }
+ }
+
+ /**
+ * @param null|\Zend\Db\Adapter\Platform\PlatformInterface $adapterPlatform
+ * @return mixed
+ * @throws Exception\RuntimeException
+ */
+ public function getSqlString(PlatformInterface $adapterPlatform = null)
+ {
+ if (!$this->subject instanceof SqlInterface) {
+ throw new Exception\RuntimeException('The subject does not appear to implement Zend\Db\Sql\PreparableSqlInterface, thus calling prepareStatement() has no effect');
+ }
+
+ $decoratorForType = false;
+ foreach ($this->decorators as $type => $decorator) {
+ if ($this->subject instanceof $type && $decorator instanceof SqlInterface) {
+ /** @var $decoratorForType SqlInterface|PlatformDecoratorInterface */
+ $decoratorForType = $decorator;
+ break;
+ }
+ }
+ if ($decoratorForType) {
+ $decoratorForType->setSubject($this->subject);
+ return $decoratorForType->getSqlString($adapterPlatform);
+ }
+
+ return $this->subject->getSqlString($adapterPlatform);
+ }
+}
diff --git a/library/Zend/Db/Sql/Platform/IbmDb2/IbmDb2.php b/library/Zend/Db/Sql/Platform/IbmDb2/IbmDb2.php
new file mode 100755
index 0000000000..f744bb470f
--- /dev/null
+++ b/library/Zend/Db/Sql/Platform/IbmDb2/IbmDb2.php
@@ -0,0 +1,23 @@
+setTypeDecorator('Zend\Db\Sql\Select', ($selectDecorator) ?: new SelectDecorator());
+ }
+}
diff --git a/library/Zend/Db/Sql/Platform/IbmDb2/SelectDecorator.php b/library/Zend/Db/Sql/Platform/IbmDb2/SelectDecorator.php
new file mode 100755
index 0000000000..4ad3236940
--- /dev/null
+++ b/library/Zend/Db/Sql/Platform/IbmDb2/SelectDecorator.php
@@ -0,0 +1,192 @@
+isSelectContainDistinct;
+ }
+
+ /**
+ * @param boolean $isSelectContainDistinct
+ */
+ public function setIsSelectContainDistinct($isSelectContainDistinct)
+ {
+ $this->isSelectContainDistinct = $isSelectContainDistinct;
+ }
+
+ /**
+ * @param Select $select
+ */
+ public function setSubject($select)
+ {
+ $this->select = $select;
+ }
+
+ /**
+ * @see Select::renderTable
+ */
+ protected function renderTable($table, $alias = null)
+ {
+ return $table . ' ' . $alias;
+ }
+
+ /**
+ * @param AdapterInterface $adapter
+ * @param StatementContainerInterface $statementContainer
+ */
+ public function prepareStatement(AdapterInterface $adapter, StatementContainerInterface $statementContainer)
+ {
+ // localize variables
+ foreach (get_object_vars($this->select) as $name => $value) {
+ $this->{$name} = $value;
+ }
+ // set specifications
+ unset($this->specifications[self::LIMIT]);
+ unset($this->specifications[self::OFFSET]);
+
+ $this->specifications['LIMITOFFSET'] = null;
+ parent::prepareStatement($adapter, $statementContainer);
+ }
+
+ /**
+ * @param PlatformInterface $platform
+ * @return string
+ */
+ public function getSqlString(PlatformInterface $platform = null)
+ {
+ // localize variables
+ foreach (get_object_vars($this->select) as $name => $value) {
+ $this->{$name} = $value;
+ }
+
+ unset($this->specifications[self::LIMIT]);
+ unset($this->specifications[self::OFFSET]);
+ $this->specifications['LIMITOFFSET'] = null;
+
+ return parent::getSqlString($platform);
+ }
+
+ /**
+ * @param PlatformInterface $platform
+ * @param DriverInterface $driver
+ * @param ParameterContainer $parameterContainer
+ * @param array $sqls
+ * @param array $parameters
+ */
+ protected function processLimitOffset(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null, &$sqls, &$parameters)
+ {
+ if ($this->limit === null && $this->offset === null) {
+ return;
+ }
+
+ $selectParameters = $parameters[self::SELECT];
+
+ $starSuffix = $platform->getIdentifierSeparator() . self::SQL_STAR;
+ foreach ($selectParameters[0] as $i => $columnParameters) {
+ if ($columnParameters[0] == self::SQL_STAR
+ || (isset($columnParameters[1]) && $columnParameters[1] == self::SQL_STAR)
+ || strpos($columnParameters[0], $starSuffix)
+ ) {
+ $selectParameters[0] = array(array(self::SQL_STAR));
+ break;
+ }
+
+ if (isset($columnParameters[1])) {
+ array_shift($columnParameters);
+ $selectParameters[0][$i] = $columnParameters;
+ }
+ }
+
+ // first, produce column list without compound names (using the AS portion only)
+ array_unshift($sqls, $this->createSqlFromSpecificationAndParameters(
+ array('SELECT %1$s FROM (' => current($this->specifications[self::SELECT])),
+ $selectParameters
+ ));
+
+ if (preg_match('/DISTINCT/i', $sqls[0])) {
+ $this->setIsSelectContainDistinct(true);
+ }
+
+ if ($parameterContainer) {
+ // create bottom part of query, with offset and limit using row_number
+ $limitParamName = $driver->formatParameterName('limit');
+ $offsetParamName = $driver->formatParameterName('offset');
+
+ array_push($sqls, sprintf(
+ ") AS ZEND_IBMDB2_SERVER_LIMIT_OFFSET_EMULATION WHERE ZEND_IBMDB2_SERVER_LIMIT_OFFSET_EMULATION.ZEND_DB_ROWNUM BETWEEN %s AND %s",
+ $offsetParamName,
+ $limitParamName
+ ));
+
+ if ((int) $this->offset > 0) {
+ $parameterContainer->offsetSet('offset', (int) $this->offset + 1);
+ } else {
+ $parameterContainer->offsetSet('offset', (int) $this->offset);
+ }
+
+ $parameterContainer->offsetSet('limit', (int) $this->limit + (int) $this->offset);
+ } else {
+ if ((int) $this->offset > 0) {
+ $offset = (int) $this->offset + 1;
+ } else {
+ $offset = (int) $this->offset;
+ }
+
+ array_push($sqls, sprintf(
+ ") AS ZEND_IBMDB2_SERVER_LIMIT_OFFSET_EMULATION WHERE ZEND_IBMDB2_SERVER_LIMIT_OFFSET_EMULATION.ZEND_DB_ROWNUM BETWEEN %d AND %d",
+ $offset,
+ (int) $this->limit + (int) $this->offset
+ ));
+ }
+
+ if (isset($sqls[self::ORDER])) {
+ $orderBy = $sqls[self::ORDER];
+ unset($sqls[self::ORDER]);
+ } else {
+ $orderBy = '';
+ }
+
+ // add a column for row_number() using the order specification //dense_rank()
+ if ($this->getIsSelectContainDistinct()) {
+ $parameters[self::SELECT][0][] = array('DENSE_RANK() OVER (' . $orderBy . ')', 'ZEND_DB_ROWNUM');
+ } else {
+ $parameters[self::SELECT][0][] = array('ROW_NUMBER() OVER (' . $orderBy . ')', 'ZEND_DB_ROWNUM');
+ }
+
+ $sqls[self::SELECT] = $this->createSqlFromSpecificationAndParameters(
+ $this->specifications[self::SELECT],
+ $parameters[self::SELECT]
+ );
+ }
+}
diff --git a/library/Zend/Db/Sql/Platform/Mysql/Ddl/CreateTableDecorator.php b/library/Zend/Db/Sql/Platform/Mysql/Ddl/CreateTableDecorator.php
new file mode 100755
index 0000000000..d9cfa15563
--- /dev/null
+++ b/library/Zend/Db/Sql/Platform/Mysql/Ddl/CreateTableDecorator.php
@@ -0,0 +1,86 @@
+createTable = $subject;
+ }
+
+ /**
+ * @param null|PlatformInterface $platform
+ * @return string
+ */
+ public function getSqlString(PlatformInterface $platform = null)
+ {
+ // localize variables
+ foreach (get_object_vars($this->createTable) as $name => $value) {
+ $this->{$name} = $value;
+ }
+ return parent::getSqlString($platform);
+ }
+
+ protected function processColumns(PlatformInterface $platform = null)
+ {
+ $sqls = array();
+ foreach ($this->columns as $i => $column) {
+ $stmtContainer = $this->processExpression($column, $platform);
+ $sql = $stmtContainer->getSql();
+ $columnOptions = $column->getOptions();
+
+ foreach ($columnOptions as $coName => $coValue) {
+ switch (strtolower(str_replace(array('-', '_', ' '), '', $coName))) {
+ case 'identity':
+ case 'serial':
+ case 'autoincrement':
+ $sql .= ' AUTO_INCREMENT';
+ break;
+ /*
+ case 'primary':
+ case 'primarykey':
+ $sql .= ' PRIMARY KEY';
+ break;
+ case 'unique':
+ case 'uniquekey':
+ $sql .= ' UNIQUE KEY';
+ break;
+ */
+ case 'comment':
+ $sql .= ' COMMENT \'' . $coValue . '\'';
+ break;
+ case 'columnformat':
+ case 'format':
+ $sql .= ' COLUMN_FORMAT ' . strtoupper($coValue);
+ break;
+ case 'storage':
+ $sql .= ' STORAGE ' . strtoupper($coValue);
+ break;
+ }
+ }
+ $stmtContainer->setSql($sql);
+ $sqls[$i] = $stmtContainer;
+ }
+ return array($sqls);
+ }
+}
diff --git a/library/Zend/Db/Sql/Platform/Mysql/Mysql.php b/library/Zend/Db/Sql/Platform/Mysql/Mysql.php
new file mode 100755
index 0000000000..80455869a4
--- /dev/null
+++ b/library/Zend/Db/Sql/Platform/Mysql/Mysql.php
@@ -0,0 +1,21 @@
+setTypeDecorator('Zend\Db\Sql\Select', new SelectDecorator());
+ $this->setTypeDecorator('Zend\Db\Sql\Ddl\CreateTable', new Ddl\CreateTableDecorator());
+ }
+}
diff --git a/library/Zend/Db/Sql/Platform/Mysql/SelectDecorator.php b/library/Zend/Db/Sql/Platform/Mysql/SelectDecorator.php
new file mode 100755
index 0000000000..5c4678a730
--- /dev/null
+++ b/library/Zend/Db/Sql/Platform/Mysql/SelectDecorator.php
@@ -0,0 +1,97 @@
+select = $select;
+ }
+
+ /**
+ * @param AdapterInterface $adapter
+ * @param StatementContainerInterface $statementContainer
+ */
+ public function prepareStatement(AdapterInterface $adapter, StatementContainerInterface $statementContainer)
+ {
+ // localize variables
+ foreach (get_object_vars($this->select) as $name => $value) {
+ $this->{$name} = $value;
+ }
+ if ($this->limit === null && $this->offset !== null) {
+ $this->specifications[self::LIMIT] = 'LIMIT 18446744073709551615';
+ }
+ parent::prepareStatement($adapter, $statementContainer);
+ }
+
+ /**
+ * @param PlatformInterface $platform
+ * @return string
+ */
+ public function getSqlString(PlatformInterface $platform = null)
+ {
+ // localize variables
+ foreach (get_object_vars($this->select) as $name => $value) {
+ $this->{$name} = $value;
+ }
+ if ($this->limit === null && $this->offset !== null) {
+ $this->specifications[self::LIMIT] = 'LIMIT 18446744073709551615';
+ }
+ return parent::getSqlString($platform);
+ }
+
+ protected function processLimit(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
+ {
+ if ($this->limit === null && $this->offset !== null) {
+ return array('');
+ }
+ if ($this->limit === null) {
+ return null;
+ }
+ if ($driver) {
+ $sql = $driver->formatParameterName('limit');
+ $parameterContainer->offsetSet('limit', $this->limit, ParameterContainer::TYPE_INTEGER);
+ } else {
+ $sql = $this->limit;
+ }
+
+ return array($sql);
+ }
+
+ protected function processOffset(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
+ {
+ if ($this->offset === null) {
+ return null;
+ }
+ if ($driver) {
+ $parameterContainer->offsetSet('offset', $this->offset, ParameterContainer::TYPE_INTEGER);
+ return array($driver->formatParameterName('offset'));
+ }
+
+ return array($this->offset);
+ }
+}
diff --git a/library/Zend/Db/Sql/Platform/Oracle/Oracle.php b/library/Zend/Db/Sql/Platform/Oracle/Oracle.php
new file mode 100755
index 0000000000..e5af22fa92
--- /dev/null
+++ b/library/Zend/Db/Sql/Platform/Oracle/Oracle.php
@@ -0,0 +1,20 @@
+setTypeDecorator('Zend\Db\Sql\Select', ($selectDecorator) ?: new SelectDecorator());
+ }
+}
diff --git a/library/Zend/Db/Sql/Platform/Oracle/SelectDecorator.php b/library/Zend/Db/Sql/Platform/Oracle/SelectDecorator.php
new file mode 100755
index 0000000000..457c696bb9
--- /dev/null
+++ b/library/Zend/Db/Sql/Platform/Oracle/SelectDecorator.php
@@ -0,0 +1,180 @@
+select = $select;
+ }
+
+ /**
+ * @see \Zend\Db\Sql\Select::renderTable
+ */
+ protected function renderTable($table, $alias = null)
+ {
+ return $table . ' ' . $alias;
+ }
+
+ /**
+ * @param AdapterInterface $adapter
+ * @param StatementContainerInterface $statementContainer
+ */
+ public function prepareStatement(AdapterInterface $adapter, StatementContainerInterface $statementContainer)
+ {
+ // localize variables
+ foreach (get_object_vars($this->select) as $name => $value) {
+ $this->{$name} = $value;
+ }
+ // set specifications
+ unset($this->specifications[self::LIMIT]);
+ unset($this->specifications[self::OFFSET]);
+
+ $this->specifications['LIMITOFFSET'] = null;
+ parent::prepareStatement($adapter, $statementContainer);
+ }
+
+ /**
+ * @param PlatformInterface $platform
+ * @return string
+ */
+ public function getSqlString(PlatformInterface $platform = null)
+ {
+ // localize variables
+ foreach (get_object_vars($this->select) as $name => $value) {
+ $this->{$name} = $value;
+ }
+
+ // set specifications
+ unset($this->specifications[self::LIMIT]);
+ unset($this->specifications[self::OFFSET]);
+
+ $this->specifications['LIMITOFFSET'] = null;
+ return parent::getSqlString($platform);
+ }
+
+ /**
+ * @param PlatformInterface $platform
+ * @param DriverInterface $driver
+ * @param ParameterContainer $parameterContainer
+ * @param $sqls
+ * @param $parameters
+ * @return null
+ */
+ protected function processLimitOffset(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null, &$sqls, &$parameters)
+ {
+ if ($this->limit === null && $this->offset === null) {
+ return null;
+ }
+
+ $selectParameters = $parameters[self::SELECT];
+
+ $starSuffix = $platform->getIdentifierSeparator() . self::SQL_STAR;
+ foreach ($selectParameters[0] as $i => $columnParameters) {
+ if ($columnParameters[0] == self::SQL_STAR || (isset($columnParameters[1]) && $columnParameters[1] == self::SQL_STAR) || strpos($columnParameters[0], $starSuffix)) {
+ $selectParameters[0] = array(array(self::SQL_STAR));
+ break;
+ }
+ if (isset($columnParameters[1])) {
+ array_shift($columnParameters);
+ $selectParameters[0][$i] = $columnParameters;
+ }
+ }
+
+ if ($this->offset === null) {
+ $this->offset = 0;
+ }
+
+ // first, produce column list without compound names (using the AS portion only)
+ array_unshift($sqls, $this->createSqlFromSpecificationAndParameters(
+ array('SELECT %1$s FROM (SELECT b.%1$s, rownum b_rownum FROM (' => current($this->specifications[self::SELECT])), $selectParameters
+ ));
+
+ if ($parameterContainer) {
+ if ($this->limit === null) {
+ array_push($sqls, ') b ) WHERE b_rownum > (:offset)');
+ $parameterContainer->offsetSet('offset', $this->offset, $parameterContainer::TYPE_INTEGER);
+ } else {
+ // create bottom part of query, with offset and limit using row_number
+ array_push($sqls, ') b WHERE rownum <= (:offset+:limit)) WHERE b_rownum >= (:offset + 1)');
+ $parameterContainer->offsetSet('offset', $this->offset, $parameterContainer::TYPE_INTEGER);
+ $parameterContainer->offsetSet('limit', $this->limit, $parameterContainer::TYPE_INTEGER);
+ }
+ } else {
+ if ($this->limit === null) {
+ array_push($sqls, ') b ) WHERE b_rownum > ('. (int) $this->offset. ')'
+ );
+ } else {
+ array_push($sqls, ') b WHERE rownum <= ('
+ . (int) $this->offset
+ . '+'
+ . (int) $this->limit
+ . ')) WHERE b_rownum >= ('
+ . (int) $this->offset
+ . ' + 1)'
+ );
+ }
+ }
+
+ $sqls[self::SELECT] = $this->createSqlFromSpecificationAndParameters(
+ $this->specifications[self::SELECT], $parameters[self::SELECT]
+ );
+ }
+
+
+ protected function processJoins(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
+ {
+ if (!$this->joins) {
+ return null;
+ }
+
+ // process joins
+ $joinSpecArgArray = array();
+ foreach ($this->joins as $j => $join) {
+ $joinSpecArgArray[$j] = array();
+ // type
+ $joinSpecArgArray[$j][] = strtoupper($join['type']);
+ // table name
+ $joinSpecArgArray[$j][] = (is_array($join['name']))
+ ? $platform->quoteIdentifier(current($join['name'])) . ' ' . $platform->quoteIdentifier(key($join['name']))
+ : $platform->quoteIdentifier($join['name']);
+ // on expression
+ $joinSpecArgArray[$j][] = ($join['on'] instanceof ExpressionInterface)
+ ? $this->processExpression($join['on'], $platform, $driver, $this->processInfo['paramPrefix'] . 'join')
+ : $platform->quoteIdentifierInFragment($join['on'], array('=', 'AND', 'OR', '(', ')', 'BETWEEN')); // on
+ if ($joinSpecArgArray[$j][2] instanceof StatementContainerInterface) {
+ if ($parameterContainer) {
+ $parameterContainer->merge($joinSpecArgArray[$j][2]->getParameterContainer());
+ }
+ $joinSpecArgArray[$j][2] = $joinSpecArgArray[$j][2]->getSql();
+ }
+ }
+
+ return array($joinSpecArgArray);
+ }
+}
diff --git a/library/Zend/Db/Sql/Platform/Platform.php b/library/Zend/Db/Sql/Platform/Platform.php
new file mode 100755
index 0000000000..4a6fc2e985
--- /dev/null
+++ b/library/Zend/Db/Sql/Platform/Platform.php
@@ -0,0 +1,46 @@
+adapter = $adapter;
+ $platform = $adapter->getPlatform();
+ switch (strtolower($platform->getName())) {
+ case 'mysql':
+ $platform = new Mysql\Mysql();
+ $this->decorators = $platform->decorators;
+ break;
+ case 'sqlserver':
+ $platform = new SqlServer\SqlServer();
+ $this->decorators = $platform->decorators;
+ break;
+ case 'oracle':
+ $platform = new Oracle\Oracle();
+ $this->decorators = $platform->decorators;
+ break;
+ case 'ibm db2':
+ case 'ibm_db2':
+ case 'ibmdb2':
+ $platform = new IbmDb2\IbmDb2();
+ $this->decorators = $platform->decorators;
+ default:
+ }
+ }
+}
diff --git a/library/Zend/Db/Sql/Platform/PlatformDecoratorInterface.php b/library/Zend/Db/Sql/Platform/PlatformDecoratorInterface.php
new file mode 100755
index 0000000000..2ff7c97ce6
--- /dev/null
+++ b/library/Zend/Db/Sql/Platform/PlatformDecoratorInterface.php
@@ -0,0 +1,15 @@
+createTable = $subject;
+ return $this;
+ }
+
+ /**
+ * @param null|PlatformInterface $platform
+ * @return string
+ */
+ public function getSqlString(PlatformInterface $platform = null)
+ {
+ // localize variables
+ foreach (get_object_vars($this->createTable) as $name => $value) {
+ $this->{$name} = $value;
+ }
+ return parent::getSqlString($platform);
+ }
+
+ /**
+ * @param PlatformInterface $adapterPlatform
+ * @return array
+ */
+ protected function processTable(PlatformInterface $adapterPlatform = null)
+ {
+ $ret = array('');
+ if ($this->isTemporary) {
+ $table = '#';
+ } else {
+ $table = '';
+ }
+ $ret[] = $adapterPlatform->quoteIdentifier($table . ltrim($this->table, '#'));
+ return $ret;
+ }
+}
diff --git a/library/Zend/Db/Sql/Platform/SqlServer/SelectDecorator.php b/library/Zend/Db/Sql/Platform/SqlServer/SelectDecorator.php
new file mode 100755
index 0000000000..7667ebcbda
--- /dev/null
+++ b/library/Zend/Db/Sql/Platform/SqlServer/SelectDecorator.php
@@ -0,0 +1,145 @@
+select = $select;
+ }
+
+ /**
+ * @param AdapterInterface $adapter
+ * @param StatementContainerInterface $statementContainer
+ */
+ public function prepareStatement(AdapterInterface $adapter, StatementContainerInterface $statementContainer)
+ {
+ // localize variables
+ foreach (get_object_vars($this->select) as $name => $value) {
+ $this->{$name} = $value;
+ }
+
+ // set specifications
+ unset($this->specifications[self::LIMIT]);
+ unset($this->specifications[self::OFFSET]);
+
+ $this->specifications['LIMITOFFSET'] = null;
+ parent::prepareStatement($adapter, $statementContainer);
+
+ //set statement cursor type
+ if ($statementContainer instanceof Statement) {
+ $statementContainer->setPrepareOptions(array('Scrollable'=>\SQLSRV_CURSOR_STATIC));
+ }
+ }
+
+ /**
+ * @param PlatformInterface $platform
+ * @return string
+ */
+ public function getSqlString(PlatformInterface $platform = null)
+ {
+ // localize variables
+ foreach (get_object_vars($this->select) as $name => $value) {
+ $this->{$name} = $value;
+ }
+
+ // set specifications
+ unset($this->specifications[self::LIMIT]);
+ unset($this->specifications[self::OFFSET]);
+
+ $this->specifications['LIMITOFFSET'] = null;
+ return parent::getSqlString($platform);
+ }
+
+ /**
+ * @param PlatformInterface $platform
+ * @param DriverInterface $driver
+ * @param ParameterContainer $parameterContainer
+ * @param $sqls
+ * @param $parameters
+ * @return null
+ */
+ protected function processLimitOffset(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null, &$sqls, &$parameters)
+ {
+ if ($this->limit === null && $this->offset === null) {
+ return null;
+ }
+
+ $selectParameters = $parameters[self::SELECT];
+
+ $starSuffix = $platform->getIdentifierSeparator() . self::SQL_STAR;
+ foreach ($selectParameters[0] as $i => $columnParameters) {
+ if ($columnParameters[0] == self::SQL_STAR || (isset($columnParameters[1]) && $columnParameters[1] == self::SQL_STAR) || strpos($columnParameters[0], $starSuffix)) {
+ $selectParameters[0] = array(array(self::SQL_STAR));
+ break;
+ }
+ if (isset($columnParameters[1])) {
+ array_shift($columnParameters);
+ $selectParameters[0][$i] = $columnParameters;
+ }
+ }
+
+ // first, produce column list without compound names (using the AS portion only)
+ array_unshift($sqls, $this->createSqlFromSpecificationAndParameters(
+ array('SELECT %1$s FROM (' => current($this->specifications[self::SELECT])),
+ $selectParameters
+ ));
+
+ if ($parameterContainer) {
+ // create bottom part of query, with offset and limit using row_number
+ $limitParamName = $driver->formatParameterName('limit');
+ $offsetParamName = $driver->formatParameterName('offset');
+ $offsetForSumParamName = $driver->formatParameterName('offsetForSum');
+ array_push($sqls, ') AS [ZEND_SQL_SERVER_LIMIT_OFFSET_EMULATION] WHERE [ZEND_SQL_SERVER_LIMIT_OFFSET_EMULATION].[__ZEND_ROW_NUMBER] BETWEEN '
+ . $offsetParamName . '+1 AND ' . $limitParamName . '+' . $offsetForSumParamName);
+ $parameterContainer->offsetSet('offset', $this->offset);
+ $parameterContainer->offsetSet('limit', $this->limit);
+ $parameterContainer->offsetSetReference('offsetForSum', 'offset');
+ } else {
+ array_push($sqls, ') AS [ZEND_SQL_SERVER_LIMIT_OFFSET_EMULATION] WHERE [ZEND_SQL_SERVER_LIMIT_OFFSET_EMULATION].[__ZEND_ROW_NUMBER] BETWEEN '
+ . (int) $this->offset . '+1 AND '
+ . (int) $this->limit . '+' . (int) $this->offset
+ );
+ }
+
+ if (isset($sqls[self::ORDER])) {
+ $orderBy = $sqls[self::ORDER];
+ unset($sqls[self::ORDER]);
+ } else {
+ $orderBy = 'ORDER BY (SELECT 1)';
+ }
+
+ // add a column for row_number() using the order specification
+ $parameters[self::SELECT][0][] = array('ROW_NUMBER() OVER (' . $orderBy . ')', '[__ZEND_ROW_NUMBER]');
+
+ $sqls[self::SELECT] = $this->createSqlFromSpecificationAndParameters(
+ $this->specifications[self::SELECT],
+ $parameters[self::SELECT]
+ );
+ }
+}
diff --git a/library/Zend/Db/Sql/Platform/SqlServer/SqlServer.php b/library/Zend/Db/Sql/Platform/SqlServer/SqlServer.php
new file mode 100755
index 0000000000..ed72b77aa5
--- /dev/null
+++ b/library/Zend/Db/Sql/Platform/SqlServer/SqlServer.php
@@ -0,0 +1,21 @@
+setTypeDecorator('Zend\Db\Sql\Select', ($selectDecorator) ?: new SelectDecorator());
+ $this->setTypeDecorator('Zend\Db\Sql\Ddl\CreateTable', new Ddl\CreateTableDecorator());
+ }
+}
diff --git a/library/Zend/Db/Sql/Predicate/Between.php b/library/Zend/Db/Sql/Predicate/Between.php
new file mode 100755
index 0000000000..686b65db58
--- /dev/null
+++ b/library/Zend/Db/Sql/Predicate/Between.php
@@ -0,0 +1,142 @@
+setIdentifier($identifier);
+ }
+ if ($minValue !== null) {
+ $this->setMinValue($minValue);
+ }
+ if ($maxValue !== null) {
+ $this->setMaxValue($maxValue);
+ }
+ }
+
+ /**
+ * Set identifier for comparison
+ *
+ * @param string $identifier
+ * @return Between
+ */
+ public function setIdentifier($identifier)
+ {
+ $this->identifier = $identifier;
+ return $this;
+ }
+
+ /**
+ * Get identifier of comparison
+ *
+ * @return null|string
+ */
+ public function getIdentifier()
+ {
+ return $this->identifier;
+ }
+
+ /**
+ * Set minimum boundary for comparison
+ *
+ * @param int|float|string $minValue
+ * @return Between
+ */
+ public function setMinValue($minValue)
+ {
+ $this->minValue = $minValue;
+ return $this;
+ }
+
+ /**
+ * Get minimum boundary for comparison
+ *
+ * @return null|int|float|string
+ */
+ public function getMinValue()
+ {
+ return $this->minValue;
+ }
+
+ /**
+ * Set maximum boundary for comparison
+ *
+ * @param int|float|string $maxValue
+ * @return Between
+ */
+ public function setMaxValue($maxValue)
+ {
+ $this->maxValue = $maxValue;
+ return $this;
+ }
+
+ /**
+ * Get maximum boundary for comparison
+ *
+ * @return null|int|float|string
+ */
+ public function getMaxValue()
+ {
+ return $this->maxValue;
+ }
+
+ /**
+ * Set specification string to use in forming SQL predicate
+ *
+ * @param string $specification
+ * @return Between
+ */
+ public function setSpecification($specification)
+ {
+ $this->specification = $specification;
+ return $this;
+ }
+
+ /**
+ * Get specification string to use in forming SQL predicate
+ *
+ * @return string
+ */
+ public function getSpecification()
+ {
+ return $this->specification;
+ }
+
+ /**
+ * Return "where" parts
+ *
+ * @return array
+ */
+ public function getExpressionData()
+ {
+ return array(
+ array(
+ $this->getSpecification(),
+ array($this->identifier, $this->minValue, $this->maxValue),
+ array(self::TYPE_IDENTIFIER, self::TYPE_VALUE, self::TYPE_VALUE),
+ ),
+ );
+ }
+}
diff --git a/library/Zend/Db/Sql/Predicate/Expression.php b/library/Zend/Db/Sql/Predicate/Expression.php
new file mode 100755
index 0000000000..4822dd0815
--- /dev/null
+++ b/library/Zend/Db/Sql/Predicate/Expression.php
@@ -0,0 +1,41 @@
+setExpression($expression);
+ }
+
+ if (is_array($valueParameter)) {
+ $this->setParameters($valueParameter);
+ } else {
+ $argNum = func_num_args();
+ if ($argNum > 2 || is_scalar($valueParameter)) {
+ $parameters = array();
+ for ($i = 1; $i < $argNum; $i++) {
+ $parameters[] = func_get_arg($i);
+ }
+ $this->setParameters($parameters);
+ }
+ }
+ }
+}
diff --git a/library/Zend/Db/Sql/Predicate/In.php b/library/Zend/Db/Sql/Predicate/In.php
new file mode 100755
index 0000000000..eb7ccc36c3
--- /dev/null
+++ b/library/Zend/Db/Sql/Predicate/In.php
@@ -0,0 +1,133 @@
+setIdentifier($identifier);
+ }
+ if ($valueSet) {
+ $this->setValueSet($valueSet);
+ }
+ }
+
+ /**
+ * Set identifier for comparison
+ *
+ * @param string|array $identifier
+ * @return In
+ */
+ public function setIdentifier($identifier)
+ {
+ $this->identifier = $identifier;
+
+ return $this;
+ }
+
+ /**
+ * Get identifier of comparison
+ *
+ * @return null|string|array
+ */
+ public function getIdentifier()
+ {
+ return $this->identifier;
+ }
+
+ /**
+ * Set set of values for IN comparison
+ *
+ * @param array|Select $valueSet
+ * @throws Exception\InvalidArgumentException
+ * @return In
+ */
+ public function setValueSet($valueSet)
+ {
+ if (!is_array($valueSet) && !$valueSet instanceof Select) {
+ throw new Exception\InvalidArgumentException(
+ '$valueSet must be either an array or a Zend\Db\Sql\Select object, ' . gettype($valueSet) . ' given'
+ );
+ }
+ $this->valueSet = $valueSet;
+
+ return $this;
+ }
+
+ /**
+ * Gets set of values in IN comparision
+ *
+ * @return array|Select
+ */
+ public function getValueSet()
+ {
+ return $this->valueSet;
+ }
+
+ /**
+ * Return array of parts for where statement
+ *
+ * @return array
+ */
+ public function getExpressionData()
+ {
+ $identifier = $this->getIdentifier();
+ $values = $this->getValueSet();
+ $replacements = array();
+
+ if (is_array($identifier)) {
+ $identifierSpecFragment = '(' . implode(', ', array_fill(0, count($identifier), '%s')) . ')';
+ $types = array_fill(0, count($identifier), self::TYPE_IDENTIFIER);
+ $replacements = $identifier;
+ } else {
+ $identifierSpecFragment = '%s';
+ $replacements[] = $identifier;
+ $types = array(self::TYPE_IDENTIFIER);
+ }
+
+ if ($values instanceof Select) {
+ $specification = vsprintf(
+ $this->specification,
+ array($identifierSpecFragment, '%s')
+ );
+ $replacements[] = $values;
+ $types[] = self::TYPE_VALUE;
+ } else {
+ $specification = vsprintf(
+ $this->specification,
+ array($identifierSpecFragment, '(' . implode(', ', array_fill(0, count($values), '%s')) . ')')
+ );
+ $replacements = array_merge($replacements, $values);
+ $types = array_merge($types, array_fill(0, count($values), self::TYPE_VALUE));
+ }
+
+ return array(array(
+ $specification,
+ $replacements,
+ $types,
+ ));
+ }
+}
diff --git a/library/Zend/Db/Sql/Predicate/IsNotNull.php b/library/Zend/Db/Sql/Predicate/IsNotNull.php
new file mode 100755
index 0000000000..e09f34912a
--- /dev/null
+++ b/library/Zend/Db/Sql/Predicate/IsNotNull.php
@@ -0,0 +1,15 @@
+setIdentifier($identifier);
+ }
+ }
+
+ /**
+ * Set identifier for comparison
+ *
+ * @param string $identifier
+ * @return IsNull
+ */
+ public function setIdentifier($identifier)
+ {
+ $this->identifier = $identifier;
+ return $this;
+ }
+
+ /**
+ * Get identifier of comparison
+ *
+ * @return null|string
+ */
+ public function getIdentifier()
+ {
+ return $this->identifier;
+ }
+
+ /**
+ * Set specification string to use in forming SQL predicate
+ *
+ * @param string $specification
+ * @return IsNull
+ */
+ public function setSpecification($specification)
+ {
+ $this->specification = $specification;
+ return $this;
+ }
+
+ /**
+ * Get specification string to use in forming SQL predicate
+ *
+ * @return string
+ */
+ public function getSpecification()
+ {
+ return $this->specification;
+ }
+
+ /**
+ * Get parts for where statement
+ *
+ * @return array
+ */
+ public function getExpressionData()
+ {
+ return array(array(
+ $this->getSpecification(),
+ array($this->identifier),
+ array(self::TYPE_IDENTIFIER),
+ ));
+ }
+}
diff --git a/library/Zend/Db/Sql/Predicate/Like.php b/library/Zend/Db/Sql/Predicate/Like.php
new file mode 100755
index 0000000000..b5d6676fe0
--- /dev/null
+++ b/library/Zend/Db/Sql/Predicate/Like.php
@@ -0,0 +1,106 @@
+setIdentifier($identifier);
+ }
+ if ($like) {
+ $this->setLike($like);
+ }
+ }
+
+ /**
+ * @param string $identifier
+ * @return self
+ */
+ public function setIdentifier($identifier)
+ {
+ $this->identifier = $identifier;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getIdentifier()
+ {
+ return $this->identifier;
+ }
+
+ /**
+ * @param string $like
+ * @return self
+ */
+ public function setLike($like)
+ {
+ $this->like = $like;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getLike()
+ {
+ return $this->like;
+ }
+
+ /**
+ * @param string $specification
+ * @return self
+ */
+ public function setSpecification($specification)
+ {
+ $this->specification = $specification;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSpecification()
+ {
+ return $this->specification;
+ }
+
+ /**
+ * @return array
+ */
+ public function getExpressionData()
+ {
+ return array(
+ array($this->specification, array($this->identifier, $this->like), array(self::TYPE_IDENTIFIER, self::TYPE_VALUE))
+ );
+ }
+}
diff --git a/library/Zend/Db/Sql/Predicate/Literal.php b/library/Zend/Db/Sql/Predicate/Literal.php
new file mode 100755
index 0000000000..5ee68c998c
--- /dev/null
+++ b/library/Zend/Db/Sql/Predicate/Literal.php
@@ -0,0 +1,16 @@
+';
+ const OP_GT = '>';
+
+ const OPERATOR_GREATER_THAN_OR_EQUAL_TO = '>=';
+ const OP_GTE = '>=';
+
+ protected $allowedTypes = array(
+ self::TYPE_IDENTIFIER,
+ self::TYPE_VALUE,
+ );
+
+ protected $left = null;
+ protected $leftType = self::TYPE_IDENTIFIER;
+ protected $operator = self::OPERATOR_EQUAL_TO;
+ protected $right = null;
+ protected $rightType = self::TYPE_VALUE;
+
+ /**
+ * Constructor
+ *
+ * @param int|float|bool|string $left
+ * @param string $operator
+ * @param int|float|bool|string $right
+ * @param string $leftType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_IDENTIFIER {@see allowedTypes}
+ * @param string $rightType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_VALUE {@see allowedTypes}
+ */
+ public function __construct($left = null, $operator = self::OPERATOR_EQUAL_TO, $right = null, $leftType = self::TYPE_IDENTIFIER, $rightType = self::TYPE_VALUE)
+ {
+ if ($left !== null) {
+ $this->setLeft($left);
+ }
+
+ if ($operator !== self::OPERATOR_EQUAL_TO) {
+ $this->setOperator($operator);
+ }
+
+ if ($right !== null) {
+ $this->setRight($right);
+ }
+
+ if ($leftType !== self::TYPE_IDENTIFIER) {
+ $this->setLeftType($leftType);
+ }
+
+ if ($rightType !== self::TYPE_VALUE) {
+ $this->setRightType($rightType);
+ }
+ }
+
+ /**
+ * Set left side of operator
+ *
+ * @param int|float|bool|string $left
+ * @return Operator
+ */
+ public function setLeft($left)
+ {
+ $this->left = $left;
+ return $this;
+ }
+
+ /**
+ * Get left side of operator
+ *
+ * @return int|float|bool|string
+ */
+ public function getLeft()
+ {
+ return $this->left;
+ }
+
+ /**
+ * Set parameter type for left side of operator
+ *
+ * @param string $type TYPE_IDENTIFIER or TYPE_VALUE {@see allowedTypes}
+ * @throws Exception\InvalidArgumentException
+ * @return Operator
+ */
+ public function setLeftType($type)
+ {
+ if (!in_array($type, $this->allowedTypes)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Invalid type "%s" provided; must be of type "%s" or "%s"',
+ $type,
+ __CLASS__ . '::TYPE_IDENTIFIER',
+ __CLASS__ . '::TYPE_VALUE'
+ ));
+ }
+ $this->leftType = $type;
+ return $this;
+ }
+
+ /**
+ * Get parameter type on left side of operator
+ *
+ * @return string
+ */
+ public function getLeftType()
+ {
+ return $this->leftType;
+ }
+
+ /**
+ * Set operator string
+ *
+ * @param string $operator
+ * @return Operator
+ */
+ public function setOperator($operator)
+ {
+ $this->operator = $operator;
+ return $this;
+ }
+
+ /**
+ * Get operator string
+ *
+ * @return string
+ */
+ public function getOperator()
+ {
+ return $this->operator;
+ }
+
+ /**
+ * Set right side of operator
+ *
+ * @param int|float|bool|string $value
+ * @return Operator
+ */
+ public function setRight($value)
+ {
+ $this->right = $value;
+ return $this;
+ }
+
+ /**
+ * Get right side of operator
+ *
+ * @return int|float|bool|string
+ */
+ public function getRight()
+ {
+ return $this->right;
+ }
+
+ /**
+ * Set parameter type for right side of operator
+ *
+ * @param string $type TYPE_IDENTIFIER or TYPE_VALUE {@see allowedTypes}
+ * @throws Exception\InvalidArgumentException
+ * @return Operator
+ */
+ public function setRightType($type)
+ {
+ if (!in_array($type, $this->allowedTypes)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Invalid type "%s" provided; must be of type "%s" or "%s"',
+ $type,
+ __CLASS__ . '::TYPE_IDENTIFIER',
+ __CLASS__ . '::TYPE_VALUE'
+ ));
+ }
+ $this->rightType = $type;
+ return $this;
+ }
+
+ /**
+ * Get parameter type on right side of operator
+ *
+ * @return string
+ */
+ public function getRightType()
+ {
+ return $this->rightType;
+ }
+
+ /**
+ * Get predicate parts for where statement
+ *
+ * @return array
+ */
+ public function getExpressionData()
+ {
+ return array(array(
+ '%s ' . $this->operator . ' %s',
+ array($this->left, $this->right),
+ array($this->leftType, $this->rightType)
+ ));
+ }
+}
diff --git a/library/Zend/Db/Sql/Predicate/Predicate.php b/library/Zend/Db/Sql/Predicate/Predicate.php
new file mode 100755
index 0000000000..e9ef5757dc
--- /dev/null
+++ b/library/Zend/Db/Sql/Predicate/Predicate.php
@@ -0,0 +1,409 @@
+setUnnest($this);
+ $this->addPredicate($predicateSet, ($this->nextPredicateCombineOperator) ?: $this->defaultCombination);
+ $this->nextPredicateCombineOperator = null;
+ return $predicateSet;
+ }
+
+ /**
+ * Indicate what predicate will be unnested
+ *
+ * @param Predicate $predicate
+ * @return void
+ */
+ public function setUnnest(Predicate $predicate)
+ {
+ $this->unnest = $predicate;
+ }
+
+ /**
+ * Indicate end of nested predicate
+ *
+ * @return Predicate
+ * @throws RuntimeException
+ */
+ public function unnest()
+ {
+ if ($this->unnest == null) {
+ throw new RuntimeException('Not nested');
+ }
+ $unnset = $this->unnest;
+ $this->unnest = null;
+ return $unnset;
+ }
+
+ /**
+ * Create "Equal To" predicate
+ *
+ * Utilizes Operator predicate
+ *
+ * @param int|float|bool|string $left
+ * @param int|float|bool|string $right
+ * @param string $leftType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_IDENTIFIER {@see allowedTypes}
+ * @param string $rightType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_VALUE {@see allowedTypes}
+ * @return Predicate
+ */
+ public function equalTo($left, $right, $leftType = self::TYPE_IDENTIFIER, $rightType = self::TYPE_VALUE)
+ {
+ $this->addPredicate(
+ new Operator($left, Operator::OPERATOR_EQUAL_TO, $right, $leftType, $rightType),
+ ($this->nextPredicateCombineOperator) ?: $this->defaultCombination
+ );
+ $this->nextPredicateCombineOperator = null;
+
+ return $this;
+ }
+
+ /**
+ * Create "Not Equal To" predicate
+ *
+ * Utilizes Operator predicate
+ *
+ * @param int|float|bool|string $left
+ * @param int|float|bool|string $right
+ * @param string $leftType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_IDENTIFIER {@see allowedTypes}
+ * @param string $rightType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_VALUE {@see allowedTypes}
+ * @return Predicate
+ */
+ public function notEqualTo($left, $right, $leftType = self::TYPE_IDENTIFIER, $rightType = self::TYPE_VALUE)
+ {
+ $this->addPredicate(
+ new Operator($left, Operator::OPERATOR_NOT_EQUAL_TO, $right, $leftType, $rightType),
+ ($this->nextPredicateCombineOperator) ?: $this->defaultCombination
+ );
+ $this->nextPredicateCombineOperator = null;
+
+ return $this;
+ }
+
+ /**
+ * Create "Less Than" predicate
+ *
+ * Utilizes Operator predicate
+ *
+ * @param int|float|bool|string $left
+ * @param int|float|bool|string $right
+ * @param string $leftType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_IDENTIFIER {@see allowedTypes}
+ * @param string $rightType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_VALUE {@see allowedTypes}
+ * @return Predicate
+ */
+ public function lessThan($left, $right, $leftType = self::TYPE_IDENTIFIER, $rightType = self::TYPE_VALUE)
+ {
+ $this->addPredicate(
+ new Operator($left, Operator::OPERATOR_LESS_THAN, $right, $leftType, $rightType),
+ ($this->nextPredicateCombineOperator) ?: $this->defaultCombination
+ );
+ $this->nextPredicateCombineOperator = null;
+
+ return $this;
+ }
+
+ /**
+ * Create "Greater Than" predicate
+ *
+ * Utilizes Operator predicate
+ *
+ * @param int|float|bool|string $left
+ * @param int|float|bool|string $right
+ * @param string $leftType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_IDENTIFIER {@see allowedTypes}
+ * @param string $rightType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_VALUE {@see allowedTypes}
+ * @return Predicate
+ */
+ public function greaterThan($left, $right, $leftType = self::TYPE_IDENTIFIER, $rightType = self::TYPE_VALUE)
+ {
+ $this->addPredicate(
+ new Operator($left, Operator::OPERATOR_GREATER_THAN, $right, $leftType, $rightType),
+ ($this->nextPredicateCombineOperator) ?: $this->defaultCombination
+ );
+ $this->nextPredicateCombineOperator = null;
+
+ return $this;
+ }
+
+ /**
+ * Create "Less Than Or Equal To" predicate
+ *
+ * Utilizes Operator predicate
+ *
+ * @param int|float|bool|string $left
+ * @param int|float|bool|string $right
+ * @param string $leftType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_IDENTIFIER {@see allowedTypes}
+ * @param string $rightType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_VALUE {@see allowedTypes}
+ * @return Predicate
+ */
+ public function lessThanOrEqualTo($left, $right, $leftType = self::TYPE_IDENTIFIER, $rightType = self::TYPE_VALUE)
+ {
+ $this->addPredicate(
+ new Operator($left, Operator::OPERATOR_LESS_THAN_OR_EQUAL_TO, $right, $leftType, $rightType),
+ ($this->nextPredicateCombineOperator) ?: $this->defaultCombination
+ );
+ $this->nextPredicateCombineOperator = null;
+
+ return $this;
+ }
+
+ /**
+ * Create "Greater Than Or Equal To" predicate
+ *
+ * Utilizes Operator predicate
+ *
+ * @param int|float|bool|string $left
+ * @param int|float|bool|string $right
+ * @param string $leftType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_IDENTIFIER {@see allowedTypes}
+ * @param string $rightType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_VALUE {@see allowedTypes}
+ * @return Predicate
+ */
+ public function greaterThanOrEqualTo($left, $right, $leftType = self::TYPE_IDENTIFIER, $rightType = self::TYPE_VALUE)
+ {
+ $this->addPredicate(
+ new Operator($left, Operator::OPERATOR_GREATER_THAN_OR_EQUAL_TO, $right, $leftType, $rightType),
+ ($this->nextPredicateCombineOperator) ?: $this->defaultCombination
+ );
+ $this->nextPredicateCombineOperator = null;
+
+ return $this;
+ }
+
+ /**
+ * Create "Like" predicate
+ *
+ * Utilizes Like predicate
+ *
+ * @param string $identifier
+ * @param string $like
+ * @return Predicate
+ */
+ public function like($identifier, $like)
+ {
+ $this->addPredicate(
+ new Like($identifier, $like),
+ ($this->nextPredicateCombineOperator) ?: $this->defaultCombination
+ );
+ $this->nextPredicateCombineOperator = null;
+
+ return $this;
+ }
+ /**
+ * Create "notLike" predicate
+ *
+ * Utilizes In predicate
+ *
+ * @param string $identifier
+ * @param string $notLike
+ * @return Predicate
+ */
+ public function notLike($identifier, $notLike)
+ {
+ $this->addPredicate(
+ new NotLike($identifier, $notLike),
+ ($this->nextPredicateCombineOperator) ? : $this->defaultCombination
+ );
+ $this->nextPredicateCombineOperator = null;
+ return $this;
+ }
+
+ /**
+ * Create an expression, with parameter placeholders
+ *
+ * @param $expression
+ * @param $parameters
+ * @return $this
+ */
+ public function expression($expression, $parameters)
+ {
+ $this->addPredicate(
+ new Expression($expression, $parameters),
+ ($this->nextPredicateCombineOperator) ?: $this->defaultCombination
+ );
+ $this->nextPredicateCombineOperator = null;
+
+ return $this;
+ }
+
+ /**
+ * Create "Literal" predicate
+ *
+ * Literal predicate, for parameters, use expression()
+ *
+ * @param string $literal
+ * @return Predicate
+ */
+ public function literal($literal)
+ {
+ // process deprecated parameters from previous literal($literal, $parameters = null) signature
+ if (func_num_args() >= 2) {
+ $parameters = func_get_arg(1);
+ $predicate = new Expression($literal, $parameters);
+ }
+
+ // normal workflow for "Literals" here
+ if (!isset($predicate)) {
+ $predicate = new Literal($literal);
+ }
+
+ $this->addPredicate(
+ $predicate,
+ ($this->nextPredicateCombineOperator) ?: $this->defaultCombination
+ );
+ $this->nextPredicateCombineOperator = null;
+
+ return $this;
+ }
+
+ /**
+ * Create "IS NULL" predicate
+ *
+ * Utilizes IsNull predicate
+ *
+ * @param string $identifier
+ * @return Predicate
+ */
+ public function isNull($identifier)
+ {
+ $this->addPredicate(
+ new IsNull($identifier),
+ ($this->nextPredicateCombineOperator) ?: $this->defaultCombination
+ );
+ $this->nextPredicateCombineOperator = null;
+
+ return $this;
+ }
+
+ /**
+ * Create "IS NOT NULL" predicate
+ *
+ * Utilizes IsNotNull predicate
+ *
+ * @param string $identifier
+ * @return Predicate
+ */
+ public function isNotNull($identifier)
+ {
+ $this->addPredicate(
+ new IsNotNull($identifier),
+ ($this->nextPredicateCombineOperator) ?: $this->defaultCombination
+ );
+ $this->nextPredicateCombineOperator = null;
+
+ return $this;
+ }
+
+ /**
+ * Create "IN" predicate
+ *
+ * Utilizes In predicate
+ *
+ * @param string $identifier
+ * @param array|\Zend\Db\Sql\Select $valueSet
+ * @return Predicate
+ */
+ public function in($identifier, $valueSet = null)
+ {
+ $this->addPredicate(
+ new In($identifier, $valueSet),
+ ($this->nextPredicateCombineOperator) ?: $this->defaultCombination
+ );
+ $this->nextPredicateCombineOperator = null;
+
+ return $this;
+ }
+
+ /**
+ * Create "NOT IN" predicate
+ *
+ * Utilizes NotIn predicate
+ *
+ * @param string $identifier
+ * @param array|\Zend\Db\Sql\Select $valueSet
+ * @return Predicate
+ */
+ public function notIn($identifier, $valueSet = null)
+ {
+ $this->addPredicate(
+ new NotIn($identifier, $valueSet),
+ ($this->nextPredicateCombineOperator) ?: $this->defaultCombination
+ );
+ $this->nextPredicateCombineOperator = null;
+
+ return $this;
+ }
+
+ /**
+ * Create "between" predicate
+ *
+ * Utilizes Between predicate
+ *
+ * @param string $identifier
+ * @param int|float|string $minValue
+ * @param int|float|string $maxValue
+ * @return Predicate
+ */
+ public function between($identifier, $minValue, $maxValue)
+ {
+ $this->addPredicate(
+ new Between($identifier, $minValue, $maxValue),
+ ($this->nextPredicateCombineOperator) ?: $this->defaultCombination
+ );
+ $this->nextPredicateCombineOperator = null;
+
+ return $this;
+ }
+
+ /**
+ * Overloading
+ *
+ * Overloads "or", "and", "nest", and "unnest"
+ *
+ * @param string $name
+ * @return Predicate
+ */
+ public function __get($name)
+ {
+ switch (strtolower($name)) {
+ case 'or':
+ $this->nextPredicateCombineOperator = self::OP_OR;
+ break;
+ case 'and':
+ $this->nextPredicateCombineOperator = self::OP_AND;
+ break;
+ case 'nest':
+ return $this->nest();
+ case 'unnest':
+ return $this->unnest();
+ }
+ return $this;
+ }
+}
diff --git a/library/Zend/Db/Sql/Predicate/PredicateInterface.php b/library/Zend/Db/Sql/Predicate/PredicateInterface.php
new file mode 100755
index 0000000000..68b197f844
--- /dev/null
+++ b/library/Zend/Db/Sql/Predicate/PredicateInterface.php
@@ -0,0 +1,16 @@
+defaultCombination = $defaultCombination;
+ if ($predicates) {
+ foreach ($predicates as $predicate) {
+ $this->addPredicate($predicate);
+ }
+ }
+ }
+
+ /**
+ * Add predicate to set
+ *
+ * @param PredicateInterface $predicate
+ * @param string $combination
+ * @return PredicateSet
+ */
+ public function addPredicate(PredicateInterface $predicate, $combination = null)
+ {
+ if ($combination === null || !in_array($combination, array(self::OP_AND, self::OP_OR))) {
+ $combination = $this->defaultCombination;
+ }
+
+ if ($combination == self::OP_OR) {
+ $this->orPredicate($predicate);
+ return $this;
+ }
+
+ $this->andPredicate($predicate);
+ return $this;
+ }
+
+ public function addPredicates($predicates, $combination = self::OP_AND)
+ {
+ if ($predicates === null) {
+ throw new Exception\InvalidArgumentException('Predicate cannot be null');
+ }
+ if ($predicates instanceof PredicateInterface) {
+ $this->addPredicate($predicates, $combination);
+ return $this;
+ }
+ if ($predicates instanceof \Closure) {
+ $predicates($this);
+ return $this;
+ }
+ if (is_string($predicates)) {
+ // String $predicate should be passed as an expression
+ $predicates = (strpos($predicates, Expression::PLACEHOLDER) !== false)
+ ? new Expression($predicates) : new Literal($predicates);
+ $this->addPredicate($predicates, $combination);
+ return $this;
+ }
+ if (is_array($predicates)) {
+ foreach ($predicates as $pkey => $pvalue) {
+ // loop through predicates
+ if (is_string($pkey)) {
+ if (strpos($pkey, '?') !== false) {
+ // First, process strings that the abstraction replacement character ?
+ // as an Expression predicate
+ $predicates = new Expression($pkey, $pvalue);
+ } elseif ($pvalue === null) { // Otherwise, if still a string, do something intelligent with the PHP type provided
+ // map PHP null to SQL IS NULL expression
+ $predicates = new IsNull($pkey, $pvalue);
+ } elseif (is_array($pvalue)) {
+ // if the value is an array, assume IN() is desired
+ $predicates = new In($pkey, $pvalue);
+ } elseif ($pvalue instanceof PredicateInterface) {
+ throw new Exception\InvalidArgumentException(
+ 'Using Predicate must not use string keys'
+ );
+ } else {
+ // otherwise assume that array('foo' => 'bar') means "foo" = 'bar'
+ $predicates = new Operator($pkey, Operator::OP_EQ, $pvalue);
+ }
+ } elseif ($pvalue instanceof PredicateInterface) {
+ // Predicate type is ok
+ $predicates = $pvalue;
+ } else {
+ // must be an array of expressions (with int-indexed array)
+ $predicates = (strpos($pvalue, Expression::PLACEHOLDER) !== false)
+ ? new Expression($pvalue) : new Literal($pvalue);
+ }
+ $this->addPredicate($predicates, $combination);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Return the predicates
+ *
+ * @return PredicateInterface[]
+ */
+ public function getPredicates()
+ {
+ return $this->predicates;
+ }
+
+ /**
+ * Add predicate using OR operator
+ *
+ * @param PredicateInterface $predicate
+ * @return PredicateSet
+ */
+ public function orPredicate(PredicateInterface $predicate)
+ {
+ $this->predicates[] = array(self::OP_OR, $predicate);
+ return $this;
+ }
+
+ /**
+ * Add predicate using AND operator
+ *
+ * @param PredicateInterface $predicate
+ * @return PredicateSet
+ */
+ public function andPredicate(PredicateInterface $predicate)
+ {
+ $this->predicates[] = array(self::OP_AND, $predicate);
+ return $this;
+ }
+
+ /**
+ * Get predicate parts for where statement
+ *
+ * @return array
+ */
+ public function getExpressionData()
+ {
+ $parts = array();
+ for ($i = 0, $count = count($this->predicates); $i < $count; $i++) {
+ /** @var $predicate PredicateInterface */
+ $predicate = $this->predicates[$i][1];
+
+ if ($predicate instanceof PredicateSet) {
+ $parts[] = '(';
+ }
+
+ $parts = array_merge($parts, $predicate->getExpressionData());
+
+ if ($predicate instanceof PredicateSet) {
+ $parts[] = ')';
+ }
+
+ if (isset($this->predicates[$i+1])) {
+ $parts[] = sprintf(' %s ', $this->predicates[$i+1][0]);
+ }
+ }
+ return $parts;
+ }
+
+ /**
+ * Get count of attached predicates
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->predicates);
+ }
+}
diff --git a/library/Zend/Db/Sql/PreparableSqlInterface.php b/library/Zend/Db/Sql/PreparableSqlInterface.php
new file mode 100755
index 0000000000..ea32cd65b7
--- /dev/null
+++ b/library/Zend/Db/Sql/PreparableSqlInterface.php
@@ -0,0 +1,23 @@
+ '%1$s',
+ self::SELECT => array(
+ 'SELECT %1$s FROM %2$s' => array(
+ array(1 => '%1$s', 2 => '%1$s AS %2$s', 'combinedby' => ', '),
+ null
+ ),
+ 'SELECT %1$s %2$s FROM %3$s' => array(
+ null,
+ array(1 => '%1$s', 2 => '%1$s AS %2$s', 'combinedby' => ', '),
+ null
+ ),
+ 'SELECT %1$s' => array(
+ array(1 => '%1$s', 2 => '%1$s AS %2$s', 'combinedby' => ', '),
+ ),
+ ),
+ self::JOINS => array(
+ '%1$s' => array(
+ array(3 => '%1$s JOIN %2$s ON %3$s', 'combinedby' => ' ')
+ )
+ ),
+ self::WHERE => 'WHERE %1$s',
+ self::GROUP => array(
+ 'GROUP BY %1$s' => array(
+ array(1 => '%1$s', 'combinedby' => ', ')
+ )
+ ),
+ self::HAVING => 'HAVING %1$s',
+ self::ORDER => array(
+ 'ORDER BY %1$s' => array(
+ array(1 => '%1$s', 2 => '%1$s %2$s', 'combinedby' => ', ')
+ )
+ ),
+ self::LIMIT => 'LIMIT %1$s',
+ self::OFFSET => 'OFFSET %1$s',
+ 'statementEnd' => '%1$s',
+ self::COMBINE => '%1$s ( %2$s )',
+ );
+
+ /**
+ * @var bool
+ */
+ protected $tableReadOnly = false;
+
+ /**
+ * @var bool
+ */
+ protected $prefixColumnsWithTable = true;
+
+ /**
+ * @var string|array|TableIdentifier
+ */
+ protected $table = null;
+
+ /**
+ * @var null|string|Expression
+ */
+ protected $quantifier = null;
+
+ /**
+ * @var array
+ */
+ protected $columns = array(self::SQL_STAR);
+
+ /**
+ * @var array
+ */
+ protected $joins = array();
+
+ /**
+ * @var Where
+ */
+ protected $where = null;
+
+ /**
+ * @var array
+ */
+ protected $order = array();
+
+ /**
+ * @var null|array
+ */
+ protected $group = null;
+
+ /**
+ * @var null|string|array
+ */
+ protected $having = null;
+
+ /**
+ * @var int|null
+ */
+ protected $limit = null;
+
+ /**
+ * @var int|null
+ */
+ protected $offset = null;
+
+ /**
+ * @var array
+ */
+ protected $combine = array();
+
+ /**
+ * Constructor
+ *
+ * @param null|string|array|TableIdentifier $table
+ */
+ public function __construct($table = null)
+ {
+ if ($table) {
+ $this->from($table);
+ $this->tableReadOnly = true;
+ }
+
+ $this->where = new Where;
+ $this->having = new Having;
+ }
+
+ /**
+ * Create from clause
+ *
+ * @param string|array|TableIdentifier $table
+ * @throws Exception\InvalidArgumentException
+ * @return Select
+ */
+ public function from($table)
+ {
+ if ($this->tableReadOnly) {
+ throw new Exception\InvalidArgumentException('Since this object was created with a table and/or schema in the constructor, it is read only.');
+ }
+
+ if (!is_string($table) && !is_array($table) && !$table instanceof TableIdentifier) {
+ throw new Exception\InvalidArgumentException('$table must be a string, array, or an instance of TableIdentifier');
+ }
+
+ if (is_array($table) && (!is_string(key($table)) || count($table) !== 1)) {
+ throw new Exception\InvalidArgumentException('from() expects $table as an array is a single element associative array');
+ }
+
+ $this->table = $table;
+ return $this;
+ }
+
+ /**
+ * @param string|Expression $quantifier DISTINCT|ALL
+ * @return Select
+ */
+ public function quantifier($quantifier)
+ {
+ if (!is_string($quantifier) && !$quantifier instanceof Expression) {
+ throw new Exception\InvalidArgumentException(
+ 'Quantifier must be one of DISTINCT, ALL, or some platform specific Expression object'
+ );
+ }
+ $this->quantifier = $quantifier;
+ return $this;
+ }
+
+ /**
+ * Specify columns from which to select
+ *
+ * Possible valid states:
+ *
+ * array(*)
+ *
+ * array(value, ...)
+ * value can be strings or Expression objects
+ *
+ * array(string => value, ...)
+ * key string will be use as alias,
+ * value can be string or Expression objects
+ *
+ * @param array $columns
+ * @param bool $prefixColumnsWithTable
+ * @return Select
+ */
+ public function columns(array $columns, $prefixColumnsWithTable = true)
+ {
+ $this->columns = $columns;
+ $this->prefixColumnsWithTable = (bool) $prefixColumnsWithTable;
+ return $this;
+ }
+
+ /**
+ * Create join clause
+ *
+ * @param string|array $name
+ * @param string $on
+ * @param string|array $columns
+ * @param string $type one of the JOIN_* constants
+ * @throws Exception\InvalidArgumentException
+ * @return Select
+ */
+ public function join($name, $on, $columns = self::SQL_STAR, $type = self::JOIN_INNER)
+ {
+ if (is_array($name) && (!is_string(key($name)) || count($name) !== 1)) {
+ throw new Exception\InvalidArgumentException(
+ sprintf("join() expects '%s' as an array is a single element associative array", array_shift($name))
+ );
+ }
+ if (!is_array($columns)) {
+ $columns = array($columns);
+ }
+ $this->joins[] = array(
+ 'name' => $name,
+ 'on' => $on,
+ 'columns' => $columns,
+ 'type' => $type
+ );
+ return $this;
+ }
+
+ /**
+ * Create where clause
+ *
+ * @param Where|\Closure|string|array|Predicate\PredicateInterface $predicate
+ * @param string $combination One of the OP_* constants from Predicate\PredicateSet
+ * @throws Exception\InvalidArgumentException
+ * @return Select
+ */
+ public function where($predicate, $combination = Predicate\PredicateSet::OP_AND)
+ {
+ if ($predicate instanceof Where) {
+ $this->where = $predicate;
+ } else {
+ $this->where->addPredicates($predicate, $combination);
+ }
+ return $this;
+ }
+
+ public function group($group)
+ {
+ if (is_array($group)) {
+ foreach ($group as $o) {
+ $this->group[] = $o;
+ }
+ } else {
+ $this->group[] = $group;
+ }
+ return $this;
+ }
+
+ /**
+ * Create where clause
+ *
+ * @param Where|\Closure|string|array $predicate
+ * @param string $combination One of the OP_* constants from Predicate\PredicateSet
+ * @return Select
+ */
+ public function having($predicate, $combination = Predicate\PredicateSet::OP_AND)
+ {
+ if ($predicate instanceof Having) {
+ $this->having = $predicate;
+ } else {
+ $this->having->addPredicates($predicate, $combination);
+ }
+ return $this;
+ }
+
+ /**
+ * @param string|array $order
+ * @return Select
+ */
+ public function order($order)
+ {
+ if (is_string($order)) {
+ if (strpos($order, ',') !== false) {
+ $order = preg_split('#,\s+#', $order);
+ } else {
+ $order = (array) $order;
+ }
+ } elseif (!is_array($order)) {
+ $order = array($order);
+ }
+ foreach ($order as $k => $v) {
+ if (is_string($k)) {
+ $this->order[$k] = $v;
+ } else {
+ $this->order[] = $v;
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * @param int $limit
+ * @return Select
+ */
+ public function limit($limit)
+ {
+ if (!is_numeric($limit)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects parameter to be numeric, "%s" given',
+ __METHOD__,
+ (is_object($limit) ? get_class($limit) : gettype($limit))
+ ));
+ }
+
+ $this->limit = $limit;
+ return $this;
+ }
+
+ /**
+ * @param int $offset
+ * @return Select
+ */
+ public function offset($offset)
+ {
+ if (!is_numeric($offset)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects parameter to be numeric, "%s" given',
+ __METHOD__,
+ (is_object($offset) ? get_class($offset) : gettype($offset))
+ ));
+ }
+
+ $this->offset = $offset;
+ return $this;
+ }
+
+ /**
+ * @param Select $select
+ * @param string $type
+ * @param string $modifier
+ * @return Select
+ * @throws Exception\InvalidArgumentException
+ */
+ public function combine(Select $select, $type = self::COMBINE_UNION, $modifier = '')
+ {
+ if ($this->combine !== array()) {
+ throw new Exception\InvalidArgumentException('This Select object is already combined and cannot be combined with multiple Selects objects');
+ }
+ $this->combine = array(
+ 'select' => $select,
+ 'type' => $type,
+ 'modifier' => $modifier
+ );
+ return $this;
+ }
+
+ /**
+ * @param string $part
+ * @return Select
+ * @throws Exception\InvalidArgumentException
+ */
+ public function reset($part)
+ {
+ switch ($part) {
+ case self::TABLE:
+ if ($this->tableReadOnly) {
+ throw new Exception\InvalidArgumentException(
+ 'Since this object was created with a table and/or schema in the constructor, it is read only.'
+ );
+ }
+ $this->table = null;
+ break;
+ case self::QUANTIFIER:
+ $this->quantifier = null;
+ break;
+ case self::COLUMNS:
+ $this->columns = array();
+ break;
+ case self::JOINS:
+ $this->joins = array();
+ break;
+ case self::WHERE:
+ $this->where = new Where;
+ break;
+ case self::GROUP:
+ $this->group = null;
+ break;
+ case self::HAVING:
+ $this->having = new Having;
+ break;
+ case self::LIMIT:
+ $this->limit = null;
+ break;
+ case self::OFFSET:
+ $this->offset = null;
+ break;
+ case self::ORDER:
+ $this->order = array();
+ break;
+ case self::COMBINE:
+ $this->combine = array();
+ break;
+ }
+ return $this;
+ }
+
+ public function setSpecification($index, $specification)
+ {
+ if (!method_exists($this, 'process' . $index)) {
+ throw new Exception\InvalidArgumentException('Not a valid specification name.');
+ }
+ $this->specifications[$index] = $specification;
+ return $this;
+ }
+
+ public function getRawState($key = null)
+ {
+ $rawState = array(
+ self::TABLE => $this->table,
+ self::QUANTIFIER => $this->quantifier,
+ self::COLUMNS => $this->columns,
+ self::JOINS => $this->joins,
+ self::WHERE => $this->where,
+ self::ORDER => $this->order,
+ self::GROUP => $this->group,
+ self::HAVING => $this->having,
+ self::LIMIT => $this->limit,
+ self::OFFSET => $this->offset,
+ self::COMBINE => $this->combine
+ );
+ return (isset($key) && array_key_exists($key, $rawState)) ? $rawState[$key] : $rawState;
+ }
+
+ /**
+ * Prepare statement
+ *
+ * @param AdapterInterface $adapter
+ * @param StatementContainerInterface $statementContainer
+ * @return void
+ */
+ public function prepareStatement(AdapterInterface $adapter, StatementContainerInterface $statementContainer)
+ {
+ // ensure statement has a ParameterContainer
+ $parameterContainer = $statementContainer->getParameterContainer();
+ if (!$parameterContainer instanceof ParameterContainer) {
+ $parameterContainer = new ParameterContainer();
+ $statementContainer->setParameterContainer($parameterContainer);
+ }
+
+ $sqls = array();
+ $parameters = array();
+ $platform = $adapter->getPlatform();
+ $driver = $adapter->getDriver();
+
+ foreach ($this->specifications as $name => $specification) {
+ $parameters[$name] = $this->{'process' . $name}($platform, $driver, $parameterContainer, $sqls, $parameters);
+ if ($specification && is_array($parameters[$name])) {
+ $sqls[$name] = $this->createSqlFromSpecificationAndParameters($specification, $parameters[$name]);
+ }
+ }
+
+ $sql = implode(' ', $sqls);
+
+ $statementContainer->setSql($sql);
+ return;
+ }
+
+ /**
+ * Get SQL string for statement
+ *
+ * @param null|PlatformInterface $adapterPlatform If null, defaults to Sql92
+ * @return string
+ */
+ public function getSqlString(PlatformInterface $adapterPlatform = null)
+ {
+ // get platform, or create default
+ $adapterPlatform = ($adapterPlatform) ?: new AdapterSql92Platform;
+
+ $sqls = array();
+ $parameters = array();
+
+ foreach ($this->specifications as $name => $specification) {
+ $parameters[$name] = $this->{'process' . $name}($adapterPlatform, null, null, $sqls, $parameters);
+ if ($specification && is_array($parameters[$name])) {
+ $sqls[$name] = $this->createSqlFromSpecificationAndParameters($specification, $parameters[$name]);
+ }
+ }
+
+ $sql = implode(' ', $sqls);
+ return $sql;
+ }
+
+ /**
+ * Returns whether the table is read only or not.
+ *
+ * @return bool
+ */
+ public function isTableReadOnly()
+ {
+ return $this->tableReadOnly;
+ }
+
+ /**
+ * Render table with alias in from/join parts
+ *
+ * @todo move TableIdentifier concatination here
+ * @param string $table
+ * @param string $alias
+ * @return string
+ */
+ protected function renderTable($table, $alias = null)
+ {
+ $sql = $table;
+ if ($alias) {
+ $sql .= ' AS ' . $alias;
+ }
+ return $sql;
+ }
+
+ protected function processStatementStart(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
+ {
+ if ($this->combine !== array()) {
+ return array('(');
+ }
+ }
+
+ protected function processStatementEnd(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
+ {
+ if ($this->combine !== array()) {
+ return array(')');
+ }
+ }
+
+ /**
+ * Process the select part
+ *
+ * @param PlatformInterface $platform
+ * @param DriverInterface $driver
+ * @param ParameterContainer $parameterContainer
+ * @return null|array
+ */
+ protected function processSelect(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
+ {
+ $expr = 1;
+
+ if ($this->table) {
+ $table = $this->table;
+ $schema = $alias = null;
+
+ if (is_array($table)) {
+ $alias = key($this->table);
+ $table = current($this->table);
+ }
+
+ // create quoted table name to use in columns processing
+ if ($table instanceof TableIdentifier) {
+ list($table, $schema) = $table->getTableAndSchema();
+ }
+
+ if ($table instanceof Select) {
+ $table = '(' . $this->processSubselect($table, $platform, $driver, $parameterContainer) . ')';
+ } else {
+ $table = $platform->quoteIdentifier($table);
+ }
+
+ if ($schema) {
+ $table = $platform->quoteIdentifier($schema) . $platform->getIdentifierSeparator() . $table;
+ }
+
+ if ($alias) {
+ $fromTable = $platform->quoteIdentifier($alias);
+ $table = $this->renderTable($table, $fromTable);
+ } else {
+ $fromTable = $table;
+ }
+ } else {
+ $fromTable = '';
+ }
+
+ if ($this->prefixColumnsWithTable) {
+ $fromTable .= $platform->getIdentifierSeparator();
+ } else {
+ $fromTable = '';
+ }
+
+ // process table columns
+ $columns = array();
+ foreach ($this->columns as $columnIndexOrAs => $column) {
+ $columnName = '';
+ if ($column === self::SQL_STAR) {
+ $columns[] = array($fromTable . self::SQL_STAR);
+ continue;
+ }
+
+ if ($column instanceof ExpressionInterface) {
+ $columnParts = $this->processExpression(
+ $column,
+ $platform,
+ $driver,
+ $this->processInfo['paramPrefix'] . ((is_string($columnIndexOrAs)) ? $columnIndexOrAs : 'column')
+ );
+ if ($parameterContainer) {
+ $parameterContainer->merge($columnParts->getParameterContainer());
+ }
+ $columnName .= $columnParts->getSql();
+ } else {
+ $columnName .= $fromTable . $platform->quoteIdentifier($column);
+ }
+
+ // process As portion
+ if (is_string($columnIndexOrAs)) {
+ $columnAs = $platform->quoteIdentifier($columnIndexOrAs);
+ } elseif (stripos($columnName, ' as ') === false) {
+ $columnAs = (is_string($column)) ? $platform->quoteIdentifier($column) : 'Expression' . $expr++;
+ }
+ $columns[] = (isset($columnAs)) ? array($columnName, $columnAs) : array($columnName);
+ }
+
+ $separator = $platform->getIdentifierSeparator();
+
+ // process join columns
+ foreach ($this->joins as $join) {
+ foreach ($join['columns'] as $jKey => $jColumn) {
+ $jColumns = array();
+ if ($jColumn instanceof ExpressionInterface) {
+ $jColumnParts = $this->processExpression(
+ $jColumn,
+ $platform,
+ $driver,
+ $this->processInfo['paramPrefix'] . ((is_string($jKey)) ? $jKey : 'column')
+ );
+ if ($parameterContainer) {
+ $parameterContainer->merge($jColumnParts->getParameterContainer());
+ }
+ $jColumns[] = $jColumnParts->getSql();
+ } else {
+ $name = (is_array($join['name'])) ? key($join['name']) : $name = $join['name'];
+ if ($name instanceof TableIdentifier) {
+ $name = ($name->hasSchema() ? $platform->quoteIdentifier($name->getSchema()) . $separator : '') . $platform->quoteIdentifier($name->getTable());
+ } else {
+ $name = $platform->quoteIdentifier($name);
+ }
+ $jColumns[] = $name . $separator . $platform->quoteIdentifierInFragment($jColumn);
+ }
+ if (is_string($jKey)) {
+ $jColumns[] = $platform->quoteIdentifier($jKey);
+ } elseif ($jColumn !== self::SQL_STAR) {
+ $jColumns[] = $platform->quoteIdentifier($jColumn);
+ }
+ $columns[] = $jColumns;
+ }
+ }
+
+ if ($this->quantifier) {
+ if ($this->quantifier instanceof ExpressionInterface) {
+ $quantifierParts = $this->processExpression($this->quantifier, $platform, $driver, 'quantifier');
+ if ($parameterContainer) {
+ $parameterContainer->merge($quantifierParts->getParameterContainer());
+ }
+ $quantifier = $quantifierParts->getSql();
+ } else {
+ $quantifier = $this->quantifier;
+ }
+ }
+
+ if (!isset($table)) {
+ return array($columns);
+ } elseif (isset($quantifier)) {
+ return array($quantifier, $columns, $table);
+ } else {
+ return array($columns, $table);
+ }
+ }
+
+ protected function processJoins(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
+ {
+ if (!$this->joins) {
+ return null;
+ }
+
+ // process joins
+ $joinSpecArgArray = array();
+ foreach ($this->joins as $j => $join) {
+ $joinSpecArgArray[$j] = array();
+ $joinName = null;
+ $joinAs = null;
+
+ // type
+ $joinSpecArgArray[$j][] = strtoupper($join['type']);
+
+ // table name
+ if (is_array($join['name'])) {
+ $joinName = current($join['name']);
+ $joinAs = $platform->quoteIdentifier(key($join['name']));
+ } else {
+ $joinName = $join['name'];
+ }
+ if ($joinName instanceof ExpressionInterface) {
+ $joinName = $joinName->getExpression();
+ } elseif ($joinName instanceof TableIdentifier) {
+ $joinName = $joinName->getTableAndSchema();
+ $joinName = ($joinName[1] ? $platform->quoteIdentifier($joinName[1]) . $platform->getIdentifierSeparator() : '') . $platform->quoteIdentifier($joinName[0]);
+ } else {
+ if ($joinName instanceof Select) {
+ $joinName = '(' . $this->processSubSelect($joinName, $platform, $driver, $parameterContainer) . ')';
+ } else {
+ $joinName = $platform->quoteIdentifier($joinName);
+ }
+ }
+ $joinSpecArgArray[$j][] = (isset($joinAs)) ? $joinName . ' AS ' . $joinAs : $joinName;
+
+ // on expression
+ // note: for Expression objects, pass them to processExpression with a prefix specific to each join (used for named parameters)
+ $joinSpecArgArray[$j][] = ($join['on'] instanceof ExpressionInterface)
+ ? $this->processExpression($join['on'], $platform, $driver, $this->processInfo['paramPrefix'] . 'join' . ($j+1) . 'part')
+ : $platform->quoteIdentifierInFragment($join['on'], array('=', 'AND', 'OR', '(', ')', 'BETWEEN', '<', '>')); // on
+ if ($joinSpecArgArray[$j][2] instanceof StatementContainerInterface) {
+ if ($parameterContainer) {
+ $parameterContainer->merge($joinSpecArgArray[$j][2]->getParameterContainer());
+ }
+ $joinSpecArgArray[$j][2] = $joinSpecArgArray[$j][2]->getSql();
+ }
+ }
+
+ return array($joinSpecArgArray);
+ }
+
+ protected function processWhere(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
+ {
+ if ($this->where->count() == 0) {
+ return null;
+ }
+ $whereParts = $this->processExpression($this->where, $platform, $driver, $this->processInfo['paramPrefix'] . 'where');
+ if ($parameterContainer) {
+ $parameterContainer->merge($whereParts->getParameterContainer());
+ }
+ return array($whereParts->getSql());
+ }
+
+ protected function processGroup(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
+ {
+ if ($this->group === null) {
+ return null;
+ }
+ // process table columns
+ $groups = array();
+ foreach ($this->group as $column) {
+ $columnSql = '';
+ if ($column instanceof Expression) {
+ $columnParts = $this->processExpression($column, $platform, $driver, $this->processInfo['paramPrefix'] . 'group');
+ if ($parameterContainer) {
+ $parameterContainer->merge($columnParts->getParameterContainer());
+ }
+ $columnSql .= $columnParts->getSql();
+ } else {
+ $columnSql .= $platform->quoteIdentifierInFragment($column);
+ }
+ $groups[] = $columnSql;
+ }
+ return array($groups);
+ }
+
+ protected function processHaving(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
+ {
+ if ($this->having->count() == 0) {
+ return null;
+ }
+ $whereParts = $this->processExpression($this->having, $platform, $driver, $this->processInfo['paramPrefix'] . 'having');
+ if ($parameterContainer) {
+ $parameterContainer->merge($whereParts->getParameterContainer());
+ }
+ return array($whereParts->getSql());
+ }
+
+ protected function processOrder(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
+ {
+ if (empty($this->order)) {
+ return null;
+ }
+ $orders = array();
+ foreach ($this->order as $k => $v) {
+ if ($v instanceof Expression) {
+ /** @var $orderParts \Zend\Db\Adapter\StatementContainer */
+ $orderParts = $this->processExpression($v, $platform, $driver);
+ if ($parameterContainer) {
+ $parameterContainer->merge($orderParts->getParameterContainer());
+ }
+ $orders[] = array($orderParts->getSql());
+ continue;
+ }
+ if (is_int($k)) {
+ if (strpos($v, ' ') !== false) {
+ list($k, $v) = preg_split('# #', $v, 2);
+ } else {
+ $k = $v;
+ $v = self::ORDER_ASCENDING;
+ }
+ }
+ if (strtoupper($v) == self::ORDER_DESCENDING) {
+ $orders[] = array($platform->quoteIdentifierInFragment($k), self::ORDER_DESCENDING);
+ } else {
+ $orders[] = array($platform->quoteIdentifierInFragment($k), self::ORDER_ASCENDING);
+ }
+ }
+ return array($orders);
+ }
+
+ protected function processLimit(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
+ {
+ if ($this->limit === null) {
+ return null;
+ }
+
+ $limit = $this->limit;
+
+ if ($driver) {
+ $sql = $driver->formatParameterName('limit');
+ $parameterContainer->offsetSet('limit', $limit, ParameterContainer::TYPE_INTEGER);
+ } else {
+ $sql = $platform->quoteValue($limit);
+ }
+
+ return array($sql);
+ }
+
+ protected function processOffset(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
+ {
+ if ($this->offset === null) {
+ return null;
+ }
+
+ $offset = $this->offset;
+
+ if ($driver) {
+ $parameterContainer->offsetSet('offset', $offset, ParameterContainer::TYPE_INTEGER);
+ return array($driver->formatParameterName('offset'));
+ }
+
+ return array($platform->quoteValue($offset));
+ }
+
+ protected function processCombine(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
+ {
+ if ($this->combine == array()) {
+ return null;
+ }
+
+ $type = $this->combine['type'];
+ if ($this->combine['modifier']) {
+ $type .= ' ' . $this->combine['modifier'];
+ }
+ $type = strtoupper($type);
+
+ if ($driver) {
+ $sql = $this->processSubSelect($this->combine['select'], $platform, $driver, $parameterContainer);
+ return array($type, $sql);
+ }
+ return array(
+ $type,
+ $this->processSubSelect($this->combine['select'], $platform)
+ );
+ }
+
+ /**
+ * Variable overloading
+ *
+ * @param string $name
+ * @throws Exception\InvalidArgumentException
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ switch (strtolower($name)) {
+ case 'where':
+ return $this->where;
+ case 'having':
+ return $this->having;
+ default:
+ throw new Exception\InvalidArgumentException('Not a valid magic property for this object');
+ }
+ }
+
+ /**
+ * __clone
+ *
+ * Resets the where object each time the Select is cloned.
+ *
+ * @return void
+ */
+ public function __clone()
+ {
+ $this->where = clone $this->where;
+ $this->having = clone $this->having;
+ }
+}
diff --git a/library/Zend/Db/Sql/Sql.php b/library/Zend/Db/Sql/Sql.php
new file mode 100755
index 0000000000..17a697e2d8
--- /dev/null
+++ b/library/Zend/Db/Sql/Sql.php
@@ -0,0 +1,151 @@
+adapter = $adapter;
+ if ($table) {
+ $this->setTable($table);
+ }
+ $this->sqlPlatform = ($sqlPlatform) ?: new Platform\Platform($adapter);
+ }
+
+ /**
+ * @return null|\Zend\Db\Adapter\AdapterInterface
+ */
+ public function getAdapter()
+ {
+ return $this->adapter;
+ }
+
+ public function hasTable()
+ {
+ return ($this->table != null);
+ }
+
+ public function setTable($table)
+ {
+ if (is_string($table) || is_array($table) || $table instanceof TableIdentifier) {
+ $this->table = $table;
+ } else {
+ throw new Exception\InvalidArgumentException('Table must be a string, array or instance of TableIdentifier.');
+ }
+ return $this;
+ }
+
+ public function getTable()
+ {
+ return $this->table;
+ }
+
+ public function getSqlPlatform()
+ {
+ return $this->sqlPlatform;
+ }
+
+ public function select($table = null)
+ {
+ if ($this->table !== null && $table !== null) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'This Sql object is intended to work with only the table "%s" provided at construction time.',
+ $this->table
+ ));
+ }
+ return new Select(($table) ?: $this->table);
+ }
+
+ public function insert($table = null)
+ {
+ if ($this->table !== null && $table !== null) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'This Sql object is intended to work with only the table "%s" provided at construction time.',
+ $this->table
+ ));
+ }
+ return new Insert(($table) ?: $this->table);
+ }
+
+ public function update($table = null)
+ {
+ if ($this->table !== null && $table !== null) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'This Sql object is intended to work with only the table "%s" provided at construction time.',
+ $this->table
+ ));
+ }
+ return new Update(($table) ?: $this->table);
+ }
+
+ public function delete($table = null)
+ {
+ if ($this->table !== null && $table !== null) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'This Sql object is intended to work with only the table "%s" provided at construction time.',
+ $this->table
+ ));
+ }
+ return new Delete(($table) ?: $this->table);
+ }
+
+ /**
+ * @param PreparableSqlInterface $sqlObject
+ * @param StatementInterface|null $statement
+ * @return StatementInterface
+ */
+ public function prepareStatementForSqlObject(PreparableSqlInterface $sqlObject, StatementInterface $statement = null)
+ {
+ $statement = ($statement) ?: $this->adapter->getDriver()->createStatement();
+
+ if ($this->sqlPlatform) {
+ $this->sqlPlatform->setSubject($sqlObject);
+ $this->sqlPlatform->prepareStatement($this->adapter, $statement);
+ } else {
+ $sqlObject->prepareStatement($this->adapter, $statement);
+ }
+
+ return $statement;
+ }
+
+ /**
+ * Get sql string using platform or sql object
+ *
+ * @param SqlInterface $sqlObject
+ * @param PlatformInterface $platform
+ *
+ * @return string
+ */
+ public function getSqlStringForSqlObject(SqlInterface $sqlObject, PlatformInterface $platform = null)
+ {
+ $platform = ($platform) ?: $this->adapter->getPlatform();
+
+ if ($this->sqlPlatform) {
+ $this->sqlPlatform->setSubject($sqlObject);
+ return $this->sqlPlatform->getSqlString($platform);
+ }
+
+ return $sqlObject->getSqlString($platform);
+ }
+}
diff --git a/library/Zend/Db/Sql/SqlInterface.php b/library/Zend/Db/Sql/SqlInterface.php
new file mode 100755
index 0000000000..f7db64c17c
--- /dev/null
+++ b/library/Zend/Db/Sql/SqlInterface.php
@@ -0,0 +1,22 @@
+table = $table;
+ $this->schema = $schema;
+ }
+
+ /**
+ * @param string $table
+ */
+ public function setTable($table)
+ {
+ $this->table = $table;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTable()
+ {
+ return $this->table;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasSchema()
+ {
+ return ($this->schema != null);
+ }
+
+ /**
+ * @param $schema
+ */
+ public function setSchema($schema)
+ {
+ $this->schema = $schema;
+ }
+
+ /**
+ * @return null|string
+ */
+ public function getSchema()
+ {
+ return $this->schema;
+ }
+
+ public function getTableAndSchema()
+ {
+ return array($this->table, $this->schema);
+ }
+}
diff --git a/library/Zend/Db/Sql/Update.php b/library/Zend/Db/Sql/Update.php
new file mode 100755
index 0000000000..11e44e8340
--- /dev/null
+++ b/library/Zend/Db/Sql/Update.php
@@ -0,0 +1,271 @@
+ 'UPDATE %1$s SET %2$s',
+ self::SPECIFICATION_WHERE => 'WHERE %1$s'
+ );
+
+ /**
+ * @var string|TableIdentifier
+ */
+ protected $table = '';
+
+ /**
+ * @var bool
+ */
+ protected $emptyWhereProtection = true;
+
+ /**
+ * @var PriorityList
+ */
+ protected $set;
+
+ /**
+ * @var string|Where
+ */
+ protected $where = null;
+
+ /**
+ * Constructor
+ *
+ * @param null|string|TableIdentifier $table
+ */
+ public function __construct($table = null)
+ {
+ if ($table) {
+ $this->table($table);
+ }
+ $this->where = new Where();
+ $this->set = new PriorityList();
+ $this->set->isLIFO(false);
+ }
+
+ /**
+ * Specify table for statement
+ *
+ * @param string|TableIdentifier $table
+ * @return Update
+ */
+ public function table($table)
+ {
+ $this->table = $table;
+ return $this;
+ }
+
+ /**
+ * Set key/value pairs to update
+ *
+ * @param array $values Associative array of key values
+ * @param string $flag One of the VALUES_* constants
+ * @throws Exception\InvalidArgumentException
+ * @return Update
+ */
+ public function set(array $values, $flag = self::VALUES_SET)
+ {
+ if ($values == null) {
+ throw new Exception\InvalidArgumentException('set() expects an array of values');
+ }
+
+ if ($flag == self::VALUES_SET) {
+ $this->set->clear();
+ }
+ $priority = is_numeric($flag) ? $flag : 0;
+ foreach ($values as $k => $v) {
+ if (!is_string($k)) {
+ throw new Exception\InvalidArgumentException('set() expects a string for the value key');
+ }
+ $this->set->insert($k, $v, $priority);
+ }
+ return $this;
+ }
+
+ /**
+ * Create where clause
+ *
+ * @param Where|\Closure|string|array $predicate
+ * @param string $combination One of the OP_* constants from Predicate\PredicateSet
+ * @throws Exception\InvalidArgumentException
+ * @return Select
+ */
+ public function where($predicate, $combination = Predicate\PredicateSet::OP_AND)
+ {
+ if ($predicate instanceof Where) {
+ $this->where = $predicate;
+ } else {
+ $this->where->addPredicates($predicate, $combination);
+ }
+ return $this;
+ }
+
+ public function getRawState($key = null)
+ {
+ $rawState = array(
+ 'emptyWhereProtection' => $this->emptyWhereProtection,
+ 'table' => $this->table,
+ 'set' => $this->set->toArray(),
+ 'where' => $this->where
+ );
+ return (isset($key) && array_key_exists($key, $rawState)) ? $rawState[$key] : $rawState;
+ }
+
+ /**
+ * Prepare statement
+ *
+ * @param AdapterInterface $adapter
+ * @param StatementContainerInterface $statementContainer
+ * @return void
+ */
+ public function prepareStatement(AdapterInterface $adapter, StatementContainerInterface $statementContainer)
+ {
+ $driver = $adapter->getDriver();
+ $platform = $adapter->getPlatform();
+ $parameterContainer = $statementContainer->getParameterContainer();
+
+ if (!$parameterContainer instanceof ParameterContainer) {
+ $parameterContainer = new ParameterContainer();
+ $statementContainer->setParameterContainer($parameterContainer);
+ }
+
+ $table = $this->table;
+ $schema = null;
+
+ // create quoted table name to use in update processing
+ if ($table instanceof TableIdentifier) {
+ list($table, $schema) = $table->getTableAndSchema();
+ }
+
+ $table = $platform->quoteIdentifier($table);
+
+ if ($schema) {
+ $table = $platform->quoteIdentifier($schema) . $platform->getIdentifierSeparator() . $table;
+ }
+
+ $setSql = array();
+ foreach ($this->set as $column => $value) {
+ if ($value instanceof Expression) {
+ $exprData = $this->processExpression($value, $platform, $driver);
+ $setSql[] = $platform->quoteIdentifier($column) . ' = ' . $exprData->getSql();
+ $parameterContainer->merge($exprData->getParameterContainer());
+ } else {
+ $setSql[] = $platform->quoteIdentifier($column) . ' = ' . $driver->formatParameterName($column);
+ $parameterContainer->offsetSet($column, $value);
+ }
+ }
+ $set = implode(', ', $setSql);
+
+ $sql = sprintf($this->specifications[static::SPECIFICATION_UPDATE], $table, $set);
+
+ // process where
+ if ($this->where->count() > 0) {
+ $whereParts = $this->processExpression($this->where, $platform, $driver, 'where');
+ $parameterContainer->merge($whereParts->getParameterContainer());
+ $sql .= ' ' . sprintf($this->specifications[static::SPECIFICATION_WHERE], $whereParts->getSql());
+ }
+ $statementContainer->setSql($sql);
+ }
+
+ /**
+ * Get SQL string for statement
+ *
+ * @param null|PlatformInterface $adapterPlatform If null, defaults to Sql92
+ * @return string
+ */
+ public function getSqlString(PlatformInterface $adapterPlatform = null)
+ {
+ $adapterPlatform = ($adapterPlatform) ?: new Sql92;
+ $table = $this->table;
+ $schema = null;
+
+ // create quoted table name to use in update processing
+ if ($table instanceof TableIdentifier) {
+ list($table, $schema) = $table->getTableAndSchema();
+ }
+
+ $table = $adapterPlatform->quoteIdentifier($table);
+
+ if ($schema) {
+ $table = $adapterPlatform->quoteIdentifier($schema) . $adapterPlatform->getIdentifierSeparator() . $table;
+ }
+
+ $setSql = array();
+ foreach ($this->set as $column => $value) {
+ if ($value instanceof ExpressionInterface) {
+ $exprData = $this->processExpression($value, $adapterPlatform);
+ $setSql[] = $adapterPlatform->quoteIdentifier($column) . ' = ' . $exprData->getSql();
+ } elseif ($value === null) {
+ $setSql[] = $adapterPlatform->quoteIdentifier($column) . ' = NULL';
+ } else {
+ $setSql[] = $adapterPlatform->quoteIdentifier($column) . ' = ' . $adapterPlatform->quoteValue($value);
+ }
+ }
+ $set = implode(', ', $setSql);
+
+ $sql = sprintf($this->specifications[static::SPECIFICATION_UPDATE], $table, $set);
+ if ($this->where->count() > 0) {
+ $whereParts = $this->processExpression($this->where, $adapterPlatform, null, 'where');
+ $sql .= ' ' . sprintf($this->specifications[static::SPECIFICATION_WHERE], $whereParts->getSql());
+ }
+ return $sql;
+ }
+
+ /**
+ * Variable overloading
+ *
+ * Proxies to "where" only
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ switch (strtolower($name)) {
+ case 'where':
+ return $this->where;
+ }
+ }
+
+ /**
+ * __clone
+ *
+ * Resets the where object each time the Update is cloned.
+ *
+ * @return void
+ */
+ public function __clone()
+ {
+ $this->where = clone $this->where;
+ $this->set = clone $this->set;
+ }
+}
diff --git a/library/Zend/Db/Sql/Where.php b/library/Zend/Db/Sql/Where.php
new file mode 100755
index 0000000000..f50efb46f2
--- /dev/null
+++ b/library/Zend/Db/Sql/Where.php
@@ -0,0 +1,14 @@
+isInitialized;
+ }
+
+ /**
+ * Initialize
+ *
+ * @throws Exception\RuntimeException
+ * @return null
+ */
+ public function initialize()
+ {
+ if ($this->isInitialized) {
+ return;
+ }
+
+ if (!$this->featureSet instanceof Feature\FeatureSet) {
+ $this->featureSet = new Feature\FeatureSet;
+ }
+
+ $this->featureSet->setTableGateway($this);
+ $this->featureSet->apply('preInitialize', array());
+
+ if (!$this->adapter instanceof AdapterInterface) {
+ throw new Exception\RuntimeException('This table does not have an Adapter setup');
+ }
+
+ if (!is_string($this->table) && !$this->table instanceof TableIdentifier) {
+ throw new Exception\RuntimeException('This table object does not have a valid table set.');
+ }
+
+ if (!$this->resultSetPrototype instanceof ResultSetInterface) {
+ $this->resultSetPrototype = new ResultSet;
+ }
+
+ if (!$this->sql instanceof Sql) {
+ $this->sql = new Sql($this->adapter, $this->table);
+ }
+
+ $this->featureSet->apply('postInitialize', array());
+
+ $this->isInitialized = true;
+ }
+
+ /**
+ * Get table name
+ *
+ * @return string
+ */
+ public function getTable()
+ {
+ return $this->table;
+ }
+
+ /**
+ * Get adapter
+ *
+ * @return AdapterInterface
+ */
+ public function getAdapter()
+ {
+ return $this->adapter;
+ }
+
+ /**
+ * @return array
+ */
+ public function getColumns()
+ {
+ return $this->columns;
+ }
+
+ /**
+ * @return Feature\FeatureSet
+ */
+ public function getFeatureSet()
+ {
+ return $this->featureSet;
+ }
+
+ /**
+ * Get select result prototype
+ *
+ * @return ResultSet
+ */
+ public function getResultSetPrototype()
+ {
+ return $this->resultSetPrototype;
+ }
+
+ /**
+ * @return Sql
+ */
+ public function getSql()
+ {
+ return $this->sql;
+ }
+
+ /**
+ * Select
+ *
+ * @param Where|\Closure|string|array $where
+ * @return ResultSet
+ */
+ public function select($where = null)
+ {
+ if (!$this->isInitialized) {
+ $this->initialize();
+ }
+
+ $select = $this->sql->select();
+
+ if ($where instanceof \Closure) {
+ $where($select);
+ } elseif ($where !== null) {
+ $select->where($where);
+ }
+
+ return $this->selectWith($select);
+ }
+
+ /**
+ * @param Select $select
+ * @return null|ResultSetInterface
+ * @throws \RuntimeException
+ */
+ public function selectWith(Select $select)
+ {
+ if (!$this->isInitialized) {
+ $this->initialize();
+ }
+ return $this->executeSelect($select);
+ }
+
+ /**
+ * @param Select $select
+ * @return ResultSet
+ * @throws Exception\RuntimeException
+ */
+ protected function executeSelect(Select $select)
+ {
+ $selectState = $select->getRawState();
+ if ($selectState['table'] != $this->table && (is_array($selectState['table']) && end($selectState['table']) != $this->table)) {
+ throw new Exception\RuntimeException('The table name of the provided select object must match that of the table');
+ }
+
+ if ($selectState['columns'] == array(Select::SQL_STAR)
+ && $this->columns !== array()) {
+ $select->columns($this->columns);
+ }
+
+ // apply preSelect features
+ $this->featureSet->apply('preSelect', array($select));
+
+ // prepare and execute
+ $statement = $this->sql->prepareStatementForSqlObject($select);
+ $result = $statement->execute();
+
+ // build result set
+ $resultSet = clone $this->resultSetPrototype;
+ $resultSet->initialize($result);
+
+ // apply postSelect features
+ $this->featureSet->apply('postSelect', array($statement, $result, $resultSet));
+
+ return $resultSet;
+ }
+
+ /**
+ * Insert
+ *
+ * @param array $set
+ * @return int
+ */
+ public function insert($set)
+ {
+ if (!$this->isInitialized) {
+ $this->initialize();
+ }
+ $insert = $this->sql->insert();
+ $insert->values($set);
+ return $this->executeInsert($insert);
+ }
+
+ /**
+ * @param Insert $insert
+ * @return mixed
+ */
+ public function insertWith(Insert $insert)
+ {
+ if (!$this->isInitialized) {
+ $this->initialize();
+ }
+ return $this->executeInsert($insert);
+ }
+
+ /**
+ * @todo add $columns support
+ *
+ * @param Insert $insert
+ * @return mixed
+ * @throws Exception\RuntimeException
+ */
+ protected function executeInsert(Insert $insert)
+ {
+ $insertState = $insert->getRawState();
+ if ($insertState['table'] != $this->table) {
+ throw new Exception\RuntimeException('The table name of the provided Insert object must match that of the table');
+ }
+
+ // apply preInsert features
+ $this->featureSet->apply('preInsert', array($insert));
+
+ $statement = $this->sql->prepareStatementForSqlObject($insert);
+ $result = $statement->execute();
+ $this->lastInsertValue = $this->adapter->getDriver()->getConnection()->getLastGeneratedValue();
+
+ // apply postInsert features
+ $this->featureSet->apply('postInsert', array($statement, $result));
+
+ return $result->getAffectedRows();
+ }
+
+ /**
+ * Update
+ *
+ * @param array $set
+ * @param string|array|\Closure $where
+ * @return int
+ */
+ public function update($set, $where = null)
+ {
+ if (!$this->isInitialized) {
+ $this->initialize();
+ }
+ $sql = $this->sql;
+ $update = $sql->update();
+ $update->set($set);
+ if ($where !== null) {
+ $update->where($where);
+ }
+ return $this->executeUpdate($update);
+ }
+
+ /**
+ * @param \Zend\Db\Sql\Update $update
+ * @return mixed
+ */
+ public function updateWith(Update $update)
+ {
+ if (!$this->isInitialized) {
+ $this->initialize();
+ }
+ return $this->executeUpdate($update);
+ }
+
+ /**
+ * @todo add $columns support
+ *
+ * @param Update $update
+ * @return mixed
+ * @throws Exception\RuntimeException
+ */
+ protected function executeUpdate(Update $update)
+ {
+ $updateState = $update->getRawState();
+ if ($updateState['table'] != $this->table) {
+ throw new Exception\RuntimeException('The table name of the provided Update object must match that of the table');
+ }
+
+ // apply preUpdate features
+ $this->featureSet->apply('preUpdate', array($update));
+
+ $statement = $this->sql->prepareStatementForSqlObject($update);
+ $result = $statement->execute();
+
+ // apply postUpdate features
+ $this->featureSet->apply('postUpdate', array($statement, $result));
+
+ return $result->getAffectedRows();
+ }
+
+ /**
+ * Delete
+ *
+ * @param Where|\Closure|string|array $where
+ * @return int
+ */
+ public function delete($where)
+ {
+ if (!$this->isInitialized) {
+ $this->initialize();
+ }
+ $delete = $this->sql->delete();
+ if ($where instanceof \Closure) {
+ $where($delete);
+ } else {
+ $delete->where($where);
+ }
+ return $this->executeDelete($delete);
+ }
+
+ /**
+ * @param Delete $delete
+ * @return mixed
+ */
+ public function deleteWith(Delete $delete)
+ {
+ $this->initialize();
+ return $this->executeDelete($delete);
+ }
+
+ /**
+ * @todo add $columns support
+ *
+ * @param Delete $delete
+ * @return mixed
+ * @throws Exception\RuntimeException
+ */
+ protected function executeDelete(Delete $delete)
+ {
+ $deleteState = $delete->getRawState();
+ if ($deleteState['table'] != $this->table) {
+ throw new Exception\RuntimeException('The table name of the provided Update object must match that of the table');
+ }
+
+ // pre delete update
+ $this->featureSet->apply('preDelete', array($delete));
+
+ $statement = $this->sql->prepareStatementForSqlObject($delete);
+ $result = $statement->execute();
+
+ // apply postDelete features
+ $this->featureSet->apply('postDelete', array($statement, $result));
+
+ return $result->getAffectedRows();
+ }
+
+ /**
+ * Get last insert value
+ *
+ * @return int
+ */
+ public function getLastInsertValue()
+ {
+ return $this->lastInsertValue;
+ }
+
+ /**
+ * __get
+ *
+ * @param string $property
+ * @throws Exception\InvalidArgumentException
+ * @return mixed
+ */
+ public function __get($property)
+ {
+ switch (strtolower($property)) {
+ case 'lastinsertvalue':
+ return $this->lastInsertValue;
+ case 'adapter':
+ return $this->adapter;
+ case 'table':
+ return $this->table;
+ }
+ if ($this->featureSet->canCallMagicGet($property)) {
+ return $this->featureSet->callMagicGet($property);
+ }
+ throw new Exception\InvalidArgumentException('Invalid magic property access in ' . __CLASS__ . '::__get()');
+ }
+
+ /**
+ * @param string $property
+ * @param mixed $value
+ * @return mixed
+ * @throws Exception\InvalidArgumentException
+ */
+ public function __set($property, $value)
+ {
+ if ($this->featureSet->canCallMagicSet($property)) {
+ return $this->featureSet->callMagicSet($property, $value);
+ }
+ throw new Exception\InvalidArgumentException('Invalid magic property access in ' . __CLASS__ . '::__set()');
+ }
+
+ /**
+ * @param $method
+ * @param $arguments
+ * @return mixed
+ * @throws Exception\InvalidArgumentException
+ */
+ public function __call($method, $arguments)
+ {
+ if ($this->featureSet->canCallMagicCall($method)) {
+ return $this->featureSet->callMagicCall($method, $arguments);
+ }
+ throw new Exception\InvalidArgumentException('Invalid method (' . $method . ') called, caught by ' . __CLASS__ . '::__call()');
+ }
+
+ /**
+ * __clone
+ */
+ public function __clone()
+ {
+ $this->resultSetPrototype = (isset($this->resultSetPrototype)) ? clone $this->resultSetPrototype : null;
+ $this->sql = clone $this->sql;
+ if (is_object($this->table)) {
+ $this->table = clone $this->table;
+ }
+ }
+}
diff --git a/library/Zend/Db/TableGateway/Exception/ExceptionInterface.php b/library/Zend/Db/TableGateway/Exception/ExceptionInterface.php
new file mode 100755
index 0000000000..ecd3085ac6
--- /dev/null
+++ b/library/Zend/Db/TableGateway/Exception/ExceptionInterface.php
@@ -0,0 +1,16 @@
+tableGateway = $tableGateway;
+ }
+
+ public function initialize()
+ {
+ throw new Exception\RuntimeException('This method is not intended to be called on this object.');
+ }
+
+ public function getMagicMethodSpecifications()
+ {
+ return array();
+ }
+
+
+ /*
+ public function preInitialize();
+ public function postInitialize();
+ public function preSelect(Select $select);
+ public function postSelect(StatementInterface $statement, ResultInterface $result, ResultSetInterface $resultSet);
+ public function preInsert(Insert $insert);
+ public function postInsert(StatementInterface $statement, ResultInterface $result);
+ public function preUpdate(Update $update);
+ public function postUpdate(StatementInterface $statement, ResultInterface $result);
+ public function preDelete(Delete $delete);
+ public function postDelete(StatementInterface $statement, ResultInterface $result);
+ */
+}
diff --git a/library/Zend/Db/TableGateway/Feature/EventFeature.php b/library/Zend/Db/TableGateway/Feature/EventFeature.php
new file mode 100755
index 0000000000..08e3cffbdc
--- /dev/null
+++ b/library/Zend/Db/TableGateway/Feature/EventFeature.php
@@ -0,0 +1,255 @@
+eventManager = ($eventManager instanceof EventManagerInterface)
+ ? $eventManager
+ : new EventManager;
+
+ $this->eventManager->addIdentifiers(array(
+ 'Zend\Db\TableGateway\TableGateway',
+ ));
+
+ $this->event = ($tableGatewayEvent) ?: new EventFeature\TableGatewayEvent();
+ }
+
+ /**
+ * Retrieve composed event manager instance
+ *
+ * @return EventManagerInterface
+ */
+ public function getEventManager()
+ {
+ return $this->eventManager;
+ }
+
+ /**
+ * Retrieve composed event instance
+ *
+ * @return EventFeature\TableGatewayEvent
+ */
+ public function getEvent()
+ {
+ return $this->event;
+ }
+
+ /**
+ * Initialize feature and trigger "preInitialize" event
+ *
+ * Ensures that the composed TableGateway has identifiers based on the
+ * class name, and that the event target is set to the TableGateway
+ * instance. It then triggers the "preInitialize" event.
+ *
+ * @return void
+ */
+ public function preInitialize()
+ {
+ if (get_class($this->tableGateway) != 'Zend\Db\TableGateway\TableGateway') {
+ $this->eventManager->addIdentifiers(get_class($this->tableGateway));
+ }
+
+ $this->event->setTarget($this->tableGateway);
+ $this->event->setName(__FUNCTION__);
+ $this->eventManager->trigger($this->event);
+ }
+
+ /**
+ * Trigger the "postInitialize" event
+ *
+ * @return void
+ */
+ public function postInitialize()
+ {
+ $this->event->setName(__FUNCTION__);
+ $this->eventManager->trigger($this->event);
+ }
+
+ /**
+ * Trigger the "preSelect" event
+ *
+ * Triggers the "preSelect" event mapping the following parameters:
+ * - $select as "select"
+ *
+ * @param Select $select
+ * @return void
+ */
+ public function preSelect(Select $select)
+ {
+ $this->event->setName(__FUNCTION__);
+ $this->event->setParams(array('select' => $select));
+ $this->eventManager->trigger($this->event);
+ }
+
+ /**
+ * Trigger the "postSelect" event
+ *
+ * Triggers the "postSelect" event mapping the following parameters:
+ * - $statement as "statement"
+ * - $result as "result"
+ * - $resultSet as "result_set"
+ *
+ * @param StatementInterface $statement
+ * @param ResultInterface $result
+ * @param ResultSetInterface $resultSet
+ * @return void
+ */
+ public function postSelect(StatementInterface $statement, ResultInterface $result, ResultSetInterface $resultSet)
+ {
+ $this->event->setName(__FUNCTION__);
+ $this->event->setParams(array(
+ 'statement' => $statement,
+ 'result' => $result,
+ 'result_set' => $resultSet
+ ));
+ $this->eventManager->trigger($this->event);
+ }
+
+ /**
+ * Trigger the "preInsert" event
+ *
+ * Triggers the "preInsert" event mapping the following parameters:
+ * - $insert as "insert"
+ *
+ * @param Insert $insert
+ * @return void
+ */
+ public function preInsert(Insert $insert)
+ {
+ $this->event->setName(__FUNCTION__);
+ $this->event->setParams(array('insert' => $insert));
+ $this->eventManager->trigger($this->event);
+ }
+
+ /**
+ * Trigger the "postInsert" event
+ *
+ * Triggers the "postInsert" event mapping the following parameters:
+ * - $statement as "statement"
+ * - $result as "result"
+ *
+ * @param StatementInterface $statement
+ * @param ResultInterface $result
+ * @return void
+ */
+ public function postInsert(StatementInterface $statement, ResultInterface $result)
+ {
+ $this->event->setName(__FUNCTION__);
+ $this->event->setParams(array(
+ 'statement' => $statement,
+ 'result' => $result,
+ ));
+ $this->eventManager->trigger($this->event);
+ }
+
+ /**
+ * Trigger the "preUpdate" event
+ *
+ * Triggers the "preUpdate" event mapping the following parameters:
+ * - $update as "update"
+ *
+ * @param Update $update
+ * @return void
+ */
+ public function preUpdate(Update $update)
+ {
+ $this->event->setName(__FUNCTION__);
+ $this->event->setParams(array('update' => $update));
+ $this->eventManager->trigger($this->event);
+ }
+
+ /**
+ * Trigger the "postUpdate" event
+ *
+ * Triggers the "postUpdate" event mapping the following parameters:
+ * - $statement as "statement"
+ * - $result as "result"
+ *
+ * @param StatementInterface $statement
+ * @param ResultInterface $result
+ * @return void
+ */
+ public function postUpdate(StatementInterface $statement, ResultInterface $result)
+ {
+ $this->event->setName(__FUNCTION__);
+ $this->event->setParams(array(
+ 'statement' => $statement,
+ 'result' => $result,
+ ));
+ $this->eventManager->trigger($this->event);
+ }
+
+ /**
+ * Trigger the "preDelete" event
+ *
+ * Triggers the "preDelete" event mapping the following parameters:
+ * - $delete as "delete"
+ *
+ * @param Delete $delete
+ * @return void
+ */
+ public function preDelete(Delete $delete)
+ {
+ $this->event->setName(__FUNCTION__);
+ $this->event->setParams(array('delete' => $delete));
+ $this->eventManager->trigger($this->event);
+ }
+
+ /**
+ * Trigger the "postDelete" event
+ *
+ * Triggers the "postDelete" event mapping the following parameters:
+ * - $statement as "statement"
+ * - $result as "result"
+ *
+ * @param StatementInterface $statement
+ * @param ResultInterface $result
+ * @return void
+ */
+ public function postDelete(StatementInterface $statement, ResultInterface $result)
+ {
+ $this->event->setName(__FUNCTION__);
+ $this->event->setParams(array(
+ 'statement' => $statement,
+ 'result' => $result,
+ ));
+ $this->eventManager->trigger($this->event);
+ }
+}
diff --git a/library/Zend/Db/TableGateway/Feature/EventFeature/TableGatewayEvent.php b/library/Zend/Db/TableGateway/Feature/EventFeature/TableGatewayEvent.php
new file mode 100755
index 0000000000..1097a86fe5
--- /dev/null
+++ b/library/Zend/Db/TableGateway/Feature/EventFeature/TableGatewayEvent.php
@@ -0,0 +1,139 @@
+name;
+ }
+
+ /**
+ * Get target/context from which event was triggered
+ *
+ * @return null|string|object
+ */
+ public function getTarget()
+ {
+ return $this->target;
+ }
+
+ /**
+ * Get parameters passed to the event
+ *
+ * @return array|\ArrayAccess
+ */
+ public function getParams()
+ {
+ return $this->params;
+ }
+
+ /**
+ * Get a single parameter by name
+ *
+ * @param string $name
+ * @param mixed $default Default value to return if parameter does not exist
+ * @return mixed
+ */
+ public function getParam($name, $default = null)
+ {
+ return (isset($this->params[$name]) ? $this->params[$name] : $default);
+ }
+
+ /**
+ * Set the event name
+ *
+ * @param string $name
+ * @return void
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * Set the event target/context
+ *
+ * @param null|string|object $target
+ * @return void
+ */
+ public function setTarget($target)
+ {
+ $this->target = $target;
+ }
+
+ /**
+ * Set event parameters
+ *
+ * @param string $params
+ * @return void
+ */
+ public function setParams($params)
+ {
+ $this->params = $params;
+ }
+
+ /**
+ * Set a single parameter by key
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return void
+ */
+ public function setParam($name, $value)
+ {
+ $this->params[$name] = $value;
+ }
+
+ /**
+ * Indicate whether or not the parent EventManagerInterface should stop propagating events
+ *
+ * @param bool $flag
+ * @return void
+ */
+ public function stopPropagation($flag = true)
+ {
+ return;
+ }
+
+ /**
+ * Has this event indicated event propagation should stop?
+ *
+ * @return bool
+ */
+ public function propagationIsStopped()
+ {
+ return false;
+ }
+}
diff --git a/library/Zend/Db/TableGateway/Feature/FeatureSet.php b/library/Zend/Db/TableGateway/Feature/FeatureSet.php
new file mode 100755
index 0000000000..498db1ad7f
--- /dev/null
+++ b/library/Zend/Db/TableGateway/Feature/FeatureSet.php
@@ -0,0 +1,146 @@
+addFeatures($features);
+ }
+ }
+
+ public function setTableGateway(AbstractTableGateway $tableGateway)
+ {
+ $this->tableGateway = $tableGateway;
+ foreach ($this->features as $feature) {
+ $feature->setTableGateway($this->tableGateway);
+ }
+ return $this;
+ }
+
+ public function getFeatureByClassName($featureClassName)
+ {
+ $feature = false;
+ foreach ($this->features as $potentialFeature) {
+ if ($potentialFeature instanceof $featureClassName) {
+ $feature = $potentialFeature;
+ break;
+ }
+ }
+ return $feature;
+ }
+
+ public function addFeatures(array $features)
+ {
+ foreach ($features as $feature) {
+ $this->addFeature($feature);
+ }
+ return $this;
+ }
+
+ public function addFeature(AbstractFeature $feature)
+ {
+ if ($this->tableGateway instanceof TableGatewayInterface) {
+ $feature->setTableGateway($this->tableGateway);
+ }
+ $this->features[] = $feature;
+ return $this;
+ }
+
+ public function apply($method, $args)
+ {
+ foreach ($this->features as $feature) {
+ if (method_exists($feature, $method)) {
+ $return = call_user_func_array(array($feature, $method), $args);
+ if ($return === self::APPLY_HALT) {
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * @param string $property
+ * @return bool
+ */
+ public function canCallMagicGet($property)
+ {
+ return false;
+ }
+
+ /**
+ * @param string $property
+ * @return mixed
+ */
+ public function callMagicGet($property)
+ {
+ $return = null;
+ return $return;
+ }
+
+ /**
+ * @param string $property
+ * @return bool
+ */
+ public function canCallMagicSet($property)
+ {
+ return false;
+ }
+
+ /**
+ * @param $property
+ * @param $value
+ * @return mixed
+ */
+ public function callMagicSet($property, $value)
+ {
+ $return = null;
+ return $return;
+ }
+
+ /**
+ * @param string $method
+ * @return bool
+ */
+ public function canCallMagicCall($method)
+ {
+ return false;
+ }
+
+ /**
+ * @param string $method
+ * @param array $arguments
+ * @return mixed
+ */
+ public function callMagicCall($method, $arguments)
+ {
+ $return = null;
+ return $return;
+ }
+}
diff --git a/library/Zend/Db/TableGateway/Feature/GlobalAdapterFeature.php b/library/Zend/Db/TableGateway/Feature/GlobalAdapterFeature.php
new file mode 100755
index 0000000000..bb8c6d60bc
--- /dev/null
+++ b/library/Zend/Db/TableGateway/Feature/GlobalAdapterFeature.php
@@ -0,0 +1,67 @@
+tableGateway->adapter = self::getStaticAdapter();
+ }
+}
diff --git a/library/Zend/Db/TableGateway/Feature/MasterSlaveFeature.php b/library/Zend/Db/TableGateway/Feature/MasterSlaveFeature.php
new file mode 100755
index 0000000000..255735de88
--- /dev/null
+++ b/library/Zend/Db/TableGateway/Feature/MasterSlaveFeature.php
@@ -0,0 +1,91 @@
+slaveAdapter = $slaveAdapter;
+ if ($slaveSql) {
+ $this->slaveSql = $slaveSql;
+ }
+ }
+
+ public function getSlaveAdapter()
+ {
+ return $this->slaveAdapter;
+ }
+
+ /**
+ * @return Sql
+ */
+ public function getSlaveSql()
+ {
+ return $this->slaveSql;
+ }
+
+ /**
+ * after initialization, retrieve the original adapter as "master"
+ */
+ public function postInitialize()
+ {
+ $this->masterSql = $this->tableGateway->sql;
+ if ($this->slaveSql == null) {
+ $this->slaveSql = new Sql(
+ $this->slaveAdapter,
+ $this->tableGateway->sql->getTable(),
+ $this->tableGateway->sql->getSqlPlatform()
+ );
+ }
+ }
+
+ /**
+ * preSelect()
+ * Replace adapter with slave temporarily
+ */
+ public function preSelect()
+ {
+ $this->tableGateway->sql = $this->slaveSql;
+ }
+
+ /**
+ * postSelect()
+ * Ensure to return to the master adapter
+ */
+ public function postSelect()
+ {
+ $this->tableGateway->sql = $this->masterSql;
+ }
+}
diff --git a/library/Zend/Db/TableGateway/Feature/MetadataFeature.php b/library/Zend/Db/TableGateway/Feature/MetadataFeature.php
new file mode 100755
index 0000000000..59393bec2a
--- /dev/null
+++ b/library/Zend/Db/TableGateway/Feature/MetadataFeature.php
@@ -0,0 +1,85 @@
+metadata = $metadata;
+ }
+ $this->sharedData['metadata'] = array(
+ 'primaryKey' => null,
+ 'columns' => array()
+ );
+ }
+
+ public function postInitialize()
+ {
+ if ($this->metadata == null) {
+ $this->metadata = new Metadata($this->tableGateway->adapter);
+ }
+
+ // localize variable for brevity
+ $t = $this->tableGateway;
+ $m = $this->metadata;
+
+ // get column named
+ $columns = $m->getColumnNames($t->table);
+ $t->columns = $columns;
+
+ // set locally
+ $this->sharedData['metadata']['columns'] = $columns;
+
+ // process primary key only if table is a table; there are no PK constraints on views
+ if (!($m->getTable($t->table) instanceof TableObject)) {
+ return;
+ }
+
+ $pkc = null;
+
+ foreach ($m->getConstraints($t->table) as $constraint) {
+ /** @var $constraint \Zend\Db\Metadata\Object\ConstraintObject */
+ if ($constraint->getType() == 'PRIMARY KEY') {
+ $pkc = $constraint;
+ break;
+ }
+ }
+
+ if ($pkc === null) {
+ throw new Exception\RuntimeException('A primary key for this column could not be found in the metadata.');
+ }
+
+ if (count($pkc->getColumns()) == 1) {
+ $pkck = $pkc->getColumns();
+ $primaryKey = $pkck[0];
+ } else {
+ $primaryKey = $pkc->getColumns();
+ }
+
+ $this->sharedData['metadata']['primaryKey'] = $primaryKey;
+ }
+}
diff --git a/library/Zend/Db/TableGateway/Feature/RowGatewayFeature.php b/library/Zend/Db/TableGateway/Feature/RowGatewayFeature.php
new file mode 100755
index 0000000000..18c4fd9df4
--- /dev/null
+++ b/library/Zend/Db/TableGateway/Feature/RowGatewayFeature.php
@@ -0,0 +1,67 @@
+constructorArguments = func_get_args();
+ }
+
+ public function postInitialize()
+ {
+ $args = $this->constructorArguments;
+
+ /** @var $resultSetPrototype ResultSet */
+ $resultSetPrototype = $this->tableGateway->resultSetPrototype;
+
+ if (!$this->tableGateway->resultSetPrototype instanceof ResultSet) {
+ throw new Exception\RuntimeException(
+ 'This feature ' . __CLASS__ . ' expects the ResultSet to be an instance of Zend\Db\ResultSet\ResultSet'
+ );
+ }
+
+ if (isset($args[0])) {
+ if (is_string($args[0])) {
+ $primaryKey = $args[0];
+ $rowGatewayPrototype = new RowGateway($primaryKey, $this->tableGateway->table, $this->tableGateway->adapter, $this->tableGateway->sql);
+ $resultSetPrototype->setArrayObjectPrototype($rowGatewayPrototype);
+ } elseif ($args[0] instanceof RowGatewayInterface) {
+ $rowGatewayPrototype = $args[0];
+ $resultSetPrototype->setArrayObjectPrototype($rowGatewayPrototype);
+ }
+ } else {
+ // get from metadata feature
+ $metadata = $this->tableGateway->featureSet->getFeatureByClassName('Zend\Db\TableGateway\Feature\MetadataFeature');
+ if ($metadata === false || !isset($metadata->sharedData['metadata'])) {
+ throw new Exception\RuntimeException(
+ 'No information was provided to the RowGatewayFeature and/or no MetadataFeature could be consulted to find the primary key necessary for RowGateway object creation.'
+ );
+ }
+ $primaryKey = $metadata->sharedData['metadata']['primaryKey'];
+ $rowGatewayPrototype = new RowGateway($primaryKey, $this->tableGateway->table, $this->tableGateway->adapter, $this->tableGateway->sql);
+ $resultSetPrototype->setArrayObjectPrototype($rowGatewayPrototype);
+ }
+ }
+}
diff --git a/library/Zend/Db/TableGateway/Feature/SequenceFeature.php b/library/Zend/Db/TableGateway/Feature/SequenceFeature.php
new file mode 100755
index 0000000000..9f58d1a569
--- /dev/null
+++ b/library/Zend/Db/TableGateway/Feature/SequenceFeature.php
@@ -0,0 +1,133 @@
+primaryKeyField = $primaryKeyField;
+ $this->sequenceName = $sequenceName;
+ }
+
+ /**
+ * @param Insert $insert
+ * @return Insert
+ */
+ public function preInsert(Insert $insert)
+ {
+ $columns = $insert->getRawState('columns');
+ $values = $insert->getRawState('values');
+ $key = array_search($this->primaryKeyField, $columns);
+ if ($key !== false) {
+ $this->sequenceValue = $values[$key];
+ return $insert;
+ }
+
+ $this->sequenceValue = $this->nextSequenceId();
+ if ($this->sequenceValue === null) {
+ return $insert;
+ }
+
+ $insert->values(array($this->primaryKeyField => $this->sequenceValue), Insert::VALUES_MERGE);
+ return $insert;
+ }
+
+ /**
+ * @param StatementInterface $statement
+ * @param ResultInterface $result
+ */
+ public function postInsert(StatementInterface $statement, ResultInterface $result)
+ {
+ if ($this->sequenceValue !== null) {
+ $this->tableGateway->lastInsertValue = $this->sequenceValue;
+ }
+ }
+
+ /**
+ * Generate a new value from the specified sequence in the database, and return it.
+ * @return int
+ */
+ public function nextSequenceId()
+ {
+ $platform = $this->tableGateway->adapter->getPlatform();
+ $platformName = $platform->getName();
+
+ switch ($platformName) {
+ case 'Oracle':
+ $sql = 'SELECT ' . $platform->quoteIdentifier($this->sequenceName) . '.NEXTVAL as "nextval" FROM dual';
+ break;
+ case 'PostgreSQL':
+ $sql = 'SELECT NEXTVAL(\'"' . $this->sequenceName . '"\')';
+ break;
+ default :
+ return null;
+ }
+
+ $statement = $this->tableGateway->adapter->createStatement();
+ $statement->prepare($sql);
+ $result = $statement->execute();
+ $sequence = $result->current();
+ unset($statement, $result);
+ return $sequence['nextval'];
+ }
+
+ /**
+ * Return the most recent value from the specified sequence in the database.
+ * @return int
+ */
+ public function lastSequenceId()
+ {
+ $platform = $this->tableGateway->adapter->getPlatform();
+ $platformName = $platform->getName();
+
+ switch ($platformName) {
+ case 'Oracle':
+ $sql = 'SELECT ' . $platform->quoteIdentifier($this->sequenceName) . '.CURRVAL as "currval" FROM dual';
+ break;
+ case 'PostgreSQL':
+ $sql = 'SELECT CURRVAL(\'' . $this->sequenceName . '\')';
+ break;
+ default :
+ return null;
+ }
+
+ $statement = $this->tableGateway->adapter->createStatement();
+ $statement->prepare($sql);
+ $result = $statement->execute();
+ $sequence = $result->current();
+ unset($statement, $result);
+ return $sequence['currval'];
+ }
+}
diff --git a/library/Zend/Db/TableGateway/TableGateway.php b/library/Zend/Db/TableGateway/TableGateway.php
new file mode 100755
index 0000000000..0defd8a4d7
--- /dev/null
+++ b/library/Zend/Db/TableGateway/TableGateway.php
@@ -0,0 +1,72 @@
+table = $table;
+
+ // adapter
+ $this->adapter = $adapter;
+
+ // process features
+ if ($features !== null) {
+ if ($features instanceof Feature\AbstractFeature) {
+ $features = array($features);
+ }
+ if (is_array($features)) {
+ $this->featureSet = new Feature\FeatureSet($features);
+ } elseif ($features instanceof Feature\FeatureSet) {
+ $this->featureSet = $features;
+ } else {
+ throw new Exception\InvalidArgumentException(
+ 'TableGateway expects $feature to be an instance of an AbstractFeature or a FeatureSet, or an array of AbstractFeatures'
+ );
+ }
+ } else {
+ $this->featureSet = new Feature\FeatureSet();
+ }
+
+ // result prototype
+ $this->resultSetPrototype = ($resultSetPrototype) ?: new ResultSet;
+
+ // Sql object (factory for select, insert, update, delete)
+ $this->sql = ($sql) ?: new Sql($this->adapter, $this->table);
+
+ // check sql object bound to same table
+ if ($this->sql->getTable() != $this->table) {
+ throw new Exception\InvalidArgumentException('The table inside the provided Sql object must match the table of this TableGateway');
+ }
+
+ $this->initialize();
+ }
+}
diff --git a/library/Zend/Db/TableGateway/TableGatewayInterface.php b/library/Zend/Db/TableGateway/TableGatewayInterface.php
new file mode 100755
index 0000000000..0a77e0f1d7
--- /dev/null
+++ b/library/Zend/Db/TableGateway/TableGatewayInterface.php
@@ -0,0 +1,19 @@
+=5.3.23"
+ },
+ "require-dev": {
+ "zendframework/zend-eventmanager": "self.version",
+ "zendframework/zend-servicemanager": "self.version",
+ "zendframework/zend-stdlib": "self.version"
+ },
+ "suggest": {
+ "zendframework/zend-eventmanager": "Zend\\EventManager component",
+ "zendframework/zend-servicemanager": "Zend\\ServiceManager component",
+ "zendframework/zend-stdlib": "Zend\\Stdlib component"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev",
+ "dev-develop": "2.4-dev"
+ }
+ }
+}
diff --git a/library/Zend/Debug/CONTRIBUTING.md b/library/Zend/Debug/CONTRIBUTING.md
new file mode 100755
index 0000000000..e77f5d2d5b
--- /dev/null
+++ b/library/Zend/Debug/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# CONTRIBUTING
+
+Please don't open pull requests against this repository, please use https://github.com/zendframework/zf2.
\ No newline at end of file
diff --git a/library/Zend/Debug/Debug.php b/library/Zend/Debug/Debug.php
new file mode 100755
index 0000000000..f240935c85
--- /dev/null
+++ b/library/Zend/Debug/Debug.php
@@ -0,0 +1,124 @@
+ tags, cleans up newlines and indents, and runs
+ * htmlentities() before output.
+ *
+ * @param mixed $var The variable to dump.
+ * @param string $label OPTIONAL Label to prepend to output.
+ * @param bool $echo OPTIONAL Echo output if true.
+ * @return string
+ */
+ public static function dump($var, $label = null, $echo = true)
+ {
+ // format the label
+ $label = ($label===null) ? '' : rtrim($label) . ' ';
+
+ // var_dump the variable into a buffer and keep the output
+ ob_start();
+ var_dump($var);
+ $output = ob_get_clean();
+
+ // neaten the newlines and indents
+ $output = preg_replace("/\]\=\>\n(\s+)/m", "] => ", $output);
+ if (static::getSapi() == 'cli') {
+ $output = PHP_EOL . $label
+ . PHP_EOL . $output
+ . PHP_EOL;
+ } else {
+ if (null !== static::$escaper) {
+ $output = static::$escaper->escapeHtml($output);
+ } elseif (!extension_loaded('xdebug')) {
+ $output = static::getEscaper()->escapeHtml($output);
+ }
+
+ $output = ''
+ . $label
+ . $output
+ . '
';
+ }
+
+ if ($echo) {
+ echo $output;
+ }
+ return $output;
+ }
+}
diff --git a/library/Zend/Debug/README.md b/library/Zend/Debug/README.md
new file mode 100755
index 0000000000..8797b8e216
--- /dev/null
+++ b/library/Zend/Debug/README.md
@@ -0,0 +1,15 @@
+Debug Component from ZF2
+========================
+
+This is the Debug component for ZF2.
+
+- File issues at https://github.com/zendframework/zf2/issues
+- Create pull requests against https://github.com/zendframework/zf2
+- Documentation is at http://framework.zend.com/docs
+
+LICENSE
+-------
+
+The files in this archive are released under the [Zend Framework
+license](http://framework.zend.com/license), which is a 3-clause BSD license.
+
diff --git a/library/Zend/Debug/composer.json b/library/Zend/Debug/composer.json
new file mode 100755
index 0000000000..b6c18c7bf6
--- /dev/null
+++ b/library/Zend/Debug/composer.json
@@ -0,0 +1,32 @@
+{
+ "name": "zendframework/zend-debug",
+ "description": " ",
+ "license": "BSD-3-Clause",
+ "keywords": [
+ "zf2",
+ "debug"
+ ],
+ "homepage": "https://github.com/zendframework/zf2",
+ "autoload": {
+ "psr-0": {
+ "Zend\\Debug\\": ""
+ }
+ },
+ "target-dir": "Zend/Debug",
+ "require": {
+ "php": ">=5.3.23"
+ },
+ "require-dev": {
+ "zendframework/zend-escaper": "*"
+ },
+ "suggest": {
+ "ext/xdebug": "XDebug, for better backtrace output",
+ "zendframework/zend-escaper": "To support escaped output"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev",
+ "dev-develop": "2.4-dev"
+ }
+ }
+}
diff --git a/library/Zend/Di/CONTRIBUTING.md b/library/Zend/Di/CONTRIBUTING.md
new file mode 100755
index 0000000000..e77f5d2d5b
--- /dev/null
+++ b/library/Zend/Di/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# CONTRIBUTING
+
+Please don't open pull requests against this repository, please use https://github.com/zendframework/zf2.
\ No newline at end of file
diff --git a/library/Zend/Di/Config.php b/library/Zend/Di/Config.php
new file mode 100755
index 0000000000..b177305045
--- /dev/null
+++ b/library/Zend/Di/Config.php
@@ -0,0 +1,196 @@
+data = $options;
+ }
+
+ /**
+ * Configure
+ *
+ * @param Di $di
+ * @return void
+ */
+ public function configure(Di $di)
+ {
+ if (isset($this->data['definition'])) {
+ $this->configureDefinition($di, $this->data['definition']);
+ }
+ if (isset($this->data['instance'])) {
+ $this->configureInstance($di, $this->data['instance']);
+ }
+ }
+
+ /**
+ * @param Di $di
+ * @param array $definition
+ */
+ public function configureDefinition(Di $di, $definition)
+ {
+ foreach ($definition as $definitionType => $definitionData) {
+ switch ($definitionType) {
+ case 'compiler':
+ foreach ($definitionData as $filename) {
+ if (is_readable($filename)) {
+ $di->definitions()->addDefinition(new ArrayDefinition(include $filename), false);
+ }
+ }
+ break;
+ case 'runtime':
+ if (isset($definitionData['enabled']) && !$definitionData['enabled']) {
+ // Remove runtime from definition list if not enabled
+ $definitions = array();
+ foreach ($di->definitions() as $definition) {
+ if (!$definition instanceof RuntimeDefinition) {
+ $definitions[] = $definition;
+ }
+ }
+ $definitionList = new DefinitionList($definitions);
+ $di->setDefinitionList($definitionList);
+ } elseif (isset($definitionData['use_annotations']) && $definitionData['use_annotations']) {
+ /* @var $runtimeDefinition Definition\RuntimeDefinition */
+ $runtimeDefinition = $di
+ ->definitions()
+ ->getDefinitionByType('\Zend\Di\Definition\RuntimeDefinition');
+ $runtimeDefinition->getIntrospectionStrategy()->setUseAnnotations(true);
+ }
+ break;
+ case 'class':
+ foreach ($definitionData as $className => $classData) {
+ $classDefinitions = $di->definitions()->getDefinitionsByType('Zend\Di\Definition\ClassDefinition');
+ foreach ($classDefinitions as $classDefinition) {
+ if (!$classDefinition->hasClass($className)) {
+ unset($classDefinition);
+ }
+ }
+ if (!isset($classDefinition)) {
+ $classDefinition = new Definition\ClassDefinition($className);
+ $di->definitions()->addDefinition($classDefinition, false);
+ }
+ foreach ($classData as $classDefKey => $classDefData) {
+ switch ($classDefKey) {
+ case 'instantiator':
+ $classDefinition->setInstantiator($classDefData);
+ break;
+ case 'supertypes':
+ $classDefinition->setSupertypes($classDefData);
+ break;
+ case 'methods':
+ case 'method':
+ foreach ($classDefData as $methodName => $methodInfo) {
+ if (isset($methodInfo['required'])) {
+ $classDefinition->addMethod($methodName, $methodInfo['required']);
+ unset($methodInfo['required']);
+ }
+ foreach ($methodInfo as $paramName => $paramInfo) {
+ $classDefinition->addMethodParameter($methodName, $paramName, $paramInfo);
+ }
+ }
+ break;
+ default:
+ $methodName = $classDefKey;
+ $methodInfo = $classDefData;
+ if (isset($classDefData['required'])) {
+ $classDefinition->addMethod($methodName, $methodInfo['required']);
+ unset($methodInfo['required']);
+ }
+ foreach ($methodInfo as $paramName => $paramInfo) {
+ $classDefinition->addMethodParameter($methodName, $paramName, $paramInfo);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Configures a given Di instance
+ *
+ * @param Di $di
+ * @param $instanceData
+ */
+ public function configureInstance(Di $di, $instanceData)
+ {
+ $im = $di->instanceManager();
+
+ foreach ($instanceData as $target => $data) {
+ switch (strtolower($target)) {
+ case 'aliases':
+ case 'alias':
+ foreach ($data as $n => $v) {
+ $im->addAlias($n, $v);
+ }
+ break;
+ case 'preferences':
+ case 'preference':
+ foreach ($data as $n => $v) {
+ if (is_array($v)) {
+ foreach ($v as $v2) {
+ $im->addTypePreference($n, $v2);
+ }
+ } else {
+ $im->addTypePreference($n, $v);
+ }
+ }
+ break;
+ default:
+ foreach ($data as $n => $v) {
+ switch ($n) {
+ case 'parameters':
+ case 'parameter':
+ $im->setParameters($target, $v);
+ break;
+ case 'injections':
+ case 'injection':
+ $im->setInjections($target, $v);
+ break;
+ case 'shared':
+ case 'share':
+ $im->setShared($target, $v);
+ break;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/library/Zend/Di/Definition/Annotation/Inject.php b/library/Zend/Di/Definition/Annotation/Inject.php
new file mode 100755
index 0000000000..8534c02151
--- /dev/null
+++ b/library/Zend/Di/Definition/Annotation/Inject.php
@@ -0,0 +1,31 @@
+content = $content;
+ }
+}
diff --git a/library/Zend/Di/Definition/Annotation/Instantiator.php b/library/Zend/Di/Definition/Annotation/Instantiator.php
new file mode 100755
index 0000000000..d0aed5310d
--- /dev/null
+++ b/library/Zend/Di/Definition/Annotation/Instantiator.php
@@ -0,0 +1,31 @@
+content = $content;
+ }
+}
diff --git a/library/Zend/Di/Definition/ArrayDefinition.php b/library/Zend/Di/Definition/ArrayDefinition.php
new file mode 100755
index 0000000000..5e64f5b38f
--- /dev/null
+++ b/library/Zend/Di/Definition/ArrayDefinition.php
@@ -0,0 +1,176 @@
+ $value) {
+ // force lower names
+ $dataArray[$class] = array_change_key_case($dataArray[$class], CASE_LOWER);
+ }
+ foreach ($dataArray as $class => $definition) {
+ if (isset($definition['methods']) && is_array($definition['methods'])) {
+ foreach ($definition['methods'] as $type => $requirement) {
+ if (!is_int($requirement)) {
+ $dataArray[$class]['methods'][$type] = InjectionMethod::detectMethodRequirement($requirement);
+ }
+ }
+ }
+ }
+ $this->dataArray = $dataArray;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getClasses()
+ {
+ return array_keys($this->dataArray);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasClass($class)
+ {
+ return array_key_exists($class, $this->dataArray);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getClassSupertypes($class)
+ {
+ if (!isset($this->dataArray[$class])) {
+ return array();
+ }
+
+ if (!isset($this->dataArray[$class]['supertypes'])) {
+ return array();
+ }
+
+ return $this->dataArray[$class]['supertypes'];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getInstantiator($class)
+ {
+ if (!isset($this->dataArray[$class])) {
+ return null;
+ }
+
+ if (!isset($this->dataArray[$class]['instantiator'])) {
+ return '__construct';
+ }
+
+ return $this->dataArray[$class]['instantiator'];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasMethods($class)
+ {
+ if (!isset($this->dataArray[$class])) {
+ return false;
+ }
+
+ if (!isset($this->dataArray[$class]['methods'])) {
+ return false;
+ }
+
+ return (count($this->dataArray[$class]['methods']) > 0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasMethod($class, $method)
+ {
+ if (!isset($this->dataArray[$class])) {
+ return false;
+ }
+
+ if (!isset($this->dataArray[$class]['methods'])) {
+ return false;
+ }
+
+ return array_key_exists($method, $this->dataArray[$class]['methods']);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getMethods($class)
+ {
+ if (!isset($this->dataArray[$class])) {
+ return array();
+ }
+
+ if (!isset($this->dataArray[$class]['methods'])) {
+ return array();
+ }
+
+ return $this->dataArray[$class]['methods'];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasMethodParameters($class, $method)
+ {
+ return isset($this->dataArray[$class]['parameters'][$method]);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getMethodParameters($class, $method)
+ {
+ if (!isset($this->dataArray[$class])) {
+ return array();
+ }
+
+ if (!isset($this->dataArray[$class]['parameters'])) {
+ return array();
+ }
+
+ if (!isset($this->dataArray[$class]['parameters'][$method])) {
+ return array();
+ }
+
+ return $this->dataArray[$class]['parameters'][$method];
+ }
+
+ /**
+ * @return array
+ */
+ public function toArray()
+ {
+ return $this->dataArray;
+ }
+}
diff --git a/library/Zend/Di/Definition/Builder/InjectionMethod.php b/library/Zend/Di/Definition/Builder/InjectionMethod.php
new file mode 100755
index 0000000000..a27f7b1922
--- /dev/null
+++ b/library/Zend/Di/Definition/Builder/InjectionMethod.php
@@ -0,0 +1,121 @@
+name = $name;
+
+ return $this;
+ }
+
+ /**
+ * @return null|string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @param string $name
+ * @param string|null $class
+ * @param mixed|null $isRequired
+ * @param mixed|null $default
+ * @return InjectionMethod
+ */
+ public function addParameter($name, $class = null, $isRequired = null, $default = null)
+ {
+ $this->parameters[] = array(
+ $name,
+ $class,
+ self::detectMethodRequirement($isRequired),
+ $default,
+ );
+
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getParameters()
+ {
+ return $this->parameters;
+ }
+
+ /**
+ *
+ * @param mixed $requirement
+ * @return int
+ */
+ public static function detectMethodRequirement($requirement)
+ {
+ if (is_bool($requirement)) {
+ return $requirement ? Di::METHOD_IS_REQUIRED : Di::METHOD_IS_OPTIONAL;
+ }
+
+ if (null === $requirement) {
+ //This is mismatch to ClassDefinition::addMethod is it ok ? is optional?
+ return Di::METHOD_IS_REQUIRED;
+ }
+
+ if (is_int($requirement)) {
+ return $requirement;
+ }
+
+ if (is_string($requirement)) {
+ switch (strtolower($requirement)) {
+ default:
+ case "require":
+ case "required":
+ return Di::METHOD_IS_REQUIRED;
+ break;
+ case "aware":
+ return Di::METHOD_IS_AWARE;
+ break;
+ case "optional":
+ return Di::METHOD_IS_OPTIONAL;
+ break;
+ case "constructor":
+ return Di::METHOD_IS_CONSTRUCTOR;
+ break;
+ case "instantiator":
+ return Di::METHOD_IS_INSTANTIATOR;
+ break;
+ case "eager":
+ return Di::METHOD_IS_EAGER;
+ break;
+ }
+ }
+ return 0;
+ }
+}
diff --git a/library/Zend/Di/Definition/Builder/PhpClass.php b/library/Zend/Di/Definition/Builder/PhpClass.php
new file mode 100755
index 0000000000..80d4197a28
--- /dev/null
+++ b/library/Zend/Di/Definition/Builder/PhpClass.php
@@ -0,0 +1,175 @@
+name = $name;
+
+ return $this;
+ }
+
+ /**
+ * Get name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @param string|\Callable|array $instantiator
+ * @return PhpClass
+ */
+ public function setInstantiator($instantiator)
+ {
+ $this->instantiator = $instantiator;
+
+ return $this;
+ }
+
+ /**
+ * @return array|\Callable|string
+ */
+ public function getInstantiator()
+ {
+ return $this->instantiator;
+ }
+
+ /**
+ * @param string $superType
+ * @return PhpClass
+ */
+ public function addSuperType($superType)
+ {
+ $this->superTypes[] = $superType;
+
+ return $this;
+ }
+
+ /**
+ * Get super types
+ *
+ * @return array
+ */
+ public function getSuperTypes()
+ {
+ return $this->superTypes;
+ }
+
+ /**
+ * Add injection method
+ *
+ * @param InjectionMethod $injectionMethod
+ * @return PhpClass
+ */
+ public function addInjectionMethod(InjectionMethod $injectionMethod)
+ {
+ $this->injectionMethods[] = $injectionMethod;
+
+ return $this;
+ }
+
+ /**
+ * Create and register an injection method
+ *
+ * Optionally takes the method name.
+ *
+ * This method may be used in lieu of addInjectionMethod() in
+ * order to provide a more fluent interface for building classes with
+ * injection methods.
+ *
+ * @param null|string $name
+ * @return InjectionMethod
+ */
+ public function createInjectionMethod($name = null)
+ {
+ $builder = $this->defaultMethodBuilder;
+ /* @var $method InjectionMethod */
+ $method = new $builder();
+ if (null !== $name) {
+ $method->setName($name);
+ }
+ $this->addInjectionMethod($method);
+
+ return $method;
+ }
+
+ /**
+ * Override which class will be used by {@link createInjectionMethod()}
+ *
+ * @param string $class
+ * @return PhpClass
+ */
+ public function setMethodBuilder($class)
+ {
+ $this->defaultMethodBuilder = $class;
+
+ return $this;
+ }
+
+ /**
+ * Determine what class will be used by {@link createInjectionMethod()}
+ *
+ * Mainly to provide the ability to temporarily override the class used.
+ *
+ * @return string
+ */
+ public function getMethodBuilder()
+ {
+ return $this->defaultMethodBuilder;
+ }
+
+ /**
+ * @return InjectionMethod[]
+ */
+ public function getInjectionMethods()
+ {
+ return $this->injectionMethods;
+ }
+}
diff --git a/library/Zend/Di/Definition/BuilderDefinition.php b/library/Zend/Di/Definition/BuilderDefinition.php
new file mode 100755
index 0000000000..6ad935a074
--- /dev/null
+++ b/library/Zend/Di/Definition/BuilderDefinition.php
@@ -0,0 +1,321 @@
+ $classInfo) {
+ $class = new Builder\PhpClass();
+ $class->setName($className);
+ foreach ($classInfo as $type => $typeData) {
+ switch (strtolower($type)) {
+ case 'supertypes':
+ foreach ($typeData as $superType) {
+ $class->addSuperType($superType);
+ }
+ break;
+ case 'instantiator':
+ $class->setInstantiator($typeData);
+ break;
+ case 'methods':
+ case 'method':
+ foreach ($typeData as $injectionMethodName => $injectionMethodData) {
+ $injectionMethod = new Builder\InjectionMethod();
+ $injectionMethod->setName($injectionMethodName);
+ foreach ($injectionMethodData as $parameterName => $parameterType) {
+ $parameterType = ($parameterType) ?: null; // force empty string to null
+ $injectionMethod->addParameter($parameterName, $parameterType);
+ }
+ $class->addInjectionMethod($injectionMethod);
+ }
+ break;
+
+ }
+ }
+ $this->addClass($class);
+ }
+ }
+
+ /**
+ * Add class
+ *
+ * @param Builder\PhpClass $phpClass
+ * @return BuilderDefinition
+ */
+ public function addClass(Builder\PhpClass $phpClass)
+ {
+ $this->classes[] = $phpClass;
+
+ return $this;
+ }
+
+ /**
+ * Create a class builder object using default class builder class
+ *
+ * This method is a factory that can be used in place of addClass().
+ *
+ * @param null|string $name Optional name of class to assign
+ * @return Builder\PhpClass
+ */
+ public function createClass($name = null)
+ {
+ $builderClass = $this->defaultClassBuilder;
+ /* @var $class Builder\PhpClass */
+ $class = new $builderClass();
+
+ if (null !== $name) {
+ $class->setName($name);
+ }
+
+ $this->addClass($class);
+
+ return $class;
+ }
+
+ /**
+ * Set the class to use with {@link createClass()}
+ *
+ * @param string $class
+ * @return BuilderDefinition
+ */
+ public function setClassBuilder($class)
+ {
+ $this->defaultClassBuilder = $class;
+
+ return $this;
+ }
+
+ /**
+ * Get the class used for {@link createClass()}
+ *
+ * This is primarily to allow developers to temporarily override
+ * the builder strategy.
+ *
+ * @return string
+ */
+ public function getClassBuilder()
+ {
+ return $this->defaultClassBuilder;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getClasses()
+ {
+ $classNames = array();
+
+ /* @var $class Builder\PhpClass */
+ foreach ($this->classes as $class) {
+ $classNames[] = $class->getName();
+ }
+
+ return $classNames;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasClass($class)
+ {
+ foreach ($this->classes as $classObj) {
+ if ($classObj->getName() === $class) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @param string $name
+ * @return bool|Builder\PhpClass
+ */
+ protected function getClass($name)
+ {
+ foreach ($this->classes as $classObj) {
+ if ($classObj->getName() === $name) {
+ return $classObj;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws \Zend\Di\Exception\RuntimeException
+ */
+ public function getClassSupertypes($class)
+ {
+ $class = $this->getClass($class);
+
+ if ($class === false) {
+ throw new Exception\RuntimeException('Cannot find class object in this builder definition.');
+ }
+
+ return $class->getSuperTypes();
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws \Zend\Di\Exception\RuntimeException
+ */
+ public function getInstantiator($class)
+ {
+ $class = $this->getClass($class);
+ if ($class === false) {
+ throw new Exception\RuntimeException('Cannot find class object in this builder definition.');
+ }
+
+ return $class->getInstantiator();
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws \Zend\Di\Exception\RuntimeException
+ */
+ public function hasMethods($class)
+ {
+ /* @var $class \Zend\Di\Definition\Builder\PhpClass */
+ $class = $this->getClass($class);
+ if ($class === false) {
+ throw new Exception\RuntimeException('Cannot find class object in this builder definition.');
+ }
+
+ return (count($class->getInjectionMethods()) > 0);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws \Zend\Di\Exception\RuntimeException
+ */
+ public function getMethods($class)
+ {
+ $class = $this->getClass($class);
+ if ($class === false) {
+ throw new Exception\RuntimeException('Cannot find class object in this builder definition.');
+ }
+ $methods = $class->getInjectionMethods();
+ $methodNames = array();
+
+ /* @var $methodObj Builder\InjectionMethod */
+ foreach ($methods as $methodObj) {
+ $methodNames[] = $methodObj->getName();
+ }
+
+ return $methodNames;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws \Zend\Di\Exception\RuntimeException
+ */
+ public function hasMethod($class, $method)
+ {
+ $class = $this->getClass($class);
+ if ($class === false) {
+ throw new Exception\RuntimeException('Cannot find class object in this builder definition.');
+ }
+ $methods = $class->getInjectionMethods();
+
+ /* @var $methodObj Builder\InjectionMethod */
+ foreach ($methods as $methodObj) {
+ if ($methodObj->getName() === $method) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasMethodParameters($class, $method)
+ {
+ $class = $this->getClass($class);
+ if ($class === false) {
+ return false;
+ }
+ $methods = $class->getInjectionMethods();
+ /* @var $methodObj Builder\InjectionMethod */
+ foreach ($methods as $methodObj) {
+ if ($methodObj->getName() === $method) {
+ $method = $methodObj;
+ }
+ }
+ if (!$method instanceof Builder\InjectionMethod) {
+ return false;
+ }
+
+ /* @var $method Builder\InjectionMethod */
+
+ return (count($method->getParameters()) > 0);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws \Zend\Di\Exception\RuntimeException
+ */
+ public function getMethodParameters($class, $method)
+ {
+ $class = $this->getClass($class);
+
+ if ($class === false) {
+ throw new Exception\RuntimeException('Cannot find class object in this builder definition.');
+ }
+
+ $methods = $class->getInjectionMethods();
+
+ /* @var $methodObj Builder\InjectionMethod */
+ foreach ($methods as $methodObj) {
+ if ($methodObj->getName() === $method) {
+ $method = $methodObj;
+ }
+ }
+
+ if (!$method instanceof Builder\InjectionMethod) {
+ throw new Exception\RuntimeException('Cannot find method object for method ' . $method . ' in this builder definition.');
+ }
+
+ $methodParameters = array();
+
+ /* @var $method Builder\InjectionMethod */
+ foreach ($method->getParameters() as $name => $info) {
+ $methodParameters[$class->getName() . '::' . $method->getName() . ':' . $name] = $info;
+ }
+
+ return $methodParameters;
+ }
+}
diff --git a/library/Zend/Di/Definition/ClassDefinition.php b/library/Zend/Di/Definition/ClassDefinition.php
new file mode 100755
index 0000000000..2b110659da
--- /dev/null
+++ b/library/Zend/Di/Definition/ClassDefinition.php
@@ -0,0 +1,231 @@
+class = $class;
+ }
+
+ /**
+ * @param null|\Callable|array|string $instantiator
+ * @return self
+ */
+ public function setInstantiator($instantiator)
+ {
+ $this->instantiator = $instantiator;
+
+ return $this;
+ }
+
+ /**
+ * @param string[] $supertypes
+ * @return self
+ */
+ public function setSupertypes(array $supertypes)
+ {
+ $this->supertypes = $supertypes;
+
+ return $this;
+ }
+
+ /**
+ * @param string $method
+ * @param mixed|bool|null $isRequired
+ * @return self
+ */
+ public function addMethod($method, $isRequired = null)
+ {
+ if ($isRequired === null) {
+ if ($method === '__construct') {
+ $methodRequirementType = Di::METHOD_IS_CONSTRUCTOR;
+ } else {
+ $methodRequirementType = Di::METHOD_IS_OPTIONAL;
+ }
+ } else {
+ $methodRequirementType = InjectionMethod::detectMethodRequirement($isRequired);
+ }
+
+ $this->methods[$method] = $methodRequirementType;
+
+ return $this;
+ }
+
+ /**
+ * @param $method
+ * @param $parameterName
+ * @param array $parameterInfo (keys: required, type)
+ * @return ClassDefinition
+ */
+ public function addMethodParameter($method, $parameterName, array $parameterInfo)
+ {
+ if (!array_key_exists($method, $this->methods)) {
+ if ($method === '__construct') {
+ $this->methods[$method] = Di::METHOD_IS_CONSTRUCTOR;
+ } else {
+ $this->methods[$method] = Di::METHOD_IS_OPTIONAL;
+ }
+ }
+
+ if (!array_key_exists($method, $this->methodParameters)) {
+ $this->methodParameters[$method] = array();
+ }
+
+ $type = (isset($parameterInfo['type'])) ? $parameterInfo['type'] : null;
+ $required = (isset($parameterInfo['required'])) ? (bool) $parameterInfo['required'] : false;
+ $default = (isset($parameterInfo['default'])) ? $parameterInfo['default'] : null;
+
+ $fqName = $this->class . '::' . $method . ':' . $parameterName;
+ $this->methodParameters[$method][$fqName] = array(
+ $parameterName,
+ $type,
+ $required,
+ $default
+ );
+
+ return $this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getClasses()
+ {
+ return array($this->class);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasClass($class)
+ {
+ return ($class === $this->class);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getClassSupertypes($class)
+ {
+ if ($this->class !== $class) {
+ return array();
+ }
+ return $this->supertypes;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getInstantiator($class)
+ {
+ if ($this->class !== $class) {
+ return null;
+ }
+ return $this->instantiator;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasMethods($class)
+ {
+ return (count($this->methods) > 0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getMethods($class)
+ {
+ if ($this->class !== $class) {
+ return array();
+ }
+ return $this->methods;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasMethod($class, $method)
+ {
+ if ($this->class !== $class) {
+ return null;
+ }
+
+ if (is_array($this->methods)) {
+ return array_key_exists($method, $this->methods);
+ }
+
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasMethodParameters($class, $method)
+ {
+ if ($this->class !== $class) {
+ return false;
+ }
+ return (array_key_exists($method, $this->methodParameters));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getMethodParameters($class, $method)
+ {
+ if ($this->class !== $class) {
+ return null;
+ }
+
+ if (array_key_exists($method, $this->methodParameters)) {
+ return $this->methodParameters[$method];
+ }
+
+ return null;
+ }
+}
diff --git a/library/Zend/Di/Definition/CompilerDefinition.php b/library/Zend/Di/Definition/CompilerDefinition.php
new file mode 100755
index 0000000000..dcecde10ff
--- /dev/null
+++ b/library/Zend/Di/Definition/CompilerDefinition.php
@@ -0,0 +1,397 @@
+introspectionStrategy = ($introspectionStrategy) ?: new IntrospectionStrategy();
+ $this->directoryScanner = new AggregateDirectoryScanner();
+ }
+
+ /**
+ * Set introspection strategy
+ *
+ * @param IntrospectionStrategy $introspectionStrategy
+ */
+ public function setIntrospectionStrategy(IntrospectionStrategy $introspectionStrategy)
+ {
+ $this->introspectionStrategy = $introspectionStrategy;
+ }
+
+ /**
+ * @param bool $allowReflectionExceptions
+ */
+ public function setAllowReflectionExceptions($allowReflectionExceptions = true)
+ {
+ $this->allowReflectionExceptions = (bool) $allowReflectionExceptions;
+ }
+
+ /**
+ * Get introspection strategy
+ *
+ * @return IntrospectionStrategy
+ */
+ public function getIntrospectionStrategy()
+ {
+ return $this->introspectionStrategy;
+ }
+
+ /**
+ * Add directory
+ *
+ * @param string $directory
+ */
+ public function addDirectory($directory)
+ {
+ $this->addDirectoryScanner(new DirectoryScanner($directory));
+ }
+
+ /**
+ * Add directory scanner
+ *
+ * @param DirectoryScanner $directoryScanner
+ */
+ public function addDirectoryScanner(DirectoryScanner $directoryScanner)
+ {
+ $this->directoryScanner->addDirectoryScanner($directoryScanner);
+ }
+
+ /**
+ * Add code scanner file
+ *
+ * @param FileScanner $fileScanner
+ */
+ public function addCodeScannerFile(FileScanner $fileScanner)
+ {
+ if ($this->directoryScanner == null) {
+ $this->directoryScanner = new DirectoryScanner();
+ }
+
+ $this->directoryScanner->addFileScanner($fileScanner);
+ }
+
+ /**
+ * Compile
+ *
+ * @return void
+ */
+ public function compile()
+ {
+ /* @var $classScanner DerivedClassScanner */
+ foreach ($this->directoryScanner->getClassNames() as $class) {
+ $this->processClass($class);
+ }
+ }
+
+ /**
+ * @return ArrayDefinition
+ */
+ public function toArrayDefinition()
+ {
+ return new ArrayDefinition(
+ $this->classes
+ );
+ }
+
+ /**
+ * @param string $class
+ * @throws \ReflectionException
+ */
+ protected function processClass($class)
+ {
+ $strategy = $this->introspectionStrategy; // localize for readability
+
+ try {
+ $rClass = new Reflection\ClassReflection($class);
+ } catch (\ReflectionException $e) {
+ if (!$this->allowReflectionExceptions) {
+ throw $e;
+ }
+
+ return;
+ }
+ $className = $rClass->getName();
+ $matches = null; // used for regex below
+
+ // setup the key in classes
+ $this->classes[$className] = array(
+ 'supertypes' => array(),
+ 'instantiator' => null,
+ 'methods' => array(),
+ 'parameters' => array()
+ );
+
+ $def = &$this->classes[$className]; // localize for brevity
+
+ // class annotations?
+ if ($strategy->getUseAnnotations() == true) {
+ $annotations = $rClass->getAnnotations($strategy->getAnnotationManager());
+
+ if (($annotations instanceof AnnotationCollection)
+ && $annotations->hasAnnotation('Zend\Di\Definition\Annotation\Instantiator')
+ ) {
+ // @todo Instantiator support in annotations
+ }
+ }
+
+ /* @var $rTarget \Zend\Code\Reflection\ClassReflection */
+ $rTarget = $rClass;
+ $supertypes = array();
+ do {
+ $supertypes = array_merge($supertypes, $rTarget->getInterfaceNames());
+ if (!($rTargetParent = $rTarget->getParentClass())) {
+ break;
+ }
+ $supertypes[] = $rTargetParent->getName();
+ $rTarget = $rTargetParent;
+ } while (true);
+
+ $def['supertypes'] = $supertypes;
+
+ if ($def['instantiator'] == null) {
+ if ($rClass->isInstantiable()) {
+ $def['instantiator'] = '__construct';
+ }
+ }
+
+ if ($rClass->hasMethod('__construct')) {
+ $def['methods']['__construct'] = true; // required
+ try {
+ $this->processParams($def, $rClass, $rClass->getMethod('__construct'));
+ } catch (\ReflectionException $e) {
+ if (!$this->allowReflectionExceptions) {
+ throw $e;
+ }
+
+ return;
+ }
+ }
+
+ foreach ($rClass->getMethods(Reflection\MethodReflection::IS_PUBLIC) as $rMethod) {
+ $methodName = $rMethod->getName();
+
+ if ($rMethod->getName() === '__construct' || $rMethod->isStatic()) {
+ continue;
+ }
+
+ if ($strategy->getUseAnnotations() == true) {
+ $annotations = $rMethod->getAnnotations($strategy->getAnnotationManager());
+
+ if (($annotations instanceof AnnotationCollection)
+ && $annotations->hasAnnotation('Zend\Di\Definition\Annotation\Inject')
+ ) {
+ $def['methods'][$methodName] = true;
+ $this->processParams($def, $rClass, $rMethod);
+ continue;
+ }
+ }
+
+ $methodPatterns = $this->introspectionStrategy->getMethodNameInclusionPatterns();
+
+ // matches a method injection pattern?
+ foreach ($methodPatterns as $methodInjectorPattern) {
+ preg_match($methodInjectorPattern, $methodName, $matches);
+ if ($matches) {
+ $def['methods'][$methodName] = false; // check ot see if this is required?
+ $this->processParams($def, $rClass, $rMethod);
+ continue 2;
+ }
+ }
+
+ // method
+ // by annotation
+ // by setter pattern,
+ // by interface
+ }
+
+ $interfaceInjectorPatterns = $this->introspectionStrategy->getInterfaceInjectionInclusionPatterns();
+
+ // matches the interface injection pattern
+ /** @var $rIface \ReflectionClass */
+ foreach ($rClass->getInterfaces() as $rIface) {
+ foreach ($interfaceInjectorPatterns as $interfaceInjectorPattern) {
+ preg_match($interfaceInjectorPattern, $rIface->getName(), $matches);
+ if ($matches) {
+ foreach ($rIface->getMethods() as $rMethod) {
+ if (($rMethod->getName() === '__construct') || !count($rMethod->getParameters())) {
+ // constructor not allowed in interfaces
+ // ignore methods without parameters
+ continue;
+ }
+ $def['methods'][$rMethod->getName()] = true;
+ $this->processParams($def, $rClass, $rMethod);
+ }
+ continue 2;
+ }
+ }
+ }
+ }
+
+ /**
+ * @param array $def
+ * @param \Zend\Code\Reflection\ClassReflection $rClass
+ * @param \Zend\Code\Reflection\MethodReflection $rMethod
+ */
+ protected function processParams(&$def, Reflection\ClassReflection $rClass, Reflection\MethodReflection $rMethod)
+ {
+ if (count($rMethod->getParameters()) === 0) {
+ return;
+ }
+
+ $methodName = $rMethod->getName();
+
+ // @todo annotations here for alternate names?
+
+ $def['parameters'][$methodName] = array();
+
+ foreach ($rMethod->getParameters() as $p) {
+ /** @var $p \ReflectionParameter */
+ $actualParamName = $p->getName();
+ $fqName = $rClass->getName() . '::' . $rMethod->getName() . ':' . $p->getPosition();
+ $def['parameters'][$methodName][$fqName] = array();
+
+ // set the class name, if it exists
+ $def['parameters'][$methodName][$fqName][] = $actualParamName;
+ $def['parameters'][$methodName][$fqName][] = ($p->getClass() !== null) ? $p->getClass()->getName() : null;
+ $def['parameters'][$methodName][$fqName][] = !($optional =$p->isOptional());
+ $def['parameters'][$methodName][$fqName][] = $optional && $p->isDefaultValueAvailable() ? $p->getDefaultValue() : null;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getClasses()
+ {
+ return array_keys($this->classes);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasClass($class)
+ {
+ return (array_key_exists($class, $this->classes));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getClassSupertypes($class)
+ {
+ if (!array_key_exists($class, $this->classes)) {
+ $this->processClass($class);
+ }
+
+ return $this->classes[$class]['supertypes'];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getInstantiator($class)
+ {
+ if (!array_key_exists($class, $this->classes)) {
+ $this->processClass($class);
+ }
+
+ return $this->classes[$class]['instantiator'];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasMethods($class)
+ {
+ if (!array_key_exists($class, $this->classes)) {
+ $this->processClass($class);
+ }
+
+ return (count($this->classes[$class]['methods']) > 0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasMethod($class, $method)
+ {
+ if (!array_key_exists($class, $this->classes)) {
+ $this->processClass($class);
+ }
+
+ return isset($this->classes[$class]['methods'][$method]);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getMethods($class)
+ {
+ if (!array_key_exists($class, $this->classes)) {
+ $this->processClass($class);
+ }
+
+ return $this->classes[$class]['methods'];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasMethodParameters($class, $method)
+ {
+ if (!isset($this->classes[$class])) {
+ return false;
+ }
+
+ return (array_key_exists($method, $this->classes[$class]['parameters']));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getMethodParameters($class, $method)
+ {
+ if (!is_array($this->classes[$class])) {
+ $this->processClass($class);
+ }
+
+ return $this->classes[$class]['parameters'][$method];
+ }
+}
diff --git a/library/Zend/Di/Definition/DefinitionInterface.php b/library/Zend/Di/Definition/DefinitionInterface.php
new file mode 100755
index 0000000000..420bb459d1
--- /dev/null
+++ b/library/Zend/Di/Definition/DefinitionInterface.php
@@ -0,0 +1,101 @@
+annotationManager = ($annotationManager) ?: $this->createDefaultAnnotationManager();
+ }
+
+ /**
+ * Get annotation manager
+ *
+ * @return null|AnnotationManager
+ */
+ public function getAnnotationManager()
+ {
+ return $this->annotationManager;
+ }
+
+ /**
+ * Create default annotation manager
+ *
+ * @return AnnotationManager
+ */
+ public function createDefaultAnnotationManager()
+ {
+ $annotationManager = new AnnotationManager;
+ $parser = new GenericAnnotationParser();
+ $parser->registerAnnotation(new Annotation\Inject());
+ $annotationManager->attach($parser);
+
+ return $annotationManager;
+ }
+
+ /**
+ * set use annotations
+ *
+ * @param bool $useAnnotations
+ */
+ public function setUseAnnotations($useAnnotations)
+ {
+ $this->useAnnotations = (bool) $useAnnotations;
+ }
+
+ /**
+ * Get use annotations
+ *
+ * @return bool
+ */
+ public function getUseAnnotations()
+ {
+ return $this->useAnnotations;
+ }
+
+ /**
+ * Set method name inclusion pattern
+ *
+ * @param array $methodNameInclusionPatterns
+ */
+ public function setMethodNameInclusionPatterns(array $methodNameInclusionPatterns)
+ {
+ $this->methodNameInclusionPatterns = $methodNameInclusionPatterns;
+ }
+
+ /**
+ * Get method name inclusion pattern
+ *
+ * @return array
+ */
+ public function getMethodNameInclusionPatterns()
+ {
+ return $this->methodNameInclusionPatterns;
+ }
+
+ /**
+ * Set interface injection inclusion patterns
+ *
+ * @param array $interfaceInjectionInclusionPatterns
+ */
+ public function setInterfaceInjectionInclusionPatterns(array $interfaceInjectionInclusionPatterns)
+ {
+ $this->interfaceInjectionInclusionPatterns = $interfaceInjectionInclusionPatterns;
+ }
+
+ /**
+ * Get interface injection inclusion patterns
+ *
+ * @return array
+ */
+ public function getInterfaceInjectionInclusionPatterns()
+ {
+ return $this->interfaceInjectionInclusionPatterns;
+ }
+}
diff --git a/library/Zend/Di/Definition/PartialMarker.php b/library/Zend/Di/Definition/PartialMarker.php
new file mode 100755
index 0000000000..4a40728f0b
--- /dev/null
+++ b/library/Zend/Di/Definition/PartialMarker.php
@@ -0,0 +1,14 @@
+introspectionStrategy = ($introspectionStrategy) ?: new IntrospectionStrategy();
+ if ($explicitClasses) {
+ $this->setExplicitClasses($explicitClasses);
+ }
+ }
+
+ /**
+ * @param IntrospectionStrategy $introspectionStrategy
+ * @return void
+ */
+ public function setIntrospectionStrategy(IntrospectionStrategy $introspectionStrategy)
+ {
+ $this->introspectionStrategy = $introspectionStrategy;
+ }
+
+ /**
+ * @return IntrospectionStrategy
+ */
+ public function getIntrospectionStrategy()
+ {
+ return $this->introspectionStrategy;
+ }
+
+ /**
+ * Set explicit classes
+ *
+ * @param array $explicitClasses
+ */
+ public function setExplicitClasses(array $explicitClasses)
+ {
+ $this->explicitLookups = true;
+ foreach ($explicitClasses as $eClass) {
+ $this->classes[$eClass] = true;
+ }
+ $this->classes = $explicitClasses;
+ }
+
+ /**
+ * @param string $class
+ */
+ public function forceLoadClass($class)
+ {
+ $this->processClass($class, true);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getClasses()
+ {
+ return array_keys($this->classes);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasClass($class)
+ {
+ if ($this->explicitLookups === true) {
+ return (array_key_exists($class, $this->classes));
+ }
+
+ return class_exists($class) || interface_exists($class);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getClassSupertypes($class)
+ {
+ $this->processClass($class);
+ return $this->classes[$class]['supertypes'];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getInstantiator($class)
+ {
+ $this->processClass($class);
+ return $this->classes[$class]['instantiator'];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasMethods($class)
+ {
+ $this->processClass($class);
+ return (count($this->classes[$class]['methods']) > 0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasMethod($class, $method)
+ {
+ $this->processClass($class);
+ return isset($this->classes[$class]['methods'][$method]);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getMethods($class)
+ {
+ $this->processClass($class);
+ return $this->classes[$class]['methods'];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasMethodParameters($class, $method)
+ {
+ $this->processClass($class);
+ return (array_key_exists($method, $this->classes[$class]['parameters']));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getMethodParameters($class, $method)
+ {
+ $this->processClass($class);
+ return $this->classes[$class]['parameters'][$method];
+ }
+
+ /**
+ * @param string $class
+ *
+ * @return bool
+ */
+ protected function hasProcessedClass($class)
+ {
+ return array_key_exists($class, $this->classes) && is_array($this->classes[$class]);
+ }
+
+ /**
+ * @param string $class
+ * @param bool $forceLoad
+ */
+ protected function processClass($class, $forceLoad = false)
+ {
+ if (!$forceLoad && $this->hasProcessedClass($class)) {
+ return;
+ }
+ $strategy = $this->introspectionStrategy; // localize for readability
+
+ /** @var $rClass \Zend\Code\Reflection\ClassReflection */
+ $rClass = new Reflection\ClassReflection($class);
+ $className = $rClass->getName();
+ $matches = null; // used for regex below
+
+ // setup the key in classes
+ $this->classes[$className] = array(
+ 'supertypes' => array(),
+ 'instantiator' => null,
+ 'methods' => array(),
+ 'parameters' => array()
+ );
+
+ $def = &$this->classes[$className]; // localize for brevity
+
+ // class annotations?
+ if ($strategy->getUseAnnotations() == true) {
+ $annotations = $rClass->getAnnotations($strategy->getAnnotationManager());
+
+ if (($annotations instanceof AnnotationCollection)
+ && $annotations->hasAnnotation('Zend\Di\Definition\Annotation\Instantiator')) {
+ // @todo Instantiator support in annotations
+ }
+ }
+
+ $rTarget = $rClass;
+ $supertypes = array();
+ do {
+ $supertypes = array_merge($supertypes, $rTarget->getInterfaceNames());
+ if (!($rTargetParent = $rTarget->getParentClass())) {
+ break;
+ }
+ $supertypes[] = $rTargetParent->getName();
+ $rTarget = $rTargetParent;
+ } while (true);
+
+ $def['supertypes'] = $supertypes;
+
+ if ($def['instantiator'] == null) {
+ if ($rClass->isInstantiable()) {
+ $def['instantiator'] = '__construct';
+ }
+ }
+
+ if ($rClass->hasMethod('__construct')) {
+ $def['methods']['__construct'] = Di::METHOD_IS_CONSTRUCTOR; // required
+ $this->processParams($def, $rClass, $rClass->getMethod('__construct'));
+ }
+
+ foreach ($rClass->getMethods(Reflection\MethodReflection::IS_PUBLIC) as $rMethod) {
+ $methodName = $rMethod->getName();
+
+ if ($rMethod->getName() === '__construct' || $rMethod->isStatic()) {
+ continue;
+ }
+
+ if ($strategy->getUseAnnotations() == true) {
+ $annotations = $rMethod->getAnnotations($strategy->getAnnotationManager());
+
+ if (($annotations instanceof AnnotationCollection)
+ && $annotations->hasAnnotation('Zend\Di\Definition\Annotation\Inject')) {
+ // use '@inject' and search for parameters
+ $def['methods'][$methodName] = Di::METHOD_IS_EAGER;
+ $this->processParams($def, $rClass, $rMethod);
+ continue;
+ }
+ }
+
+ $methodPatterns = $this->introspectionStrategy->getMethodNameInclusionPatterns();
+
+ // matches a method injection pattern?
+ foreach ($methodPatterns as $methodInjectorPattern) {
+ preg_match($methodInjectorPattern, $methodName, $matches);
+ if ($matches) {
+ $def['methods'][$methodName] = Di::METHOD_IS_OPTIONAL; // check ot see if this is required?
+ $this->processParams($def, $rClass, $rMethod);
+ continue 2;
+ }
+ }
+
+ // method
+ // by annotation
+ // by setter pattern,
+ // by interface
+ }
+
+ $interfaceInjectorPatterns = $this->introspectionStrategy->getInterfaceInjectionInclusionPatterns();
+
+ // matches the interface injection pattern
+ /** @var $rIface \ReflectionClass */
+ foreach ($rClass->getInterfaces() as $rIface) {
+ foreach ($interfaceInjectorPatterns as $interfaceInjectorPattern) {
+ preg_match($interfaceInjectorPattern, $rIface->getName(), $matches);
+ if ($matches) {
+ foreach ($rIface->getMethods() as $rMethod) {
+ if (($rMethod->getName() === '__construct') || !count($rMethod->getParameters())) {
+ // constructor not allowed in interfaces
+ // Don't call interface methods without a parameter (Some aware interfaces define setters in ZF2)
+ continue;
+ }
+ $def['methods'][$rMethod->getName()] = Di::METHOD_IS_AWARE;
+ $this->processParams($def, $rClass, $rMethod);
+ }
+ continue 2;
+ }
+ }
+ }
+ }
+
+ /**
+ * @param array $def
+ * @param \Zend\Code\Reflection\ClassReflection $rClass
+ * @param \Zend\Code\Reflection\MethodReflection $rMethod
+ */
+ protected function processParams(&$def, Reflection\ClassReflection $rClass, Reflection\MethodReflection $rMethod)
+ {
+ if (count($rMethod->getParameters()) === 0) {
+ return;
+ }
+
+ $methodName = $rMethod->getName();
+
+ // @todo annotations here for alternate names?
+
+ $def['parameters'][$methodName] = array();
+
+ foreach ($rMethod->getParameters() as $p) {
+ /** @var $p \ReflectionParameter */
+ $actualParamName = $p->getName();
+
+ $fqName = $rClass->getName() . '::' . $rMethod->getName() . ':' . $p->getPosition();
+
+ $def['parameters'][$methodName][$fqName] = array();
+
+ // set the class name, if it exists
+ $def['parameters'][$methodName][$fqName][] = $actualParamName;
+ $def['parameters'][$methodName][$fqName][] = ($p->getClass() !== null) ? $p->getClass()->getName() : null;
+ $def['parameters'][$methodName][$fqName][] = !($optional = $p->isOptional() && $p->isDefaultValueAvailable());
+ $def['parameters'][$methodName][$fqName][] = $optional ? $p->getDefaultValue() : null;
+ }
+ }
+}
diff --git a/library/Zend/Di/DefinitionList.php b/library/Zend/Di/DefinitionList.php
new file mode 100755
index 0000000000..efe190e57f
--- /dev/null
+++ b/library/Zend/Di/DefinitionList.php
@@ -0,0 +1,257 @@
+push($definition);
+ }
+ }
+
+ /**
+ * Add definitions
+ *
+ * @param Definition\DefinitionInterface $definition
+ * @param bool $addToBackOfList
+ * @return void
+ */
+ public function addDefinition(Definition\DefinitionInterface $definition, $addToBackOfList = true)
+ {
+ if ($addToBackOfList) {
+ $this->push($definition);
+ } else {
+ $this->unshift($definition);
+ }
+ }
+
+ /**
+ * @param string $type
+ * @return Definition\DefinitionInterface[]
+ */
+ public function getDefinitionsByType($type)
+ {
+ $definitions = array();
+ foreach ($this as $definition) {
+ if ($definition instanceof $type) {
+ $definitions[] = $definition;
+ }
+ }
+
+ return $definitions;
+ }
+
+ /**
+ * Get definition by type
+ *
+ * @param string $type
+ * @return Definition\DefinitionInterface
+ */
+ public function getDefinitionByType($type)
+ {
+ foreach ($this as $definition) {
+ if ($definition instanceof $type) {
+ return $definition;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @param string $class
+ * @return bool|Definition\DefinitionInterface
+ */
+ public function getDefinitionForClass($class)
+ {
+ /** @var $definition Definition\DefinitionInterface */
+ foreach ($this as $definition) {
+ if ($definition->hasClass($class)) {
+ return $definition;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @param string $class
+ * @return bool|Definition\DefinitionInterface
+ */
+ public function forClass($class)
+ {
+ return $this->getDefinitionForClass($class);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getClasses()
+ {
+ $classes = array();
+ /** @var $definition Definition\DefinitionInterface */
+ foreach ($this as $definition) {
+ $classes = array_merge($classes, $definition->getClasses());
+ }
+
+ return $classes;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasClass($class)
+ {
+ /** @var $definition Definition\DefinitionInterface */
+ foreach ($this as $definition) {
+ if ($definition->hasClass($class)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getClassSupertypes($class)
+ {
+ $supertypes = array();
+ /** @var $definition Definition\DefinitionInterface */
+ foreach ($this as $definition) {
+ if ($definition->hasClass($class)) {
+ $supertypes = array_merge($supertypes, $definition->getClassSupertypes($class));
+ if ($definition instanceof Definition\PartialMarker) {
+ continue;
+ }
+
+ return $supertypes;
+ }
+ }
+ return $supertypes;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getInstantiator($class)
+ {
+ /** @var $definition Definition\DefinitionInterface */
+ foreach ($this as $definition) {
+ if ($definition->hasClass($class)) {
+ $value = $definition->getInstantiator($class);
+ if ($value === null && $definition instanceof Definition\PartialMarker) {
+ continue;
+ }
+
+ return $value;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasMethods($class)
+ {
+ /** @var $definition Definition\DefinitionInterface */
+ foreach ($this as $definition) {
+ if ($definition->hasClass($class)) {
+ if ($definition->hasMethods($class) === false && $definition instanceof Definition\PartialMarker) {
+ continue;
+ }
+
+ return $definition->hasMethods($class);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasMethod($class, $method)
+ {
+ if (!$this->hasMethods($class)) {
+ return false;
+ }
+
+ /** @var $definition Definition\DefinitionInterface */
+ foreach ($this as $definition) {
+ if ($definition->hasClass($class) && $definition->hasMethod($class, $method)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getMethods($class)
+ {
+ /** @var $definition Definition\DefinitionInterface */
+ $methods = array();
+ foreach ($this as $definition) {
+ if ($definition->hasClass($class)) {
+ if (!$definition instanceof Definition\PartialMarker) {
+ return array_merge($definition->getMethods($class), $methods);
+ }
+
+ $methods = array_merge($definition->getMethods($class), $methods);
+ }
+ }
+
+ return $methods;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasMethodParameters($class, $method)
+ {
+ $methodParameters = $this->getMethodParameters($class, $method);
+
+ return ($methodParameters !== array());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getMethodParameters($class, $method)
+ {
+ /** @var $definition Definition\DefinitionInterface */
+ foreach ($this as $definition) {
+ if ($definition->hasClass($class) && $definition->hasMethod($class, $method) && $definition->hasMethodParameters($class, $method)) {
+ return $definition->getMethodParameters($class, $method);
+ }
+ }
+
+ return array();
+ }
+}
diff --git a/library/Zend/Di/DependencyInjectionInterface.php b/library/Zend/Di/DependencyInjectionInterface.php
new file mode 100755
index 0000000000..b821876136
--- /dev/null
+++ b/library/Zend/Di/DependencyInjectionInterface.php
@@ -0,0 +1,25 @@
+definitions = ($definitions) ?: new DefinitionList(new Definition\RuntimeDefinition());
+ $this->instanceManager = ($instanceManager) ?: new InstanceManager();
+
+ if ($config) {
+ $this->configure($config);
+ }
+ }
+
+ /**
+ * Provide a configuration object to configure this instance
+ *
+ * @param Config $config
+ * @return void
+ */
+ public function configure(Config $config)
+ {
+ $config->configure($this);
+ }
+
+ /**
+ * @param DefinitionList $definitions
+ * @return self
+ */
+ public function setDefinitionList(DefinitionList $definitions)
+ {
+ $this->definitions = $definitions;
+
+ return $this;
+ }
+
+ /**
+ * @return DefinitionList
+ */
+ public function definitions()
+ {
+ return $this->definitions;
+ }
+
+ /**
+ * Set the instance manager
+ *
+ * @param InstanceManager $instanceManager
+ * @return Di
+ */
+ public function setInstanceManager(InstanceManager $instanceManager)
+ {
+ $this->instanceManager = $instanceManager;
+
+ return $this;
+ }
+
+ /**
+ *
+ * @return InstanceManager
+ */
+ public function instanceManager()
+ {
+ return $this->instanceManager;
+ }
+
+ /**
+ * Utility method used to retrieve the class of a particular instance. This is here to allow extending classes to
+ * override how class names are resolved
+ *
+ * @internal this method is used by the ServiceLocator\DependencyInjectorProxy class to interact with instances
+ * and is a hack to be used internally until a major refactor does not split the `resolveMethodParameters`. Do not
+ * rely on its functionality.
+ * @param object $instance
+ * @return string
+ */
+ protected function getClass($instance)
+ {
+ return get_class($instance);
+ }
+
+ /**
+ * @param $name
+ * @param array $params
+ * @param string $method
+ * @return array
+ */
+ protected function getCallParameters($name, array $params, $method = "__construct")
+ {
+ $im = $this->instanceManager;
+ $class = $im->hasAlias($name) ? $im->getClassFromAlias($name) : $name;
+ if ($this->definitions->hasClass($class)) {
+ $callParameters = array();
+ if ($this->definitions->hasMethod($class, $method)) {
+ foreach ($this->definitions->getMethodParameters($class, $method) as $param) {
+ if (isset($params[$param[0]])) {
+ $callParameters[$param[0]] = $params[$param[0]];
+ }
+ }
+ }
+ return $callParameters;
+ }
+ return $params;
+ }
+
+ /**
+ * Lazy-load a class
+ *
+ * Attempts to load the class (or service alias) provided. If it has been
+ * loaded before, the previous instance will be returned (unless the service
+ * definition indicates shared instances should not be used).
+ *
+ * @param string $name Class name or service alias
+ * @param null|array $params Parameters to pass to the constructor
+ * @return object|null
+ */
+ public function get($name, array $params = array())
+ {
+ array_push($this->instanceContext, array('GET', $name, null));
+
+ $im = $this->instanceManager;
+
+ $callParameters = $this->getCallParameters($name, $params);
+ if ($callParameters) {
+ $fastHash = $im->hasSharedInstanceWithParameters($name, $callParameters, true);
+ if ($fastHash) {
+ array_pop($this->instanceContext);
+ return $im->getSharedInstanceWithParameters(null, array(), $fastHash);
+ }
+ }
+
+ if ($im->hasSharedInstance($name, $callParameters)) {
+ array_pop($this->instanceContext);
+ return $im->getSharedInstance($name, $callParameters);
+ }
+
+
+ $config = $im->getConfig($name);
+ $instance = $this->newInstance($name, $params, $config['shared']);
+ array_pop($this->instanceContext);
+
+ return $instance;
+ }
+
+ /**
+ * Retrieve a new instance of a class
+ *
+ * Forces retrieval of a discrete instance of the given class, using the
+ * constructor parameters provided.
+ *
+ * @param mixed $name Class name or service alias
+ * @param array $params Parameters to pass to the constructor
+ * @param bool $isShared
+ * @return object|null
+ * @throws Exception\ClassNotFoundException
+ * @throws Exception\RuntimeException
+ */
+ public function newInstance($name, array $params = array(), $isShared = true)
+ {
+ // localize dependencies
+ $definitions = $this->definitions;
+ $instanceManager = $this->instanceManager();
+
+ if ($instanceManager->hasAlias($name)) {
+ $class = $instanceManager->getClassFromAlias($name);
+ $alias = $name;
+ } else {
+ $class = $name;
+ $alias = null;
+ }
+
+ array_push($this->instanceContext, array('NEW', $class, $alias));
+
+ if (!$definitions->hasClass($class)) {
+ $aliasMsg = ($alias) ? '(specified by alias ' . $alias . ') ' : '';
+ throw new Exception\ClassNotFoundException(
+ 'Class ' . $aliasMsg . $class . ' could not be located in provided definitions.'
+ );
+ }
+
+ $instantiator = $definitions->getInstantiator($class);
+ $injectionMethods = array();
+ $injectionMethods[$class] = $definitions->getMethods($class);
+
+ foreach ($definitions->getClassSupertypes($class) as $supertype) {
+ $injectionMethods[$supertype] = $definitions->getMethods($supertype);
+ }
+
+ if ($instantiator === '__construct') {
+ $instance = $this->createInstanceViaConstructor($class, $params, $alias);
+ if (array_key_exists('__construct', $injectionMethods)) {
+ unset($injectionMethods['__construct']);
+ }
+ } elseif (is_callable($instantiator, false)) {
+ $instance = $this->createInstanceViaCallback($instantiator, $params, $alias);
+ } else {
+ if (is_array($instantiator)) {
+ $msg = sprintf(
+ 'Invalid instantiator: %s::%s() is not callable.',
+ isset($instantiator[0]) ? $instantiator[0] : 'NoClassGiven',
+ isset($instantiator[1]) ? $instantiator[1] : 'NoMethodGiven'
+ );
+ } else {
+ $msg = sprintf(
+ 'Invalid instantiator of type "%s" for "%s".',
+ gettype($instantiator),
+ $name
+ );
+ }
+ throw new Exception\RuntimeException($msg);
+ }
+
+ if ($isShared) {
+ if ($callParameters = $this->getCallParameters($name, $params)) {
+ $this->instanceManager->addSharedInstanceWithParameters($instance, $name, $callParameters);
+ } else {
+ $this->instanceManager->addSharedInstance($instance, $name);
+ }
+ }
+
+ $this->handleInjectDependencies($instance, $injectionMethods, $params, $class, $alias, $name);
+
+ array_pop($this->instanceContext);
+
+ return $instance;
+ }
+
+ /**
+ * Inject dependencies
+ *
+ * @param object $instance
+ * @param array $params
+ * @return void
+ */
+ public function injectDependencies($instance, array $params = array())
+ {
+ $definitions = $this->definitions();
+ $class = $this->getClass($instance);
+ $injectionMethods = array(
+ $class => ($definitions->hasClass($class)) ? $definitions->getMethods($class) : array()
+ );
+ $parent = $class;
+ while ($parent = get_parent_class($parent)) {
+ if ($definitions->hasClass($parent)) {
+ $injectionMethods[$parent] = $definitions->getMethods($parent);
+ }
+ }
+ foreach (class_implements($class) as $interface) {
+ if ($definitions->hasClass($interface)) {
+ $injectionMethods[$interface] = $definitions->getMethods($interface);
+ }
+ }
+ $this->handleInjectDependencies($instance, $injectionMethods, $params, $class, null, null);
+ }
+
+ /**
+ * @param object $instance
+ * @param array $injectionMethods
+ * @param array $params
+ * @param string|null $instanceClass
+ * @param string|null$instanceAlias
+ * @param string $requestedName
+ * @throws Exception\RuntimeException
+ */
+ protected function handleInjectDependencies($instance, $injectionMethods, $params, $instanceClass, $instanceAlias, $requestedName)
+ {
+ // localize dependencies
+ $definitions = $this->definitions;
+ $instanceManager = $this->instanceManager();
+
+ $calledMethods = array('__construct' => true);
+
+ if ($injectionMethods) {
+ foreach ($injectionMethods as $type => $typeInjectionMethods) {
+ foreach ($typeInjectionMethods as $typeInjectionMethod => $methodRequirementType) {
+ if (!isset($calledMethods[$typeInjectionMethod])) {
+ if ($this->resolveAndCallInjectionMethodForInstance($instance, $typeInjectionMethod, $params, $instanceAlias, $methodRequirementType, $type)) {
+ $calledMethods[$typeInjectionMethod] = true;
+ }
+ }
+ }
+ }
+
+ if ($requestedName) {
+ $instanceConfig = $instanceManager->getConfig($requestedName);
+
+ if ($instanceConfig['injections']) {
+ $objectsToInject = $methodsToCall = array();
+ foreach ($instanceConfig['injections'] as $injectName => $injectValue) {
+ if (is_int($injectName) && is_string($injectValue)) {
+ $objectsToInject[] = $this->get($injectValue, $params);
+ } elseif (is_string($injectName) && is_array($injectValue)) {
+ if (is_string(key($injectValue))) {
+ $methodsToCall[] = array('method' => $injectName, 'args' => $injectValue);
+ } else {
+ foreach ($injectValue as $methodCallArgs) {
+ $methodsToCall[] = array('method' => $injectName, 'args' => $methodCallArgs);
+ }
+ }
+ } elseif (is_object($injectValue)) {
+ $objectsToInject[] = $injectValue;
+ } elseif (is_int($injectName) && is_array($injectValue)) {
+ throw new Exception\RuntimeException(
+ 'An injection was provided with a keyed index and an array of data, try using'
+ . ' the name of a particular method as a key for your injection data.'
+ );
+ }
+ }
+ if ($objectsToInject) {
+ foreach ($objectsToInject as $objectToInject) {
+ $calledMethods = array('__construct' => true);
+ foreach ($injectionMethods as $type => $typeInjectionMethods) {
+ foreach ($typeInjectionMethods as $typeInjectionMethod => $methodRequirementType) {
+ if (!isset($calledMethods[$typeInjectionMethod])) {
+ $methodParams = $definitions->getMethodParameters($type, $typeInjectionMethod);
+ if ($methodParams) {
+ foreach ($methodParams as $methodParam) {
+ $objectToInjectClass = $this->getClass($objectToInject);
+ if ($objectToInjectClass == $methodParam[1] || self::isSubclassOf($objectToInjectClass, $methodParam[1])) {
+ if ($this->resolveAndCallInjectionMethodForInstance($instance, $typeInjectionMethod, array($methodParam[0] => $objectToInject), $instanceAlias, self::METHOD_IS_REQUIRED, $type)) {
+ $calledMethods[$typeInjectionMethod] = true;
+ }
+ continue 3;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if ($methodsToCall) {
+ foreach ($methodsToCall as $methodInfo) {
+ $this->resolveAndCallInjectionMethodForInstance($instance, $methodInfo['method'], $methodInfo['args'], $instanceAlias, self::METHOD_IS_REQUIRED, $instanceClass);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Retrieve a class instance based on class name
+ *
+ * Any parameters provided will be used as constructor arguments. If any
+ * given parameter is a DependencyReference object, it will be fetched
+ * from the container so that the instance may be injected.
+ *
+ * @param string $class
+ * @param array $params
+ * @param string|null $alias
+ * @return object
+ */
+ protected function createInstanceViaConstructor($class, $params, $alias = null)
+ {
+ $callParameters = array();
+ if ($this->definitions->hasMethod($class, '__construct')) {
+ $callParameters = $this->resolveMethodParameters($class, '__construct', $params, $alias, self::METHOD_IS_CONSTRUCTOR, true);
+ }
+
+ if (!class_exists($class)) {
+ if (interface_exists($class)) {
+ throw new Exception\ClassNotFoundException(sprintf(
+ 'Cannot instantiate interface "%s"',
+ $class
+ ));
+ }
+ throw new Exception\ClassNotFoundException(sprintf(
+ 'Class "%s" does not exist; cannot instantiate',
+ $class
+ ));
+ }
+
+ // Hack to avoid Reflection in most common use cases
+ switch (count($callParameters)) {
+ case 0:
+ return new $class();
+ case 1:
+ return new $class($callParameters[0]);
+ case 2:
+ return new $class($callParameters[0], $callParameters[1]);
+ case 3:
+ return new $class($callParameters[0], $callParameters[1], $callParameters[2]);
+ default:
+ $r = new \ReflectionClass($class);
+
+ return $r->newInstanceArgs($callParameters);
+ }
+ }
+
+ /**
+ * Get an object instance from the defined callback
+ *
+ * @param callable $callback
+ * @param array $params
+ * @param string $alias
+ * @return object
+ * @throws Exception\InvalidCallbackException
+ * @throws Exception\RuntimeException
+ */
+ protected function createInstanceViaCallback($callback, $params, $alias)
+ {
+ if (!is_callable($callback)) {
+ throw new Exception\InvalidCallbackException('An invalid constructor callback was provided');
+ }
+
+ if (is_array($callback)) {
+ $class = (is_object($callback[0])) ? $this->getClass($callback[0]) : $callback[0];
+ $method = $callback[1];
+ } elseif (is_string($callback) && strpos($callback, '::') !== false) {
+ list($class, $method) = explode('::', $callback, 2);
+ } else {
+ throw new Exception\RuntimeException('Invalid callback type provided to ' . __METHOD__);
+ }
+
+ $callParameters = array();
+ if ($this->definitions->hasMethod($class, $method)) {
+ $callParameters = $this->resolveMethodParameters($class, $method, $params, $alias, self::METHOD_IS_INSTANTIATOR, true);
+ }
+
+ return call_user_func_array($callback, $callParameters);
+ }
+
+ /**
+ * This parameter will handle any injection methods and resolution of
+ * dependencies for such methods
+ *
+ * @param object $instance
+ * @param string $method
+ * @param array $params
+ * @param string $alias
+ * @param bool $methodRequirementType
+ * @param string|null $methodClass
+ * @return bool
+ */
+ protected function resolveAndCallInjectionMethodForInstance($instance, $method, $params, $alias, $methodRequirementType, $methodClass = null)
+ {
+ $methodClass = ($methodClass) ?: $this->getClass($instance);
+ $callParameters = $this->resolveMethodParameters($methodClass, $method, $params, $alias, $methodRequirementType);
+ if ($callParameters == false) {
+ return false;
+ }
+ if ($callParameters !== array_fill(0, count($callParameters), null)) {
+ call_user_func_array(array($instance, $method), $callParameters);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Resolve parameters referencing other services
+ *
+ * @param string $class
+ * @param string $method
+ * @param array $callTimeUserParams
+ * @param string $alias
+ * @param int|bool $methodRequirementType
+ * @param bool $isInstantiator
+ * @throws Exception\MissingPropertyException
+ * @throws Exception\CircularDependencyException
+ * @return array
+ */
+ protected function resolveMethodParameters($class, $method, array $callTimeUserParams, $alias, $methodRequirementType, $isInstantiator = false)
+ {
+ //for BC
+ if (is_bool($methodRequirementType)) {
+ if ($isInstantiator) {
+ $methodRequirementType = Di::METHOD_IS_INSTANTIATOR;
+ } elseif ($methodRequirementType) {
+ $methodRequirementType = Di::METHOD_IS_REQUIRED;
+ } else {
+ $methodRequirementType = Di::METHOD_IS_OPTIONAL;
+ }
+ }
+ // parameters for this method, in proper order, to be returned
+ $resolvedParams = array();
+
+ // parameter requirements from the definition
+ $injectionMethodParameters = $this->definitions->getMethodParameters($class, $method);
+
+ // computed parameters array
+ $computedParams = array(
+ 'value' => array(),
+ 'retrieval' => array(),
+ 'optional' => array()
+ );
+
+ // retrieve instance configurations for all contexts
+ $iConfig = array();
+ $aliases = $this->instanceManager->getAliases();
+
+ // for the alias in the dependency tree
+ if ($alias && $this->instanceManager->hasConfig($alias)) {
+ $iConfig['thisAlias'] = $this->instanceManager->getConfig($alias);
+ }
+
+ // for the current class in the dependency tree
+ if ($this->instanceManager->hasConfig($class)) {
+ $iConfig['thisClass'] = $this->instanceManager->getConfig($class);
+ }
+
+ // for the parent class, provided we are deeper than one node
+ if (isset($this->instanceContext[0])) {
+ list($requestedClass, $requestedAlias) = ($this->instanceContext[0][0] == 'NEW')
+ ? array($this->instanceContext[0][1], $this->instanceContext[0][2])
+ : array($this->instanceContext[1][1], $this->instanceContext[1][2]);
+ } else {
+ $requestedClass = $requestedAlias = null;
+ }
+
+ if ($requestedClass != $class && $this->instanceManager->hasConfig($requestedClass)) {
+ $iConfig['requestedClass'] = $this->instanceManager->getConfig($requestedClass);
+
+ if (array_key_exists('parameters', $iConfig['requestedClass'])) {
+ $newParameters = array();
+
+ foreach ($iConfig['requestedClass']['parameters'] as $name=>$parameter) {
+ $newParameters[$requestedClass.'::'.$method.'::'.$name] = $parameter;
+ }
+
+ $iConfig['requestedClass']['parameters'] = $newParameters;
+ }
+
+ if ($requestedAlias) {
+ $iConfig['requestedAlias'] = $this->instanceManager->getConfig($requestedAlias);
+ }
+ }
+
+ // This is a 2 pass system for resolving parameters
+ // first pass will find the sources, the second pass will order them and resolve lookups if they exist
+ // MOST methods will only have a single parameters to resolve, so this should be fast
+
+ foreach ($injectionMethodParameters as $fqParamPos => $info) {
+ list($name, $type, $isRequired) = $info;
+
+ $fqParamName = substr_replace($fqParamPos, ':' . $info[0], strrpos($fqParamPos, ':'));
+
+ // PRIORITY 1 - consult user provided parameters
+ if (isset($callTimeUserParams[$fqParamPos]) || isset($callTimeUserParams[$name])) {
+ if (isset($callTimeUserParams[$fqParamPos])) {
+ $callTimeCurValue =& $callTimeUserParams[$fqParamPos];
+ } elseif (isset($callTimeUserParams[$fqParamName])) {
+ $callTimeCurValue =& $callTimeUserParams[$fqParamName];
+ } else {
+ $callTimeCurValue =& $callTimeUserParams[$name];
+ }
+
+ if ($type !== false && is_string($callTimeCurValue)) {
+ if ($this->instanceManager->hasAlias($callTimeCurValue)) {
+ // was an alias provided?
+ $computedParams['retrieval'][$fqParamPos] = array(
+ $callTimeUserParams[$name],
+ $this->instanceManager->getClassFromAlias($callTimeCurValue)
+ );
+ } elseif ($this->definitions->hasClass($callTimeUserParams[$name])) {
+ // was a known class provided?
+ $computedParams['retrieval'][$fqParamPos] = array(
+ $callTimeCurValue,
+ $callTimeCurValue
+ );
+ } else {
+ // must be a value
+ $computedParams['value'][$fqParamPos] = $callTimeCurValue;
+ }
+ } else {
+ // int, float, null, object, etc
+ $computedParams['value'][$fqParamPos] = $callTimeCurValue;
+ }
+ unset($callTimeCurValue);
+ continue;
+ }
+
+ // PRIORITY 2 -specific instance configuration (thisAlias) - this alias
+ // PRIORITY 3 -THEN specific instance configuration (thisClass) - this class
+ // PRIORITY 4 -THEN specific instance configuration (requestedAlias) - requested alias
+ // PRIORITY 5 -THEN specific instance configuration (requestedClass) - requested class
+
+ foreach (array('thisAlias', 'thisClass', 'requestedAlias', 'requestedClass') as $thisIndex) {
+ // check the provided parameters config
+ if (isset($iConfig[$thisIndex]['parameters'][$fqParamPos])
+ || isset($iConfig[$thisIndex]['parameters'][$fqParamName])
+ || isset($iConfig[$thisIndex]['parameters'][$name])) {
+ if (isset($iConfig[$thisIndex]['parameters'][$fqParamPos])) {
+ $iConfigCurValue =& $iConfig[$thisIndex]['parameters'][$fqParamPos];
+ } elseif (isset($iConfig[$thisIndex]['parameters'][$fqParamName])) {
+ $iConfigCurValue =& $iConfig[$thisIndex]['parameters'][$fqParamName];
+ } else {
+ $iConfigCurValue =& $iConfig[$thisIndex]['parameters'][$name];
+ }
+
+ if ($type === false && is_string($iConfigCurValue)) {
+ $computedParams['value'][$fqParamPos] = $iConfigCurValue;
+ } elseif (is_string($iConfigCurValue)
+ && isset($aliases[$iConfigCurValue])) {
+ $computedParams['retrieval'][$fqParamPos] = array(
+ $iConfig[$thisIndex]['parameters'][$name],
+ $this->instanceManager->getClassFromAlias($iConfigCurValue)
+ );
+ } elseif (is_string($iConfigCurValue)
+ && $this->definitions->hasClass($iConfigCurValue)) {
+ $computedParams['retrieval'][$fqParamPos] = array(
+ $iConfigCurValue,
+ $iConfigCurValue
+ );
+ } elseif (is_object($iConfigCurValue)
+ && $iConfigCurValue instanceof Closure
+ && $type !== 'Closure') {
+ /* @var $iConfigCurValue Closure */
+ $computedParams['value'][$fqParamPos] = $iConfigCurValue();
+ } else {
+ $computedParams['value'][$fqParamPos] = $iConfigCurValue;
+ }
+ unset($iConfigCurValue);
+ continue 2;
+ }
+ }
+
+ // PRIORITY 6 - globally preferred implementations
+
+ // next consult alias level preferred instances
+ // RESOLVE_EAGER wants to inject the cross-cutting concerns.
+ // If you want to retrieve an instance from TypePreferences,
+ // use AwareInterface or specify the method requirement option METHOD_IS_EAGER at ClassDefinition
+ if ($methodRequirementType & self::RESOLVE_EAGER) {
+ if ($alias && $this->instanceManager->hasTypePreferences($alias)) {
+ $pInstances = $this->instanceManager->getTypePreferences($alias);
+ foreach ($pInstances as $pInstance) {
+ if (is_object($pInstance)) {
+ $computedParams['value'][$fqParamPos] = $pInstance;
+ continue 2;
+ }
+ $pInstanceClass = ($this->instanceManager->hasAlias($pInstance)) ?
+ $this->instanceManager->getClassFromAlias($pInstance) : $pInstance;
+ if ($pInstanceClass === $type || self::isSubclassOf($pInstanceClass, $type)) {
+ $computedParams['retrieval'][$fqParamPos] = array($pInstance, $pInstanceClass);
+ continue 2;
+ }
+ }
+ }
+
+ // next consult class level preferred instances
+ if ($type && $this->instanceManager->hasTypePreferences($type)) {
+ $pInstances = $this->instanceManager->getTypePreferences($type);
+ foreach ($pInstances as $pInstance) {
+ if (is_object($pInstance)) {
+ $computedParams['value'][$fqParamPos] = $pInstance;
+ continue 2;
+ }
+ $pInstanceClass = ($this->instanceManager->hasAlias($pInstance)) ?
+ $this->instanceManager->getClassFromAlias($pInstance) : $pInstance;
+ if ($pInstanceClass === $type || self::isSubclassOf($pInstanceClass, $type)) {
+ $computedParams['retrieval'][$fqParamPos] = array($pInstance, $pInstanceClass);
+ continue 2;
+ }
+ }
+ }
+ }
+ if (!$isRequired) {
+ $computedParams['optional'][$fqParamPos] = true;
+ }
+
+ if ($type && $isRequired && ($methodRequirementType & self::RESOLVE_EAGER)) {
+ $computedParams['retrieval'][$fqParamPos] = array($type, $type);
+ }
+ }
+
+ $index = 0;
+ foreach ($injectionMethodParameters as $fqParamPos => $value) {
+ $name = $value[0];
+
+ if (isset($computedParams['value'][$fqParamPos])) {
+ // if there is a value supplied, use it
+ $resolvedParams[$index] = $computedParams['value'][$fqParamPos];
+ } elseif (isset($computedParams['retrieval'][$fqParamPos])) {
+ // detect circular dependencies! (they can only happen in instantiators)
+ if ($isInstantiator && in_array($computedParams['retrieval'][$fqParamPos][1], $this->currentDependencies)
+ && (!isset($alias) || in_array($computedParams['retrieval'][$fqParamPos][0], $this->currentAliasDependenencies))
+ ) {
+ $msg = "Circular dependency detected: $class depends on {$value[1]} and viceversa";
+ if (isset($alias)) {
+ $msg .= " (Aliased as $alias)";
+ }
+ throw new Exception\CircularDependencyException($msg);
+ }
+
+ array_push($this->currentDependencies, $class);
+ if (isset($alias)) {
+ array_push($this->currentAliasDependenencies, $alias);
+ }
+
+ $dConfig = $this->instanceManager->getConfig($computedParams['retrieval'][$fqParamPos][0]);
+
+ try {
+ if ($dConfig['shared'] === false) {
+ $resolvedParams[$index] = $this->newInstance($computedParams['retrieval'][$fqParamPos][0], $callTimeUserParams, false);
+ } else {
+ $resolvedParams[$index] = $this->get($computedParams['retrieval'][$fqParamPos][0], $callTimeUserParams);
+ }
+ } catch (DiRuntimeException $e) {
+ if ($methodRequirementType & self::RESOLVE_STRICT) {
+ //finally ( be aware to do at the end of flow)
+ array_pop($this->currentDependencies);
+ if (isset($alias)) {
+ array_pop($this->currentAliasDependenencies);
+ }
+ // if this item was marked strict,
+ // plus it cannot be resolve, and no value exist, bail out
+ throw new Exception\MissingPropertyException(sprintf(
+ 'Missing %s for parameter ' . $name . ' for ' . $class . '::' . $method,
+ (($value[0] === null) ? 'value' : 'instance/object' )
+ ),
+ $e->getCode(),
+ $e);
+ } else {
+ //finally ( be aware to do at the end of flow)
+ array_pop($this->currentDependencies);
+ if (isset($alias)) {
+ array_pop($this->currentAliasDependenencies);
+ }
+ return false;
+ }
+ } catch (ServiceManagerException $e) {
+ // Zend\ServiceManager\Exception\ServiceNotCreatedException
+ if ($methodRequirementType & self::RESOLVE_STRICT) {
+ //finally ( be aware to do at the end of flow)
+ array_pop($this->currentDependencies);
+ if (isset($alias)) {
+ array_pop($this->currentAliasDependenencies);
+ }
+ // if this item was marked strict,
+ // plus it cannot be resolve, and no value exist, bail out
+ throw new Exception\MissingPropertyException(sprintf(
+ 'Missing %s for parameter ' . $name . ' for ' . $class . '::' . $method,
+ (($value[0] === null) ? 'value' : 'instance/object' )
+ ),
+ $e->getCode(),
+ $e);
+ } else {
+ //finally ( be aware to do at the end of flow)
+ array_pop($this->currentDependencies);
+ if (isset($alias)) {
+ array_pop($this->currentAliasDependenencies);
+ }
+ return false;
+ }
+ }
+ array_pop($this->currentDependencies);
+ if (isset($alias)) {
+ array_pop($this->currentAliasDependenencies);
+ }
+ } elseif (!array_key_exists($fqParamPos, $computedParams['optional'])) {
+ if ($methodRequirementType & self::RESOLVE_STRICT) {
+ // if this item was not marked as optional,
+ // plus it cannot be resolve, and no value exist, bail out
+ throw new Exception\MissingPropertyException(sprintf(
+ 'Missing %s for parameter ' . $name . ' for ' . $class . '::' . $method,
+ (($value[0] === null) ? 'value' : 'instance/object' )
+ ));
+ } else {
+ return false;
+ }
+ } else {
+ $resolvedParams[$index] = $value[3];
+ }
+
+ $index++;
+ }
+
+ return $resolvedParams; // return ordered list of parameters
+ }
+
+ /**
+ * Checks if the object has this class as one of its parents
+ *
+ * @see https://bugs.php.net/bug.php?id=53727
+ * @see https://github.com/zendframework/zf2/pull/1807
+ *
+ * @param string $className
+ * @param $type
+ * @return bool
+ */
+ protected static function isSubclassOf($className, $type)
+ {
+ if (is_subclass_of($className, $type)) {
+ return true;
+ }
+ if (PHP_VERSION_ID >= 50307) {
+ return false;
+ }
+ if (!interface_exists($type)) {
+ return false;
+ }
+ $r = new ReflectionClass($className);
+
+ return $r->implementsInterface($type);
+ }
+}
diff --git a/library/Zend/Di/Display/Console.php b/library/Zend/Di/Display/Console.php
new file mode 100755
index 0000000000..31b861e638
--- /dev/null
+++ b/library/Zend/Di/Display/Console.php
@@ -0,0 +1,176 @@
+addRuntimeClasses($runtimeClasses);
+ $console->render($di);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param null|Di $di
+ */
+ public function __construct(Di $di = null)
+ {
+ $this->di = ($di) ?: new Di;
+ }
+
+ /**
+ * @param string[] $runtimeClasses
+ */
+ public function addRuntimeClasses(array $runtimeClasses)
+ {
+ foreach ($runtimeClasses as $runtimeClass) {
+ $this->addRuntimeClass($runtimeClass);
+ }
+ }
+
+ /**
+ * @param string $runtimeClass
+ */
+ public function addRuntimeClass($runtimeClass)
+ {
+ $this->runtimeClasses[] = $runtimeClass;
+ }
+
+ public function render()
+ {
+ $knownClasses = array();
+
+ echo 'Definitions' . PHP_EOL . PHP_EOL;
+
+ foreach ($this->di->definitions() as $definition) {
+ $this->renderDefinition($definition);
+ foreach ($definition->getClasses() as $class) {
+ $knownClasses[] = $class;
+ $this->renderClassDefinition($definition, $class);
+ }
+ if (count($definition->getClasses()) == 0) {
+ echo PHP_EOL .' No Classes Found' . PHP_EOL . PHP_EOL;
+ }
+ }
+
+ if ($this->runtimeClasses) {
+ echo ' Runtime classes:' . PHP_EOL;
+ }
+
+ $unknownRuntimeClasses = array_diff($this->runtimeClasses, $knownClasses);
+ foreach ($unknownRuntimeClasses as $runtimeClass) {
+ $definition = $this->di->definitions()->getDefinitionForClass($runtimeClass);
+ $this->renderClassDefinition($definition, $runtimeClass);
+ }
+
+ echo PHP_EOL . 'Instance Configuration Info:' . PHP_EOL;
+
+ echo PHP_EOL . ' Aliases:' . PHP_EOL;
+
+ $configuredTypes = array();
+ foreach ($this->di->instanceManager()->getAliases() as $alias => $class) {
+ echo ' ' . $alias . ' [type: ' . $class . ']' . PHP_EOL;
+ $configuredTypes[] = $alias;
+ }
+
+ echo PHP_EOL . ' Classes:' . PHP_EOL;
+
+ foreach ($this->di->instanceManager()->getClasses() as $class) {
+ echo ' ' . $class . PHP_EOL;
+ $configuredTypes[] = $class;
+ }
+
+ echo PHP_EOL . ' Configurations:' . PHP_EOL;
+
+ foreach ($configuredTypes as $type) {
+ $info = $this->di->instanceManager()->getConfig($type);
+ echo ' ' . $type . PHP_EOL;
+
+ if ($info['parameters']) {
+ echo ' parameters:' . PHP_EOL;
+ foreach ($info['parameters'] as $param => $value) {
+ echo ' ' . $param . ' = ' . $value . PHP_EOL;
+ }
+ }
+
+ if ($info['injections']) {
+ echo ' injections:' . PHP_EOL;
+ foreach ($info['injections'] as $injection => $value) {
+ var_dump($injection, $value);
+ }
+ }
+ }
+ }
+
+ /**
+ * @param object $definition
+ */
+ protected function renderDefinition($definition)
+ {
+ echo ' Definition Type: ' . get_class($definition) . PHP_EOL;
+ $r = new \ReflectionClass($definition);
+ foreach ($r->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $property) {
+ $property->setAccessible(true);
+ echo ' internal property: ' . $property->getName();
+ $value = $property->getValue($definition);
+ if (is_object($value)) {
+ echo ' instance of ' . get_class($value);
+ } else {
+ echo ' = ' . $value;
+ }
+ echo PHP_EOL;
+ }
+ }
+
+ /**
+ * @param \Zend\Di\Definition\DefinitionInterface $definition
+ * @param string $class
+ */
+ protected function renderClassDefinition($definition, $class)
+ {
+ echo PHP_EOL . ' Parameters For Class: ' . $class . PHP_EOL;
+ foreach ($definition->getMethods($class) as $methodName => $methodIsRequired) {
+ foreach ($definition->getMethodParameters($class, $methodName) as $fqName => $pData) {
+ echo ' ' . $pData[0] . ' [type: ';
+ echo ($pData[1]) ? $pData[1] : 'scalar';
+ echo ($pData[2] === true && $methodIsRequired) ? ', required' : ', not required';
+ echo ', injection-method: ' . $methodName;
+ echo ' fq-name: ' . $fqName;
+ echo ']' . PHP_EOL;
+ }
+ }
+ echo PHP_EOL;
+ }
+}
diff --git a/library/Zend/Di/Exception/CircularDependencyException.php b/library/Zend/Di/Exception/CircularDependencyException.php
new file mode 100755
index 0000000000..8cb9eae3f6
--- /dev/null
+++ b/library/Zend/Di/Exception/CircularDependencyException.php
@@ -0,0 +1,16 @@
+ array(), 'hashLong' => array());
+
+ /**
+ * Array of class aliases
+ * @var array key: alias, value: class
+ */
+ protected $aliases = array();
+
+ /**
+ * The template to use for housing configuration information
+ * @var array
+ */
+ protected $configurationTemplate = array(
+ /**
+ * alias|class => alias|class
+ * interface|abstract => alias|class|object
+ * name => value
+ */
+ 'parameters' => array(),
+ /**
+ * injection type => array of ordered method params
+ */
+ 'injections' => array(),
+ /**
+ * alias|class => bool
+ */
+ 'shared' => true
+ );
+
+ /**
+ * An array of instance configuration data
+ * @var array
+ */
+ protected $configurations = array();
+
+ /**
+ * An array of globally preferred implementations for interfaces/abstracts
+ * @var array
+ */
+ protected $typePreferences = array();
+
+ /**
+ * Does this instance manager have this shared instance
+ * @param string $classOrAlias
+ * @return bool
+ */
+ public function hasSharedInstance($classOrAlias)
+ {
+ return isset($this->sharedInstances[$classOrAlias]);
+ }
+
+ /**
+ * getSharedInstance()
+ */
+ public function getSharedInstance($classOrAlias)
+ {
+ return $this->sharedInstances[$classOrAlias];
+ }
+
+ /**
+ * Add shared instance
+ *
+ * @param object $instance
+ * @param string $classOrAlias
+ * @throws Exception\InvalidArgumentException
+ */
+ public function addSharedInstance($instance, $classOrAlias)
+ {
+ if (!is_object($instance)) {
+ throw new Exception\InvalidArgumentException('This method requires an object to be shared. Class or Alias given: ' . $classOrAlias);
+ }
+
+ $this->sharedInstances[$classOrAlias] = $instance;
+ }
+
+ /**
+ * hasSharedInstanceWithParameters()
+ *
+ * @param string $classOrAlias
+ * @param array $params
+ * @param bool $returnFastHashLookupKey
+ * @return bool|string
+ */
+ public function hasSharedInstanceWithParameters($classOrAlias, array $params, $returnFastHashLookupKey = false)
+ {
+ ksort($params);
+ $hashKey = $this->createHashForKeys($classOrAlias, array_keys($params));
+ if (isset($this->sharedInstancesWithParams['hashShort'][$hashKey])) {
+ $hashValue = $this->createHashForValues($classOrAlias, $params);
+ if (isset($this->sharedInstancesWithParams['hashLong'][$hashKey . '/' . $hashValue])) {
+ return ($returnFastHashLookupKey) ? $hashKey . '/' . $hashValue : true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * addSharedInstanceWithParameters()
+ *
+ * @param object $instance
+ * @param string $classOrAlias
+ * @param array $params
+ * @return void
+ */
+ public function addSharedInstanceWithParameters($instance, $classOrAlias, array $params)
+ {
+ ksort($params);
+ $hashKey = $this->createHashForKeys($classOrAlias, array_keys($params));
+ $hashValue = $this->createHashForValues($classOrAlias, $params);
+
+ if (!isset($this->sharedInstancesWithParams[$hashKey])
+ || !is_array($this->sharedInstancesWithParams[$hashKey])) {
+ $this->sharedInstancesWithParams[$hashKey] = array();
+ }
+
+ $this->sharedInstancesWithParams['hashShort'][$hashKey] = true;
+ $this->sharedInstancesWithParams['hashLong'][$hashKey . '/' . $hashValue] = $instance;
+ }
+
+ /**
+ * Retrieves an instance by its name and the parameters stored at its instantiation
+ *
+ * @param string $classOrAlias
+ * @param array $params
+ * @param bool|null $fastHashFromHasLookup
+ * @return object|bool false if no instance was found
+ */
+ public function getSharedInstanceWithParameters($classOrAlias, array $params, $fastHashFromHasLookup = null)
+ {
+ if ($fastHashFromHasLookup) {
+ return $this->sharedInstancesWithParams['hashLong'][$fastHashFromHasLookup];
+ }
+
+ ksort($params);
+ $hashKey = $this->createHashForKeys($classOrAlias, array_keys($params));
+ if (isset($this->sharedInstancesWithParams['hashShort'][$hashKey])) {
+ $hashValue = $this->createHashForValues($classOrAlias, $params);
+ if (isset($this->sharedInstancesWithParams['hashLong'][$hashKey . '/' . $hashValue])) {
+ return $this->sharedInstancesWithParams['hashLong'][$hashKey . '/' . $hashValue];
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Check for an alias
+ *
+ * @param string $alias
+ * @return bool
+ */
+ public function hasAlias($alias)
+ {
+ return (isset($this->aliases[$alias]));
+ }
+
+ /**
+ * Get aliases
+ *
+ * @return array
+ */
+ public function getAliases()
+ {
+ return $this->aliases;
+ }
+
+ /**
+ * getClassFromAlias()
+ *
+ * @param string
+ * @return string|bool
+ * @throws Exception\RuntimeException
+ */
+ public function getClassFromAlias($alias)
+ {
+ if (!isset($this->aliases[$alias])) {
+ return false;
+ }
+ $r = 0;
+ while (isset($this->aliases[$alias])) {
+ $alias = $this->aliases[$alias];
+ $r++;
+ if ($r > 100) {
+ throw new Exception\RuntimeException(
+ sprintf('Possible infinite recursion in DI alias! Max recursion of 100 levels reached at alias "%s".', $alias)
+ );
+ }
+ }
+
+ return $alias;
+ }
+
+ /**
+ * @param string $alias
+ * @return string|bool
+ * @throws Exception\RuntimeException
+ */
+ protected function getBaseAlias($alias)
+ {
+ if (!$this->hasAlias($alias)) {
+ return false;
+ }
+ $lastAlias = false;
+ $r = 0;
+ while (isset($this->aliases[$alias])) {
+ $lastAlias = $alias;
+ $alias = $this->aliases[$alias];
+ $r++;
+ if ($r > 100) {
+ throw new Exception\RuntimeException(
+ sprintf('Possible infinite recursion in DI alias! Max recursion of 100 levels reached at alias "%s".', $alias)
+ );
+ }
+ }
+
+ return $lastAlias;
+ }
+
+ /**
+ * Add alias
+ *
+ * @throws Exception\InvalidArgumentException
+ * @param string $alias
+ * @param string $class
+ * @param array $parameters
+ * @return void
+ */
+ public function addAlias($alias, $class, array $parameters = array())
+ {
+ if (!preg_match('#^[a-zA-Z0-9-_]+$#', $alias)) {
+ throw new Exception\InvalidArgumentException(
+ 'Aliases must be alphanumeric and can contain dashes and underscores only.'
+ );
+ }
+ $this->aliases[$alias] = $class;
+ if ($parameters) {
+ $this->setParameters($alias, $parameters);
+ }
+ }
+
+ /**
+ * Check for configuration
+ *
+ * @param string $aliasOrClass
+ * @return bool
+ */
+ public function hasConfig($aliasOrClass)
+ {
+ $key = ($this->hasAlias($aliasOrClass)) ? 'alias:' . $this->getBaseAlias($aliasOrClass) : $aliasOrClass;
+ if (!isset($this->configurations[$key])) {
+ return false;
+ }
+ if ($this->configurations[$key] === $this->configurationTemplate) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Sets configuration for a single alias/class
+ *
+ * @param string $aliasOrClass
+ * @param array $configuration
+ * @param bool $append
+ */
+ public function setConfig($aliasOrClass, array $configuration, $append = false)
+ {
+ $key = ($this->hasAlias($aliasOrClass)) ? 'alias:' . $this->getBaseAlias($aliasOrClass) : $aliasOrClass;
+ if (!isset($this->configurations[$key]) || !$append) {
+ $this->configurations[$key] = $this->configurationTemplate;
+ }
+ // Ignore anything but 'parameters' and 'injections'
+ $configuration = array(
+ 'parameters' => isset($configuration['parameters']) ? $configuration['parameters'] : array(),
+ 'injections' => isset($configuration['injections']) ? $configuration['injections'] : array(),
+ 'shared' => isset($configuration['shared']) ? $configuration['shared'] : true
+ );
+ $this->configurations[$key] = array_replace_recursive($this->configurations[$key], $configuration);
+ }
+
+ /**
+ * Get classes
+ *
+ * @return array
+ */
+ public function getClasses()
+ {
+ $classes = array();
+ foreach ($this->configurations as $name => $data) {
+ if (strpos($name, 'alias') === 0) {
+ continue;
+ }
+ $classes[] = $name;
+ }
+
+ return $classes;
+ }
+
+ /**
+ * @param string $aliasOrClass
+ * @return array
+ */
+ public function getConfig($aliasOrClass)
+ {
+ $key = ($this->hasAlias($aliasOrClass)) ? 'alias:' . $this->getBaseAlias($aliasOrClass) : $aliasOrClass;
+ if (isset($this->configurations[$key])) {
+ return $this->configurations[$key];
+ }
+
+ return $this->configurationTemplate;
+ }
+
+ /**
+ * setParameters() is a convenience method for:
+ * setConfig($type, array('parameters' => array(...)), true);
+ *
+ * @param string $aliasOrClass Alias or Class
+ * @param array $parameters Multi-dim array of parameters and their values
+ * @return void
+ */
+ public function setParameters($aliasOrClass, array $parameters)
+ {
+ $this->setConfig($aliasOrClass, array('parameters' => $parameters), true);
+ }
+
+ /**
+ * setInjections() is a convenience method for:
+ * setConfig($type, array('injections' => array(...)), true);
+ *
+ * @param string $aliasOrClass Alias or Class
+ * @param array $injections Multi-dim array of methods and their parameters
+ * @return void
+ */
+ public function setInjections($aliasOrClass, array $injections)
+ {
+ $this->setConfig($aliasOrClass, array('injections' => $injections), true);
+ }
+
+ /**
+ * Set shared
+ *
+ * @param string $aliasOrClass
+ * @param bool $isShared
+ * @return void
+ */
+ public function setShared($aliasOrClass, $isShared)
+ {
+ $this->setConfig($aliasOrClass, array('shared' => (bool) $isShared), true);
+ }
+
+ /**
+ * Check for type preferences
+ *
+ * @param string $interfaceOrAbstract
+ * @return bool
+ */
+ public function hasTypePreferences($interfaceOrAbstract)
+ {
+ $key = ($this->hasAlias($interfaceOrAbstract)) ? 'alias:' . $interfaceOrAbstract : $interfaceOrAbstract;
+
+ return (isset($this->typePreferences[$key]) && $this->typePreferences[$key]);
+ }
+
+ /**
+ * Set type preference
+ *
+ * @param string $interfaceOrAbstract
+ * @param array $preferredImplementations
+ * @return InstanceManager
+ */
+ public function setTypePreference($interfaceOrAbstract, array $preferredImplementations)
+ {
+ $key = ($this->hasAlias($interfaceOrAbstract)) ? 'alias:' . $interfaceOrAbstract : $interfaceOrAbstract;
+ foreach ($preferredImplementations as $preferredImplementation) {
+ $this->addTypePreference($key, $preferredImplementation);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get type preferences
+ *
+ * @param string $interfaceOrAbstract
+ * @return array
+ */
+ public function getTypePreferences($interfaceOrAbstract)
+ {
+ $key = ($this->hasAlias($interfaceOrAbstract)) ? 'alias:' . $interfaceOrAbstract : $interfaceOrAbstract;
+ if (isset($this->typePreferences[$key])) {
+ return $this->typePreferences[$key];
+ }
+
+ return array();
+ }
+
+ /**
+ * Unset type preferences
+ *
+ * @param string $interfaceOrAbstract
+ * @return void
+ */
+ public function unsetTypePreferences($interfaceOrAbstract)
+ {
+ $key = ($this->hasAlias($interfaceOrAbstract)) ? 'alias:' . $interfaceOrAbstract : $interfaceOrAbstract;
+ unset($this->typePreferences[$key]);
+ }
+
+ /**
+ * Adds a type preference. A type preference is a redirection to a preferred alias or type when an abstract type
+ * $interfaceOrAbstract is requested
+ *
+ * @param string $interfaceOrAbstract
+ * @param string $preferredImplementation
+ * @return self
+ */
+ public function addTypePreference($interfaceOrAbstract, $preferredImplementation)
+ {
+ $key = ($this->hasAlias($interfaceOrAbstract)) ? 'alias:' . $interfaceOrAbstract : $interfaceOrAbstract;
+ if (!isset($this->typePreferences[$key])) {
+ $this->typePreferences[$key] = array();
+ }
+ $this->typePreferences[$key][] = $preferredImplementation;
+
+ return $this;
+ }
+
+ /**
+ * Removes a previously set type preference
+ *
+ * @param string $interfaceOrAbstract
+ * @param string $preferredType
+ * @return bool|self
+ */
+ public function removeTypePreference($interfaceOrAbstract, $preferredType)
+ {
+ $key = ($this->hasAlias($interfaceOrAbstract)) ? 'alias:' . $interfaceOrAbstract : $interfaceOrAbstract;
+ if (!isset($this->typePreferences[$key]) || !in_array($preferredType, $this->typePreferences[$key])) {
+ return false;
+ }
+ unset($this->typePreferences[$key][array_search($key, $this->typePreferences)]);
+
+ return $this;
+ }
+
+ /**
+ * @param string $classOrAlias
+ * @param string[] $paramKeys
+ * @return string
+ */
+ protected function createHashForKeys($classOrAlias, $paramKeys)
+ {
+ return $classOrAlias . ':' . implode('|', $paramKeys);
+ }
+
+ /**
+ * @param string $classOrAlias
+ * @param array $paramValues
+ * @return string
+ */
+ protected function createHashForValues($classOrAlias, $paramValues)
+ {
+ $hashValue = '';
+ foreach ($paramValues as $param) {
+ switch (gettype($param)) {
+ case 'object':
+ $hashValue .= spl_object_hash($param) . '|';
+ break;
+ case 'integer':
+ case 'string':
+ case 'boolean':
+ case 'NULL':
+ case 'double':
+ $hashValue .= $param . '|';
+ break;
+ case 'array':
+ $hashValue .= 'Array|';
+ break;
+ case 'resource':
+ $hashValue .= 'resource|';
+ break;
+ }
+ }
+
+ return $hashValue;
+ }
+}
diff --git a/library/Zend/Di/LocatorInterface.php b/library/Zend/Di/LocatorInterface.php
new file mode 100755
index 0000000000..88a12f6cca
--- /dev/null
+++ b/library/Zend/Di/LocatorInterface.php
@@ -0,0 +1,22 @@
+
+ * protected $map = array('foo' => 'getFoo');
+ *
+ *
+ * When encountered, the return value of that method will be used.
+ *
+ * Methods mapped in this way may expect a single, array argument, the
+ * $params passed to {@link get()}, if any.
+ *
+ * @var array
+ */
+ protected $map = array();
+
+ /**
+ * Registered services and cached values
+ *
+ * @var array
+ */
+ protected $services = array();
+
+ /**
+ * {@inheritDoc}
+ */
+ public function set($name, $service)
+ {
+ $this->services[$name] = $service;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve a registered service
+ *
+ * Tests first if a value is registered for the service, and, if so,
+ * returns it.
+ *
+ * If the value returned is a non-object callback or closure, the return
+ * value is retrieved, stored, and returned. Parameters passed to the method
+ * are passed to the callback, but only on the first retrieval.
+ *
+ * If the service requested matches a method in the method map, the return
+ * value of that method is returned. Parameters are passed to the matching
+ * method.
+ *
+ * @param string $name
+ * @param array $params
+ * @return mixed
+ */
+ public function get($name, array $params = array())
+ {
+ if (!isset($this->services[$name])) {
+ if (!isset($this->map[$name])) {
+ return null;
+ }
+ $method = $this->map[$name];
+
+ return $this->$method($params);
+ }
+
+ $service = $this->services[$name];
+ if ($service instanceof Closure
+ || (!is_object($service) && is_callable($service))
+ ) {
+ $this->services[$name] = $service = call_user_func_array($service, $params);
+ }
+
+ return $service;
+ }
+}
diff --git a/library/Zend/Di/ServiceLocator/DependencyInjectorProxy.php b/library/Zend/Di/ServiceLocator/DependencyInjectorProxy.php
new file mode 100755
index 0000000000..be0c3cb1d5
--- /dev/null
+++ b/library/Zend/Di/ServiceLocator/DependencyInjectorProxy.php
@@ -0,0 +1,168 @@
+di = $di;
+ $this->definitions = $di->definitions();
+ $this->instanceManager = $di->instanceManager();
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return GeneratorInstance
+ */
+ public function get($name, array $params = array())
+ {
+ return parent::get($name, $params);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return GeneratorInstance
+ */
+ public function newInstance($name, array $params = array(), $isShared = true)
+ {
+ $instance = parent::newInstance($name, $params, $isShared);
+
+ if ($instance instanceof GeneratorInstance) {
+ /* @var $instance GeneratorInstance */
+ $instance->setShared($isShared);
+
+ // When a callback is used, we don't know instance the class name.
+ // That's why we assume $name as the instance alias
+ if (null === $instance->getName()) {
+ $instance->setAlias($name);
+ }
+ }
+
+ return $instance;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return GeneratorInstance
+ */
+ public function createInstanceViaConstructor($class, $params, $alias = null)
+ {
+ $callParameters = array();
+
+ if ($this->di->definitions->hasMethod($class, '__construct')
+ && (count($this->di->definitions->getMethodParameters($class, '__construct')) > 0)
+ ) {
+ $callParameters = $this->resolveMethodParameters($class, '__construct', $params, $alias, true, true);
+ $callParameters = $callParameters ?: array();
+ }
+
+ return new GeneratorInstance($class, $alias, '__construct', $callParameters);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws \Zend\Di\Exception\InvalidCallbackException
+ * @return GeneratorInstance
+ */
+ public function createInstanceViaCallback($callback, $params, $alias)
+ {
+ if (is_string($callback)) {
+ $callback = explode('::', $callback);
+ }
+
+ if (!is_callable($callback)) {
+ throw new Exception\InvalidCallbackException('An invalid constructor callback was provided');
+ }
+
+ if (!is_array($callback) || is_object($callback[0])) {
+ throw new Exception\InvalidCallbackException(
+ 'For purposes of service locator generation, constructor callbacks must refer to static methods only'
+ );
+ }
+
+ $class = $callback[0];
+ $method = $callback[1];
+
+ $callParameters = array();
+ if ($this->di->definitions->hasMethod($class, $method)) {
+ $callParameters = $this->resolveMethodParameters($class, $method, $params, $alias, true, true);
+ }
+
+ $callParameters = $callParameters ?: array();
+
+ return new GeneratorInstance(null, $alias, $callback, $callParameters);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function handleInjectionMethodForObject($class, $method, $params, $alias, $isRequired)
+ {
+ return array(
+ 'method' => $method,
+ 'params' => $this->resolveMethodParameters($class, $method, $params, $alias, $isRequired),
+ );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function resolveAndCallInjectionMethodForInstance($instance, $method, $params, $alias, $methodIsRequired, $methodClass = null)
+ {
+ if (!$instance instanceof GeneratorInstance) {
+ return parent::resolveAndCallInjectionMethodForInstance($instance, $method, $params, $alias, $methodIsRequired, $methodClass);
+ }
+
+ /* @var $instance GeneratorInstance */
+ $methodClass = $instance->getClass();
+ $callParameters = $this->resolveMethodParameters($methodClass, $method, $params, $alias, $methodIsRequired);
+
+ if ($callParameters !== false) {
+ $instance->addMethod(array(
+ 'method' => $method,
+ 'params' => $callParameters,
+ ));
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getClass($instance)
+ {
+ if ($instance instanceof GeneratorInstance) {
+ /* @var $instance GeneratorInstance */
+
+ return $instance->getClass();
+ }
+
+ return parent::getClass($instance);
+ }
+}
diff --git a/library/Zend/Di/ServiceLocator/Generator.php b/library/Zend/Di/ServiceLocator/Generator.php
new file mode 100755
index 0000000000..82e8ca191e
--- /dev/null
+++ b/library/Zend/Di/ServiceLocator/Generator.php
@@ -0,0 +1,342 @@
+injector = new DependencyInjectorProxy($injector);
+ }
+
+ /**
+ * Set the class name for the generated service locator container
+ *
+ * @param string $name
+ * @return Generator
+ */
+ public function setContainerClass($name)
+ {
+ $this->containerClass = $name;
+
+ return $this;
+ }
+
+ /**
+ * Set the namespace to use for the generated class file
+ *
+ * @param string $namespace
+ * @return Generator
+ */
+ public function setNamespace($namespace)
+ {
+ $this->namespace = $namespace;
+
+ return $this;
+ }
+
+ /**
+ * Construct, configure, and return a PHP class file code generation object
+ *
+ * Creates a Zend\Code\Generator\FileGenerator object that has
+ * created the specified class and service locator methods.
+ *
+ * @param null|string $filename
+ * @throws \Zend\Di\Exception\RuntimeException
+ * @return FileGenerator
+ */
+ public function getCodeGenerator($filename = null)
+ {
+ $injector = $this->injector;
+ $im = $injector->instanceManager();
+ $indent = ' ';
+ $aliases = $this->reduceAliases($im->getAliases());
+ $caseStatements = array();
+ $getters = array();
+ $definitions = $injector->definitions();
+
+ $fetched = array_unique(array_merge($definitions->getClasses(), $im->getAliases()));
+
+ foreach ($fetched as $name) {
+ $getter = $this->normalizeAlias($name);
+ $meta = $injector->get($name);
+ $params = $meta->getParams();
+
+ // Build parameter list for instantiation
+ foreach ($params as $key => $param) {
+ if (null === $param || is_scalar($param) || is_array($param)) {
+ $string = var_export($param, 1);
+ if (strstr($string, '::__set_state(')) {
+ throw new Exception\RuntimeException('Arguments in definitions may not contain objects');
+ }
+ $params[$key] = $string;
+ } elseif ($param instanceof GeneratorInstance) {
+ /* @var $param GeneratorInstance */
+ $params[$key] = sprintf('$this->%s()', $this->normalizeAlias($param->getName()));
+ } else {
+ $message = sprintf('Unable to use object arguments when building containers. Encountered with "%s", parameter of type "%s"', $name, get_class($param));
+ throw new Exception\RuntimeException($message);
+ }
+ }
+
+ // Strip null arguments from the end of the params list
+ $reverseParams = array_reverse($params, true);
+ foreach ($reverseParams as $key => $param) {
+ if ('NULL' === $param) {
+ unset($params[$key]);
+ continue;
+ }
+ break;
+ }
+
+ // Create instantiation code
+ $constructor = $meta->getConstructor();
+ if ('__construct' != $constructor) {
+ // Constructor callback
+ $callback = var_export($constructor, 1);
+ if (strstr($callback, '::__set_state(')) {
+ throw new Exception\RuntimeException('Unable to build containers that use callbacks requiring object instances');
+ }
+ if (count($params)) {
+ $creation = sprintf('$object = call_user_func(%s, %s);', $callback, implode(', ', $params));
+ } else {
+ $creation = sprintf('$object = call_user_func(%s);', $callback);
+ }
+ } else {
+ // Normal instantiation
+ $className = '\\' . ltrim($name, '\\');
+ $creation = sprintf('$object = new %s(%s);', $className, implode(', ', $params));
+ }
+
+ // Create method call code
+ $methods = '';
+ foreach ($meta->getMethods() as $methodData) {
+ if (!isset($methodData['name']) && !isset($methodData['method'])) {
+ continue;
+ }
+ $methodName = isset($methodData['name']) ? $methodData['name'] : $methodData['method'];
+ $methodParams = $methodData['params'];
+
+ // Create method parameter representation
+ foreach ($methodParams as $key => $param) {
+ if (null === $param || is_scalar($param) || is_array($param)) {
+ $string = var_export($param, 1);
+ if (strstr($string, '::__set_state(')) {
+ throw new Exception\RuntimeException('Arguments in definitions may not contain objects');
+ }
+ $methodParams[$key] = $string;
+ } elseif ($param instanceof GeneratorInstance) {
+ $methodParams[$key] = sprintf('$this->%s()', $this->normalizeAlias($param->getName()));
+ } else {
+ $message = sprintf('Unable to use object arguments when generating method calls. Encountered with class "%s", method "%s", parameter of type "%s"', $name, $methodName, get_class($param));
+ throw new Exception\RuntimeException($message);
+ }
+ }
+
+ // Strip null arguments from the end of the params list
+ $reverseParams = array_reverse($methodParams, true);
+ foreach ($reverseParams as $key => $param) {
+ if ('NULL' === $param) {
+ unset($methodParams[$key]);
+ continue;
+ }
+ break;
+ }
+
+ $methods .= sprintf("\$object->%s(%s);\n", $methodName, implode(', ', $methodParams));
+ }
+
+ // Generate caching statement
+ $storage = '';
+ if ($im->hasSharedInstance($name, $params)) {
+ $storage = sprintf("\$this->services['%s'] = \$object;\n", $name);
+ }
+
+ // Start creating getter
+ $getterBody = '';
+
+ // Create fetch of stored service
+ if ($im->hasSharedInstance($name, $params)) {
+ $getterBody .= sprintf("if (isset(\$this->services['%s'])) {\n", $name);
+ $getterBody .= sprintf("%sreturn \$this->services['%s'];\n}\n\n", $indent, $name);
+ }
+
+ // Creation and method calls
+ $getterBody .= sprintf("%s\n", $creation);
+ $getterBody .= $methods;
+
+ // Stored service
+ $getterBody .= $storage;
+
+ // End getter body
+ $getterBody .= "return \$object;\n";
+
+ $getterDef = new MethodGenerator();
+ $getterDef->setName($getter);
+ $getterDef->setBody($getterBody);
+ $getters[] = $getterDef;
+
+ // Get cases for case statements
+ $cases = array($name);
+ if (isset($aliases[$name])) {
+ $cases = array_merge($aliases[$name], $cases);
+ }
+
+ // Build case statement and store
+ $statement = '';
+ foreach ($cases as $value) {
+ $statement .= sprintf("%scase '%s':\n", $indent, $value);
+ }
+ $statement .= sprintf("%sreturn \$this->%s();\n", str_repeat($indent, 2), $getter);
+
+ $caseStatements[] = $statement;
+ }
+
+ // Build switch statement
+ $switch = sprintf("switch (%s) {\n%s\n", '$name', implode("\n", $caseStatements));
+ $switch .= sprintf("%sdefault:\n%sreturn parent::get(%s, %s);\n", $indent, str_repeat($indent, 2), '$name', '$params');
+ $switch .= "}\n\n";
+
+ // Build get() method
+ $nameParam = new ParameterGenerator();
+ $nameParam->setName('name');
+ $paramsParam = new ParameterGenerator();
+ $paramsParam->setName('params')
+ ->setType('array')
+ ->setDefaultValue(array());
+
+ $get = new MethodGenerator();
+ $get->setName('get');
+ $get->setParameters(array(
+ $nameParam,
+ $paramsParam,
+ ));
+ $get->setBody($switch);
+
+ // Create getters for aliases
+ $aliasMethods = array();
+ foreach ($aliases as $class => $classAliases) {
+ foreach ($classAliases as $alias) {
+ $aliasMethods[] = $this->getCodeGenMethodFromAlias($alias, $class);
+ }
+ }
+
+ // Create class code generation object
+ $container = new ClassGenerator();
+ $container->setName($this->containerClass)
+ ->setExtendedClass('ServiceLocator')
+ ->addMethodFromGenerator($get)
+ ->addMethods($getters)
+ ->addMethods($aliasMethods);
+
+ // Create PHP file code generation object
+ $classFile = new FileGenerator();
+ $classFile->setUse('Zend\Di\ServiceLocator')
+ ->setClass($container);
+
+ if (null !== $this->namespace) {
+ $classFile->setNamespace($this->namespace);
+ }
+
+ if (null !== $filename) {
+ $classFile->setFilename($filename);
+ }
+
+ return $classFile;
+ }
+
+ /**
+ * Reduces aliases
+ *
+ * Takes alias list and reduces it to a 2-dimensional array of
+ * class names pointing to an array of aliases that resolve to
+ * it.
+ *
+ * @param array $aliasList
+ * @return array
+ */
+ protected function reduceAliases(array $aliasList)
+ {
+ $reduced = array();
+ $aliases = array_keys($aliasList);
+ foreach ($aliasList as $alias => $service) {
+ if (in_array($service, $aliases)) {
+ do {
+ $service = $aliasList[$service];
+ } while (in_array($service, $aliases));
+ }
+ if (!isset($reduced[$service])) {
+ $reduced[$service] = array();
+ }
+ $reduced[$service][] = $alias;
+ }
+
+ return $reduced;
+ }
+
+ /**
+ * Create a PhpMethod code generation object named after a given alias
+ *
+ * @param string $alias
+ * @param string $class Class to which alias refers
+ * @return MethodGenerator
+ */
+ protected function getCodeGenMethodFromAlias($alias, $class)
+ {
+ $alias = $this->normalizeAlias($alias);
+ $method = new MethodGenerator();
+ $method->setName($alias);
+ $method->setBody(sprintf('return $this->get(\'%s\');', $class));
+
+ return $method;
+ }
+
+ /**
+ * Normalize an alias to a getter method name
+ *
+ * @param string $alias
+ * @return string
+ */
+ protected function normalizeAlias($alias)
+ {
+ $normalized = preg_replace('/[^a-zA-Z0-9]/', ' ', $alias);
+ $normalized = 'get' . str_replace(' ', '', ucwords($normalized));
+
+ return $normalized;
+ }
+}
diff --git a/library/Zend/Di/ServiceLocator/GeneratorInstance.php b/library/Zend/Di/ServiceLocator/GeneratorInstance.php
new file mode 100755
index 0000000000..aeb5f93acb
--- /dev/null
+++ b/library/Zend/Di/ServiceLocator/GeneratorInstance.php
@@ -0,0 +1,196 @@
+class = $class;
+ $this->alias = $alias;
+ $this->constructor = $constructor;
+ $this->params = $params;
+ }
+
+ /**
+ * Retrieves the best available name for this instance (instance alias first then class name)
+ *
+ * @return string|null
+ */
+ public function getName()
+ {
+ return $this->alias ? $this->alias : $this->class;
+ }
+
+ /**
+ * Class of the instance. Null if class is unclear (such as when the instance is produced by a callback)
+ *
+ * @return string|null
+ */
+ public function getClass()
+ {
+ return $this->class;
+ }
+
+ /**
+ * Alias for the instance (if any)
+ *
+ * @return string|null
+ */
+ public function getAlias()
+ {
+ return $this->alias;
+ }
+
+ /**
+ * Set class name
+ *
+ * In the case of an instance created via a callback, we need to set the
+ * class name after creating the generator instance.
+ *
+ * @param string $class
+ * @return GeneratorInstance
+ */
+ public function setClass($class)
+ {
+ $this->class = $class;
+
+ return $this;
+ }
+
+ /**
+ * Set instance alias
+ *
+ * @param string $alias
+ * @return GeneratorInstance
+ */
+ public function setAlias($alias)
+ {
+ $this->alias = $alias;
+
+ return $this;
+ }
+
+ /**
+ * Get instantiator
+ *
+ * @return mixed constructor method name or callable responsible for generating instance
+ */
+ public function getConstructor()
+ {
+ return $this->constructor;
+ }
+
+ /**
+ * Parameters passed to the instantiator as an ordered list of parameters. Each parameter that refers to another
+ * instance fetched recursively is a GeneratorInstance itself
+ *
+ * @return array
+ */
+ public function getParams()
+ {
+ return $this->params;
+ }
+
+ /**
+ * Set methods
+ *
+ * @param array $methods
+ * @return GeneratorInstance
+ */
+ public function setMethods(array $methods)
+ {
+ $this->methods = $methods;
+
+ return $this;
+ }
+
+ /**
+ * Add a method called on the instance
+ *
+ * @param $method
+ * @return GeneratorInstance
+ */
+ public function addMethod($method)
+ {
+ $this->methods[] = $method;
+
+ return $this;
+ }
+
+ /**
+ * Retrieves a list of methods that are called on the instance in their call order. Each returned element has form
+ * array('method' => 'methodName', 'params' => array( ... ordered list of call parameters ... ), where every call
+ * parameter that is a recursively fetched instance is a GeneratorInstance itself
+ *
+ * @return array
+ */
+ public function getMethods()
+ {
+ return $this->methods;
+ }
+
+ /**
+ * @param bool $shared
+ */
+ public function setShared($shared)
+ {
+ $this->shared = (bool) $shared;
+ }
+
+ /**
+ * Retrieves whether the instance is shared or not
+ *
+ * @return bool
+ */
+ public function isShared()
+ {
+ return $this->shared;
+ }
+}
diff --git a/library/Zend/Di/ServiceLocatorInterface.php b/library/Zend/Di/ServiceLocatorInterface.php
new file mode 100755
index 0000000000..fe5e125726
--- /dev/null
+++ b/library/Zend/Di/ServiceLocatorInterface.php
@@ -0,0 +1,23 @@
+ array(
+ 'Zend\Foo\Bar' => array(
+ 'public' => true,
+ 'methods' => array(
+ '__construct' => array(
+ 'params' => array(
+ 'foo' => 'bar',
+ ),
+ 'class' => 'Some\Default\Class'
+ ),
+ 'setConfig' => array(
+ 'params' => array(
+ 'bar' => 'baz',
+ ),
+ ),
+ ),
+ ),
+ ),
+ )
+
+- Ability to pass configuration to a generated ServiceLocator
+
+- Skip optional arguments if not passed in configuration or part of definition
+ (current behavior is to raise an exception if *any* arguments are missing)
+
+- Scoped Containers:
+
+ Described here:
+ http://picocontainer.org/scopes.html
+
+ This is something that should be explored when we start using these containers
+ with ServiceLocators inside an application. While part of this has to do with
+ garbage collection in Java (something we need not worry with in PHP since there
+ is no persistent in-memory objects), the interesting use case would be having a
+ container cloned from another container that has more or less (a subset) of the
+ definitions available to the Container's newInstance() and get() facilities.
+
+- Better Strategy Management
+
+ Currently, the strategies for determining dependencies is hard coded into the
+ various definitions. Ideally, we'd be able to have configurable strategies
+ that the definitions can then utilize to do their job:
+
+ http://picocontainer.org/injection.html
+
+ We currently support constructor injection and setter injection (methods prefixed
+ by set[A-Z])
+
+- Annotation Parsing
+
+ Ideally, at some point, Zend\Code\Scanner will support Annotation parsing. When
+ this is possible, we'd like to be able to use @inject similar to
+ http://picocontainer.org/annotated-method-injection.html
+
+- SuperType Resolution
+
+ (partially done inside resolveMethodParameters with is_subtype_of())
+
+ If a class claims it needs a dependency of not an object, but a particular
+ interface, the ability to find an object that suits that dependency, either
+ through a concept called 'Preferred Objects' or via a 'Property'. The
+ following should be supported:
+
+ The compiler also needs to be aware of other definitions when looking up SuperTypes
+
+ $definition = new AggregateDefinition();
+ $definition->addDefinition('Zend\Controller\DiDefinition');
+
+ $compiler = new Compiler()
+ $compiler->addDefinition($definition);
+ $compiler->addCodeScanner(__DIR__ . 'My/Blog');
+ $array = $compiler->compile()
+ $definition->addDefinition(new ArrayDefinition($array));
+
+- Performance & Benchmarking
+
+ Zend\Code\Scanner- check memory usage, perhaps use gc_collect_cycles() to free memory,
+ we'll have to do this on large scan bases.
+
+ Benchmark compiler: reflection vs. code scanner
+
+
diff --git a/library/Zend/Di/composer.json b/library/Zend/Di/composer.json
new file mode 100755
index 0000000000..81f6cb7818
--- /dev/null
+++ b/library/Zend/Di/composer.json
@@ -0,0 +1,33 @@
+{
+ "name": "zendframework/zend-di",
+ "description": " ",
+ "license": "BSD-3-Clause",
+ "keywords": [
+ "zf2",
+ "di"
+ ],
+ "homepage": "https://github.com/zendframework/zf2",
+ "autoload": {
+ "psr-0": {
+ "Zend\\Di\\": ""
+ }
+ },
+ "target-dir": "Zend/Di",
+ "require": {
+ "php": ">=5.3.23",
+ "zendframework/zend-code": "self.version",
+ "zendframework/zend-stdlib": "self.version"
+ },
+ "require-dev": {
+ "zendframework/zend-servicemanager": "self.version"
+ },
+ "suggest": {
+ "zendframework/zend-servicemanager": "Zend\\ServiceManager component"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev",
+ "dev-develop": "2.4-dev"
+ }
+ }
+}
diff --git a/library/Zend/Dom/CONTRIBUTING.md b/library/Zend/Dom/CONTRIBUTING.md
new file mode 100755
index 0000000000..e77f5d2d5b
--- /dev/null
+++ b/library/Zend/Dom/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# CONTRIBUTING
+
+Please don't open pull requests against this repository, please use https://github.com/zendframework/zf2.
\ No newline at end of file
diff --git a/library/Zend/Dom/Css2Xpath.php b/library/Zend/Dom/Css2Xpath.php
new file mode 100755
index 0000000000..7aa7d506cc
--- /dev/null
+++ b/library/Zend/Dom/Css2Xpath.php
@@ -0,0 +1,33 @@
+errors = array(null);
+
+ set_error_handler(array($this, 'addError'), \E_WARNING);
+ $nodeList = $this->query($expression);
+ restore_error_handler();
+
+ $exception = array_pop($this->errors);
+ if ($exception) {
+ throw $exception;
+ }
+
+ return $nodeList;
+ }
+
+ /**
+ * Adds an error to the stack of errors
+ *
+ * @param int $errno
+ * @param string $errstr
+ * @param string $errfile
+ * @param int $errline
+ * @return void
+ */
+ public function addError($errno, $errstr = '', $errfile = '', $errline = 0)
+ {
+ $last_error = end($this->errors);
+ $this->errors[] = new ErrorException(
+ $errstr,
+ 0,
+ $errno,
+ $errfile,
+ $errline,
+ $last_error
+ );
+ }
+}
diff --git a/library/Zend/Dom/Document.php b/library/Zend/Dom/Document.php
new file mode 100755
index 0000000000..456e17f864
--- /dev/null
+++ b/library/Zend/Dom/Document.php
@@ -0,0 +1,310 @@
+setStringDocument($document, $type, $encoding);
+ }
+
+ /**
+ * Get raw set document
+ *
+ * @return string|null
+ */
+ public function getStringDocument()
+ {
+ return $this->stringDocument;
+ }
+
+ /**
+ * Set raw document
+ *
+ * @param string|null $document
+ * @param string|null $forcedType Type for the provided document (see constants)
+ * @param string|null $forcedEncoding Encoding for the provided document
+ * @return self
+ */
+ protected function setStringDocument($document, $forcedType = null, $forcedEncoding = null)
+ {
+ $type = static::DOC_HTML;
+ if (strstr($document, 'DTD XHTML')) {
+ $type = static::DOC_XHTML;
+ }
+
+ // Breaking XML declaration to make syntax highlighting work
+ if ('<' . '?xml' == substr(trim($document), 0, 5)) {
+ $type = static::DOC_XML;
+ if (preg_match('/]*xmlns="([^"]+)"[^>]*>/i', $document, $matches)) {
+ $this->xpathNamespaces[] = $matches[1];
+ $type = static::DOC_XHTML;
+ }
+ }
+
+ // Unsetting previously registered DOMDocument
+ $this->domDocument = null;
+ $this->stringDocument = !empty($document) ? $document : null;
+
+ $this->setType($forcedType ?: (!empty($document) ? $type : null));
+ $this->setEncoding($forcedEncoding);
+ $this->setErrors(array());
+
+ return $this;
+ }
+
+ /**
+ * Get raw document type
+ *
+ * @return string|null
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * Set raw document type
+ *
+ * @param string $type
+ * @return self
+ */
+ protected function setType($type)
+ {
+ $this->type = $type;
+
+ return $this;
+ }
+
+ /**
+ * Get DOMDocument generated from set raw document
+ *
+ * @return DOMDocument
+ * @throws Exception\RuntimeException If cannot get DOMDocument; no document registered
+ */
+ public function getDomDocument()
+ {
+ if (null === ($stringDocument = $this->getStringDocument())) {
+ throw new Exception\RuntimeException('Cannot get DOMDocument; no document registered');
+ }
+
+ if (null === $this->domDocument) {
+ $this->domDocument = $this->getDomDocumentFromString($stringDocument);
+ }
+
+ return $this->domDocument;
+ }
+
+ /**
+ * Set DOMDocument
+ *
+ * @param DOMDocument $domDocument
+ * @return self
+ */
+ protected function setDomDocument(DOMDocument $domDocument)
+ {
+ $this->domDocument = $domDocument;
+
+ return $this;
+ }
+
+ /**
+ * Get set document encoding
+ *
+ * @return string|null
+ */
+ public function getEncoding()
+ {
+ return $this->encoding;
+ }
+
+ /**
+ * Set raw document encoding for DOMDocument generation
+ *
+ * @param string|null $encoding
+ * @return self
+ */
+ public function setEncoding($encoding)
+ {
+ $this->encoding = $encoding;
+
+ return $this->encoding;
+ }
+
+ /**
+ * Get DOMDocument generation errors
+ *
+ * @return array
+ */
+ public function getErrors()
+ {
+ return $this->errors;
+ }
+
+ /**
+ * Set document errors from DOMDocument generation
+ *
+ * @param array $errors
+ * @return self
+ */
+ protected function setErrors($errors)
+ {
+ $this->errors = $errors;
+
+ return $this;
+ }
+
+ /**
+ * Get DOMDocument from set raw document
+ *
+ * @return DOMDocument
+ * @throws Exception\RuntimeException
+ */
+ protected function getDomDocumentFromString($stringDocument)
+ {
+ libxml_use_internal_errors(true);
+ libxml_disable_entity_loader(true);
+
+ $encoding = $this->getEncoding();
+ $domDoc = null === $encoding ? new DOMDocument('1.0') : new DOMDocument('1.0', $encoding);
+ $type = $this->getType();
+
+ switch ($type) {
+ case static::DOC_XML:
+ $success = $domDoc->loadXML($stringDocument);
+ foreach ($domDoc->childNodes as $child) {
+ if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
+ throw new Exception\RuntimeException(
+ 'Invalid XML: Detected use of illegal DOCTYPE'
+ );
+ }
+ }
+ break;
+ case static::DOC_HTML:
+ case static::DOC_XHTML:
+ default:
+ $success = $domDoc->loadHTML($stringDocument);
+ break;
+ }
+
+ $errors = libxml_get_errors();
+ if (!empty($errors)) {
+ $this->setErrors($errors);
+ libxml_clear_errors();
+ }
+
+ libxml_disable_entity_loader(false);
+ libxml_use_internal_errors(false);
+
+ if (!$success) {
+ throw new Exception\RuntimeException(sprintf('Error parsing document (type == %s)', $type));
+ }
+
+ return $domDoc;
+ }
+
+ /**
+ * Get Document's registered XPath namespaces
+ *
+ * @return array
+ */
+ public function getXpathNamespaces()
+ {
+ return $this->xpathNamespaces;
+ }
+
+ /**
+ * Register XPath namespaces
+ *
+ * @param array $xpathNamespaces
+ * @return void
+ */
+ public function registerXpathNamespaces($xpathNamespaces)
+ {
+ $this->xpathNamespaces = $xpathNamespaces;
+ }
+
+ /**
+ * Get Document's registered XPath PHP Functions
+ *
+ * @return string|null
+ */
+ public function getXpathPhpFunctions()
+ {
+ return $this->xpathPhpFunctions;
+ }
+ /**
+ * Register PHP Functions to use in internal DOMXPath
+ *
+ * @param bool $xpathPhpFunctions
+ * @return void
+ */
+ public function registerXpathPhpFunctions($xpathPhpFunctions = true)
+ {
+ $this->xpathPhpFunctions = $xpathPhpFunctions;
+ }
+}
diff --git a/library/Zend/Dom/Document/NodeList.php b/library/Zend/Dom/Document/NodeList.php
new file mode 100755
index 0000000000..352326fe27
--- /dev/null
+++ b/library/Zend/Dom/Document/NodeList.php
@@ -0,0 +1,160 @@
+list = $list;
+ }
+
+ /**
+ * Iterator: rewind to first element
+ *
+ * @return DOMNode
+ */
+ public function rewind()
+ {
+ $this->position = 0;
+
+ return $this->list->item(0);
+ }
+
+ /**
+ * Iterator: is current position valid?
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ if (in_array($this->position, range(0, $this->list->length - 1)) && $this->list->length > 0) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Iterator: return current element
+ *
+ * @return DOMNode
+ */
+ public function current()
+ {
+ return $this->list->item($this->position);
+ }
+
+ /**
+ * Iterator: return key of current element
+ *
+ * @return int
+ */
+ public function key()
+ {
+ return $this->position;
+ }
+
+ /**
+ * Iterator: move to next element
+ *
+ * @return DOMNode
+ */
+ public function next()
+ {
+ ++$this->position;
+
+ return $this->list->item($this->position);
+ }
+
+ /**
+ * Countable: get count
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return $this->list->length;
+ }
+
+ /**
+ * ArrayAccess: offset exists
+ *
+ * @param int $key
+ * @return bool
+ */
+ public function offsetExists($key)
+ {
+ if (in_array($key, range(0, $this->list->length - 1)) && $this->list->length > 0) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * ArrayAccess: get offset
+ *
+ * @param int $key
+ * @return mixed
+ */
+ public function offsetGet($key)
+ {
+ return $this->list->item($key);
+ }
+
+ /**
+ * ArrayAccess: set offset
+ *
+ * @param mixed $key
+ * @param mixed $value
+ * @throws Exception\BadMethodCallException when attempting to write to a read-only item
+ */
+ public function offsetSet($key, $value)
+ {
+ throw new Exception\BadMethodCallException('Attempting to write to a read-only list');
+ }
+
+ /**
+ * ArrayAccess: unset offset
+ *
+ * @param mixed $key
+ * @throws Exception\BadMethodCallException when attempting to unset a read-only item
+ */
+ public function offsetUnset($key)
+ {
+ throw new Exception\BadMethodCallException('Attempting to unset on a read-only list');
+ }
+}
diff --git a/library/Zend/Dom/Document/Query.php b/library/Zend/Dom/Document/Query.php
new file mode 100755
index 0000000000..bea590328a
--- /dev/null
+++ b/library/Zend/Dom/Document/Query.php
@@ -0,0 +1,169 @@
+getDomDocument());
+
+ $xpathNamespaces = $document->getXpathNamespaces();
+ foreach ($xpathNamespaces as $prefix => $namespaceUri) {
+ $xpath->registerNamespace($prefix, $namespaceUri);
+ }
+
+ if ($xpathPhpfunctions = $document->getXpathPhpFunctions()) {
+ $xpath->registerNamespace('php', 'http://php.net/xpath');
+ ($xpathPhpfunctions === true) ? $xpath->registerPHPFunctions() : $xpath->registerPHPFunctions($xpathPhpfunctions);
+ }
+
+ $nodeList = $xpath->queryWithErrorException($expression);
+ return new NodeList($nodeList);
+ }
+
+ /**
+ * Transform CSS expression to XPath
+ *
+ * @param string $path
+ * @return string
+ */
+ public static function cssToXpath($path)
+ {
+ $path = (string) $path;
+ if (strstr($path, ',')) {
+ $paths = explode(',', $path);
+ $expressions = array();
+ foreach ($paths as $path) {
+ $xpath = static::cssToXpath(trim($path));
+ if (is_string($xpath)) {
+ $expressions[] = $xpath;
+ } elseif (is_array($xpath)) {
+ $expressions = array_merge($expressions, $xpath);
+ }
+ }
+ return implode('|', $expressions);
+ }
+
+ $paths = array('//');
+ $path = preg_replace('|\s+>\s+|', '>', $path);
+ $segments = preg_split('/\s+/', $path);
+ foreach ($segments as $key => $segment) {
+ $pathSegment = static::_tokenize($segment);
+ if (0 == $key) {
+ if (0 === strpos($pathSegment, '[contains(')) {
+ $paths[0] .= '*' . ltrim($pathSegment, '*');
+ } else {
+ $paths[0] .= $pathSegment;
+ }
+ continue;
+ }
+ if (0 === strpos($pathSegment, '[contains(')) {
+ foreach ($paths as $pathKey => $xpath) {
+ $paths[$pathKey] .= '//*' . ltrim($pathSegment, '*');
+ $paths[] = $xpath . $pathSegment;
+ }
+ } else {
+ foreach ($paths as $pathKey => $xpath) {
+ $paths[$pathKey] .= '//' . $pathSegment;
+ }
+ }
+ }
+
+ if (1 == count($paths)) {
+ return $paths[0];
+ }
+ return implode('|', $paths);
+ }
+
+ /**
+ * Tokenize CSS expressions to XPath
+ *
+ * @param string $expression
+ * @return string
+ */
+ protected static function _tokenize($expression)
+ {
+ // Child selectors
+ $expression = str_replace('>', '/', $expression);
+
+ // IDs
+ $expression = preg_replace('|#([a-z][a-z0-9_-]*)|i', '[@id=\'$1\']', $expression);
+ $expression = preg_replace('|(?cssQuery = $cssQuery;
+ $this->xpathQuery = $xpathQuery;
+ $this->document = $document;
+ $this->nodeList = $nodeList;
+ }
+
+ /**
+ * Retrieve CSS Query
+ *
+ * @return string
+ */
+ public function getCssQuery()
+ {
+ return $this->cssQuery;
+ }
+
+ /**
+ * Retrieve XPath query
+ *
+ * @return string
+ */
+ public function getXpathQuery()
+ {
+ return $this->xpathQuery;
+ }
+
+ /**
+ * Retrieve DOMDocument
+ *
+ * @return DOMDocument
+ */
+ public function getDocument()
+ {
+ return $this->document;
+ }
+
+ /**
+ * Iterator: rewind to first element
+ *
+ * @return DOMNode
+ */
+ public function rewind()
+ {
+ $this->position = 0;
+
+ return $this->nodeList->item(0);
+ }
+
+ /**
+ * Iterator: is current position valid?
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ if (in_array($this->position, range(0, $this->nodeList->length - 1)) && $this->nodeList->length > 0) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Iterator: return current element
+ *
+ * @return DOMNode
+ */
+ public function current()
+ {
+ return $this->nodeList->item($this->position);
+ }
+
+ /**
+ * Iterator: return key of current element
+ *
+ * @return int
+ */
+ public function key()
+ {
+ return $this->position;
+ }
+
+ /**
+ * Iterator: move to next element
+ *
+ * @return DOMNode
+ */
+ public function next()
+ {
+ ++$this->position;
+
+ return $this->nodeList->item($this->position);
+ }
+
+ /**
+ * Countable: get count
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return $this->nodeList->length;
+ }
+
+ /**
+ * ArrayAccess: offset exists
+ *
+ * @param int $key
+ * @return bool
+ */
+ public function offsetExists($key)
+ {
+ if (in_array($key, range(0, $this->nodeList->length - 1)) && $this->nodeList->length > 0) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * ArrayAccess: get offset
+ *
+ * @param int $key
+ * @return mixed
+ */
+ public function offsetGet($key)
+ {
+ return $this->nodeList->item($key);
+ }
+
+ /**
+ * ArrayAccess: set offset
+ *
+ * @param mixed $key
+ * @param mixed $value
+ * @throws Exception\BadMethodCallException when attempting to write to a read-only item
+ */
+ public function offsetSet($key, $value)
+ {
+ throw new Exception\BadMethodCallException('Attempting to write to a read-only list');
+ }
+
+ /**
+ * ArrayAccess: unset offset
+ *
+ * @param mixed $key
+ * @throws Exception\BadMethodCallException when attempting to unset a read-only item
+ */
+ public function offsetUnset($key)
+ {
+ throw new Exception\BadMethodCallException('Attempting to unset on a read-only list');
+ }
+}
diff --git a/library/Zend/Dom/Query.php b/library/Zend/Dom/Query.php
new file mode 100755
index 0000000000..5514299169
--- /dev/null
+++ b/library/Zend/Dom/Query.php
@@ -0,0 +1,320 @@
+setEncoding($encoding);
+ $this->setDocument($document);
+ }
+
+ /**
+ * Set document encoding
+ *
+ * @param string $encoding
+ * @return Query
+ */
+ public function setEncoding($encoding)
+ {
+ $this->encoding = (null === $encoding) ? null : (string) $encoding;
+ return $this;
+ }
+
+ /**
+ * Get document encoding
+ *
+ * @return null|string
+ */
+ public function getEncoding()
+ {
+ return $this->encoding;
+ }
+
+ /**
+ * Set document to query
+ *
+ * @param string $document
+ * @param null|string $encoding Document encoding
+ * @return Query
+ */
+ public function setDocument($document, $encoding = null)
+ {
+ if (0 === strlen($document)) {
+ return $this;
+ }
+ // breaking XML declaration to make syntax highlighting work
+ if ('<' . '?xml' == substr(trim($document), 0, 5)) {
+ if (preg_match('/]*xmlns="([^"]+)"[^>]*>/i', $document, $matches)) {
+ $this->xpathNamespaces[] = $matches[1];
+ return $this->setDocumentXhtml($document, $encoding);
+ }
+ return $this->setDocumentXml($document, $encoding);
+ }
+ if (strstr($document, 'DTD XHTML')) {
+ return $this->setDocumentXhtml($document, $encoding);
+ }
+ return $this->setDocumentHtml($document, $encoding);
+ }
+
+ /**
+ * Register HTML document
+ *
+ * @param string $document
+ * @param null|string $encoding Document encoding
+ * @return Query
+ */
+ public function setDocumentHtml($document, $encoding = null)
+ {
+ $this->document = (string) $document;
+ $this->docType = self::DOC_HTML;
+ if (null !== $encoding) {
+ $this->setEncoding($encoding);
+ }
+ return $this;
+ }
+
+ /**
+ * Register XHTML document
+ *
+ * @param string $document
+ * @param null|string $encoding Document encoding
+ * @return Query
+ */
+ public function setDocumentXhtml($document, $encoding = null)
+ {
+ $this->document = (string) $document;
+ $this->docType = self::DOC_XHTML;
+ if (null !== $encoding) {
+ $this->setEncoding($encoding);
+ }
+ return $this;
+ }
+
+ /**
+ * Register XML document
+ *
+ * @param string $document
+ * @param null|string $encoding Document encoding
+ * @return Query
+ */
+ public function setDocumentXml($document, $encoding = null)
+ {
+ $this->document = (string) $document;
+ $this->docType = self::DOC_XML;
+ if (null !== $encoding) {
+ $this->setEncoding($encoding);
+ }
+ return $this;
+ }
+
+ /**
+ * Retrieve current document
+ *
+ * @return string
+ */
+ public function getDocument()
+ {
+ return $this->document;
+ }
+
+ /**
+ * Get document type
+ *
+ * @return string
+ */
+ public function getDocumentType()
+ {
+ return $this->docType;
+ }
+
+ /**
+ * Get any DOMDocument errors found
+ *
+ * @return false|array
+ */
+ public function getDocumentErrors()
+ {
+ return $this->documentErrors;
+ }
+
+ /**
+ * Perform a CSS selector query
+ *
+ * @param string $query
+ * @return NodeList
+ */
+ public function execute($query)
+ {
+ $xpathQuery = Document\Query::cssToXpath($query);
+ return $this->queryXpath($xpathQuery, $query);
+ }
+
+ /**
+ * Perform an XPath query
+ *
+ * @param string|array $xpathQuery
+ * @param string|null $query CSS selector query
+ * @throws Exception\RuntimeException
+ * @return NodeList
+ */
+ public function queryXpath($xpathQuery, $query = null)
+ {
+ if (null === ($document = $this->getDocument())) {
+ throw new Exception\RuntimeException('Cannot query; no document registered');
+ }
+
+ $encoding = $this->getEncoding();
+ libxml_use_internal_errors(true);
+ libxml_disable_entity_loader(true);
+ if (null === $encoding) {
+ $domDoc = new DOMDocument('1.0');
+ } else {
+ $domDoc = new DOMDocument('1.0', $encoding);
+ }
+ $type = $this->getDocumentType();
+ switch ($type) {
+ case self::DOC_XML:
+ $success = $domDoc->loadXML($document);
+ foreach ($domDoc->childNodes as $child) {
+ if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
+ throw new Exception\RuntimeException(
+ 'Invalid XML: Detected use of illegal DOCTYPE'
+ );
+ }
+ }
+ break;
+ case self::DOC_HTML:
+ case self::DOC_XHTML:
+ default:
+ $success = $domDoc->loadHTML($document);
+ break;
+ }
+ $errors = libxml_get_errors();
+ if (!empty($errors)) {
+ $this->documentErrors = $errors;
+ libxml_clear_errors();
+ }
+ libxml_disable_entity_loader(false);
+ libxml_use_internal_errors(false);
+
+ if (!$success) {
+ throw new Exception\RuntimeException(sprintf('Error parsing document (type == %s)', $type));
+ }
+
+ $nodeList = $this->getNodeList($domDoc, $xpathQuery);
+ return new NodeList($query, $xpathQuery, $domDoc, $nodeList);
+ }
+
+ /**
+ * Register XPath namespaces
+ *
+ * @param array $xpathNamespaces
+ * @return void
+ */
+ public function registerXpathNamespaces($xpathNamespaces)
+ {
+ $this->xpathNamespaces = $xpathNamespaces;
+ }
+
+ /**
+ * Register PHP Functions to use in internal DOMXPath
+ *
+ * @param bool $xpathPhpFunctions
+ * @return void
+ */
+ public function registerXpathPhpFunctions($xpathPhpFunctions = true)
+ {
+ $this->xpathPhpFunctions = $xpathPhpFunctions;
+ }
+
+ /**
+ * Prepare node list
+ *
+ * @param DOMDocument $document
+ * @param string|array $xpathQuery
+ * @return array
+ * @throws \ErrorException If query cannot be executed
+ */
+ protected function getNodeList($document, $xpathQuery)
+ {
+ $xpath = new DOMXPath($document);
+ foreach ($this->xpathNamespaces as $prefix => $namespaceUri) {
+ $xpath->registerNamespace($prefix, $namespaceUri);
+ }
+ if ($this->xpathPhpFunctions) {
+ $xpath->registerNamespace("php", "http://php.net/xpath");
+ ($this->xpathPhpFunctions === true) ?
+ $xpath->registerPHPFunctions()
+ : $xpath->registerPHPFunctions($this->xpathPhpFunctions);
+ }
+ $xpathQuery = (string) $xpathQuery;
+
+ $nodeList = $xpath->queryWithErrorException($xpathQuery);
+ return $nodeList;
+ }
+}
diff --git a/library/Zend/Dom/README.md b/library/Zend/Dom/README.md
new file mode 100755
index 0000000000..b82c2b9ffc
--- /dev/null
+++ b/library/Zend/Dom/README.md
@@ -0,0 +1,15 @@
+DOM Component from ZF2
+======================
+
+This is the DOM component for ZF2.
+
+- File issues at https://github.com/zendframework/zf2/issues
+- Create pull requests against https://github.com/zendframework/zf2
+- Documentation is at http://framework.zend.com/docs
+
+LICENSE
+-------
+
+The files in this archive are released under the [Zend Framework
+license](http://framework.zend.com/license), which is a 3-clause BSD license.
+
diff --git a/library/Zend/Dom/composer.json b/library/Zend/Dom/composer.json
new file mode 100755
index 0000000000..94433c52af
--- /dev/null
+++ b/library/Zend/Dom/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "zendframework/zend-dom",
+ "description": "provides tools for working with DOM documents and structures",
+ "license": "BSD-3-Clause",
+ "keywords": [
+ "zf2",
+ "dom"
+ ],
+ "homepage": "https://github.com/zendframework/zf2",
+ "autoload": {
+ "psr-0": {
+ "Zend\\Dom\\": ""
+ }
+ },
+ "target-dir": "Zend/Dom",
+ "require": {
+ "php": ">=5.3.23"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev",
+ "dev-develop": "2.4-dev"
+ }
+ }
+}
diff --git a/library/Zend/Escaper/CONTRIBUTING.md b/library/Zend/Escaper/CONTRIBUTING.md
new file mode 100755
index 0000000000..e77f5d2d5b
--- /dev/null
+++ b/library/Zend/Escaper/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# CONTRIBUTING
+
+Please don't open pull requests against this repository, please use https://github.com/zendframework/zf2.
\ No newline at end of file
diff --git a/library/Zend/Escaper/Escaper.php b/library/Zend/Escaper/Escaper.php
new file mode 100755
index 0000000000..3a3d30eb6c
--- /dev/null
+++ b/library/Zend/Escaper/Escaper.php
@@ -0,0 +1,387 @@
+ 'quot', // quotation mark
+ 38 => 'amp', // ampersand
+ 60 => 'lt', // less-than sign
+ 62 => 'gt', // greater-than sign
+ );
+
+ /**
+ * Current encoding for escaping. If not UTF-8, we convert strings from this encoding
+ * pre-escaping and back to this encoding post-escaping.
+ *
+ * @var string
+ */
+ protected $encoding = 'utf-8';
+
+ /**
+ * Holds the value of the special flags passed as second parameter to
+ * htmlspecialchars(). We modify these for PHP 5.4 to take advantage
+ * of the new ENT_SUBSTITUTE flag for correctly dealing with invalid
+ * UTF-8 sequences.
+ *
+ * @var string
+ */
+ protected $htmlSpecialCharsFlags = ENT_QUOTES;
+
+ /**
+ * Static Matcher which escapes characters for HTML Attribute contexts
+ *
+ * @var callable
+ */
+ protected $htmlAttrMatcher;
+
+ /**
+ * Static Matcher which escapes characters for Javascript contexts
+ *
+ * @var callable
+ */
+ protected $jsMatcher;
+
+ /**
+ * Static Matcher which escapes characters for CSS Attribute contexts
+ *
+ * @var callable
+ */
+ protected $cssMatcher;
+
+ /**
+ * List of all encoding supported by this class
+ *
+ * @var array
+ */
+ protected $supportedEncodings = array(
+ 'iso-8859-1', 'iso8859-1', 'iso-8859-5', 'iso8859-5',
+ 'iso-8859-15', 'iso8859-15', 'utf-8', 'cp866',
+ 'ibm866', '866', 'cp1251', 'windows-1251',
+ 'win-1251', '1251', 'cp1252', 'windows-1252',
+ '1252', 'koi8-r', 'koi8-ru', 'koi8r',
+ 'big5', '950', 'gb2312', '936',
+ 'big5-hkscs', 'shift_jis', 'sjis', 'sjis-win',
+ 'cp932', '932', 'euc-jp', 'eucjp',
+ 'eucjp-win', 'macroman'
+ );
+
+ /**
+ * Constructor: Single parameter allows setting of global encoding for use by
+ * the current object. If PHP 5.4 is detected, additional ENT_SUBSTITUTE flag
+ * is set for htmlspecialchars() calls.
+ *
+ * @param string $encoding
+ * @throws Exception\InvalidArgumentException
+ */
+ public function __construct($encoding = null)
+ {
+ if ($encoding !== null) {
+ $encoding = (string) $encoding;
+ if ($encoding === '') {
+ throw new Exception\InvalidArgumentException(
+ get_class($this) . ' constructor parameter does not allow a blank value'
+ );
+ }
+
+ $encoding = strtolower($encoding);
+ if (!in_array($encoding, $this->supportedEncodings)) {
+ throw new Exception\InvalidArgumentException(
+ 'Value of \'' . $encoding . '\' passed to ' . get_class($this)
+ . ' constructor parameter is invalid. Provide an encoding supported by htmlspecialchars()'
+ );
+ }
+
+ $this->encoding = $encoding;
+ }
+
+ if (defined('ENT_SUBSTITUTE')) {
+ $this->htmlSpecialCharsFlags|= ENT_SUBSTITUTE;
+ }
+
+ // set matcher callbacks
+ $this->htmlAttrMatcher = array($this, 'htmlAttrMatcher');
+ $this->jsMatcher = array($this, 'jsMatcher');
+ $this->cssMatcher = array($this, 'cssMatcher');
+ }
+
+ /**
+ * Return the encoding that all output/input is expected to be encoded in.
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->encoding;
+ }
+
+ /**
+ * Escape a string for the HTML Body context where there are very few characters
+ * of special meaning. Internally this will use htmlspecialchars().
+ *
+ * @param string $string
+ * @return string
+ */
+ public function escapeHtml($string)
+ {
+ return htmlspecialchars($string, $this->htmlSpecialCharsFlags, $this->encoding);
+ }
+
+ /**
+ * Escape a string for the HTML Attribute context. We use an extended set of characters
+ * to escape that are not covered by htmlspecialchars() to cover cases where an attribute
+ * might be unquoted or quoted illegally (e.g. backticks are valid quotes for IE).
+ *
+ * @param string $string
+ * @return string
+ */
+ public function escapeHtmlAttr($string)
+ {
+ $string = $this->toUtf8($string);
+ if ($string === '' || ctype_digit($string)) {
+ return $string;
+ }
+
+ $result = preg_replace_callback('/[^a-z0-9,\.\-_]/iSu', $this->htmlAttrMatcher, $string);
+ return $this->fromUtf8($result);
+ }
+
+ /**
+ * Escape a string for the Javascript context. This does not use json_encode(). An extended
+ * set of characters are escaped beyond ECMAScript's rules for Javascript literal string
+ * escaping in order to prevent misinterpretation of Javascript as HTML leading to the
+ * injection of special characters and entities. The escaping used should be tolerant
+ * of cases where HTML escaping was not applied on top of Javascript escaping correctly.
+ * Backslash escaping is not used as it still leaves the escaped character as-is and so
+ * is not useful in a HTML context.
+ *
+ * @param string $string
+ * @return string
+ */
+ public function escapeJs($string)
+ {
+ $string = $this->toUtf8($string);
+ if ($string === '' || ctype_digit($string)) {
+ return $string;
+ }
+
+ $result = preg_replace_callback('/[^a-z0-9,\._]/iSu', $this->jsMatcher, $string);
+ return $this->fromUtf8($result);
+ }
+
+ /**
+ * Escape a string for the URI or Parameter contexts. This should not be used to escape
+ * an entire URI - only a subcomponent being inserted. The function is a simple proxy
+ * to rawurlencode() which now implements RFC 3986 since PHP 5.3 completely.
+ *
+ * @param string $string
+ * @return string
+ */
+ public function escapeUrl($string)
+ {
+ return rawurlencode($string);
+ }
+
+ /**
+ * Escape a string for the CSS context. CSS escaping can be applied to any string being
+ * inserted into CSS and escapes everything except alphanumerics.
+ *
+ * @param string $string
+ * @return string
+ */
+ public function escapeCss($string)
+ {
+ $string = $this->toUtf8($string);
+ if ($string === '' || ctype_digit($string)) {
+ return $string;
+ }
+
+ $result = preg_replace_callback('/[^a-z0-9]/iSu', $this->cssMatcher, $string);
+ return $this->fromUtf8($result);
+ }
+
+ /**
+ * Callback function for preg_replace_callback that applies HTML Attribute
+ * escaping to all matches.
+ *
+ * @param array $matches
+ * @return string
+ */
+ protected function htmlAttrMatcher($matches)
+ {
+ $chr = $matches[0];
+ $ord = ord($chr);
+
+ /**
+ * The following replaces characters undefined in HTML with the
+ * hex entity for the Unicode replacement character.
+ */
+ if (($ord <= 0x1f && $chr != "\t" && $chr != "\n" && $chr != "\r")
+ || ($ord >= 0x7f && $ord <= 0x9f)
+ ) {
+ return '�';
+ }
+
+ /**
+ * Check if the current character to escape has a name entity we should
+ * replace it with while grabbing the integer value of the character.
+ */
+ if (strlen($chr) > 1) {
+ $chr = $this->convertEncoding($chr, 'UTF-16BE', 'UTF-8');
+ }
+
+ $hex = bin2hex($chr);
+ $ord = hexdec($hex);
+ if (isset(static::$htmlNamedEntityMap[$ord])) {
+ return '&' . static::$htmlNamedEntityMap[$ord] . ';';
+ }
+
+ /**
+ * Per OWASP recommendations, we'll use upper hex entities
+ * for any other characters where a named entity does not exist.
+ */
+ if ($ord > 255) {
+ return sprintf('%04X;', $ord);
+ }
+ return sprintf('%02X;', $ord);
+ }
+
+ /**
+ * Callback function for preg_replace_callback that applies Javascript
+ * escaping to all matches.
+ *
+ * @param array $matches
+ * @return string
+ */
+ protected function jsMatcher($matches)
+ {
+ $chr = $matches[0];
+ if (strlen($chr) == 1) {
+ return sprintf('\\x%02X', ord($chr));
+ }
+ $chr = $this->convertEncoding($chr, 'UTF-16BE', 'UTF-8');
+ return sprintf('\\u%04s', strtoupper(bin2hex($chr)));
+ }
+
+ /**
+ * Callback function for preg_replace_callback that applies CSS
+ * escaping to all matches.
+ *
+ * @param array $matches
+ * @return string
+ */
+ protected function cssMatcher($matches)
+ {
+ $chr = $matches[0];
+ if (strlen($chr) == 1) {
+ $ord = ord($chr);
+ } else {
+ $chr = $this->convertEncoding($chr, 'UTF-16BE', 'UTF-8');
+ $ord = hexdec(bin2hex($chr));
+ }
+ return sprintf('\\%X ', $ord);
+ }
+
+ /**
+ * Converts a string to UTF-8 from the base encoding. The base encoding is set via this
+ * class' constructor.
+ *
+ * @param string $string
+ * @throws Exception\RuntimeException
+ * @return string
+ */
+ protected function toUtf8($string)
+ {
+ if ($this->getEncoding() === 'utf-8') {
+ $result = $string;
+ } else {
+ $result = $this->convertEncoding($string, 'UTF-8', $this->getEncoding());
+ }
+
+ if (!$this->isUtf8($result)) {
+ throw new Exception\RuntimeException(sprintf(
+ 'String to be escaped was not valid UTF-8 or could not be converted: %s', $result
+ ));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Converts a string from UTF-8 to the base encoding. The base encoding is set via this
+ * class' constructor.
+ * @param string $string
+ * @return string
+ */
+ protected function fromUtf8($string)
+ {
+ if ($this->getEncoding() === 'utf-8') {
+ return $string;
+ }
+
+ return $this->convertEncoding($string, $this->getEncoding(), 'UTF-8');
+ }
+
+ /**
+ * Checks if a given string appears to be valid UTF-8 or not.
+ *
+ * @param string $string
+ * @return bool
+ */
+ protected function isUtf8($string)
+ {
+ return ($string === '' || preg_match('/^./su', $string));
+ }
+
+ /**
+ * Encoding conversion helper which wraps iconv and mbstring where they exist or throws
+ * and exception where neither is available.
+ *
+ * @param string $string
+ * @param string $to
+ * @param array|string $from
+ * @throws Exception\RuntimeException
+ * @return string
+ */
+ protected function convertEncoding($string, $to, $from)
+ {
+ if (function_exists('iconv')) {
+ $result = iconv($from, $to, $string);
+ } elseif (function_exists('mb_convert_encoding')) {
+ $result = mb_convert_encoding($string, $to, $from);
+ } else {
+ throw new Exception\RuntimeException(
+ get_class($this)
+ . ' requires either the iconv or mbstring extension to be installed'
+ . ' when escaping for non UTF-8 strings.'
+ );
+ }
+
+ if ($result === false) {
+ return ''; // return non-fatal blank string on encoding errors from users
+ }
+ return $result;
+ }
+}
diff --git a/library/Zend/Escaper/Exception/ExceptionInterface.php b/library/Zend/Escaper/Exception/ExceptionInterface.php
new file mode 100755
index 0000000000..3a7c8a3f2e
--- /dev/null
+++ b/library/Zend/Escaper/Exception/ExceptionInterface.php
@@ -0,0 +1,14 @@
+=5.3.23"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev",
+ "dev-develop": "2.4-dev"
+ }
+ }
+}
diff --git a/library/Zend/EventManager/AbstractListenerAggregate.php b/library/Zend/EventManager/AbstractListenerAggregate.php
new file mode 100755
index 0000000000..3f4df278fc
--- /dev/null
+++ b/library/Zend/EventManager/AbstractListenerAggregate.php
@@ -0,0 +1,33 @@
+listeners as $index => $callback) {
+ if ($events->detach($callback)) {
+ unset($this->listeners[$index]);
+ }
+ }
+ }
+}
diff --git a/library/Zend/EventManager/CONTRIBUTING.md b/library/Zend/EventManager/CONTRIBUTING.md
new file mode 100755
index 0000000000..e77f5d2d5b
--- /dev/null
+++ b/library/Zend/EventManager/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# CONTRIBUTING
+
+Please don't open pull requests against this repository, please use https://github.com/zendframework/zf2.
\ No newline at end of file
diff --git a/library/Zend/EventManager/Event.php b/library/Zend/EventManager/Event.php
new file mode 100755
index 0000000000..abc34e7449
--- /dev/null
+++ b/library/Zend/EventManager/Event.php
@@ -0,0 +1,209 @@
+setName($name);
+ }
+
+ if (null !== $target) {
+ $this->setTarget($target);
+ }
+
+ if (null !== $params) {
+ $this->setParams($params);
+ }
+ }
+
+ /**
+ * Get event name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Get the event target
+ *
+ * This may be either an object, or the name of a static method.
+ *
+ * @return string|object
+ */
+ public function getTarget()
+ {
+ return $this->target;
+ }
+
+ /**
+ * Set parameters
+ *
+ * Overwrites parameters
+ *
+ * @param array|ArrayAccess|object $params
+ * @return Event
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setParams($params)
+ {
+ if (!is_array($params) && !is_object($params)) {
+ throw new Exception\InvalidArgumentException(
+ sprintf('Event parameters must be an array or object; received "%s"', gettype($params))
+ );
+ }
+
+ $this->params = $params;
+ return $this;
+ }
+
+ /**
+ * Get all parameters
+ *
+ * @return array|object|ArrayAccess
+ */
+ public function getParams()
+ {
+ return $this->params;
+ }
+
+ /**
+ * Get an individual parameter
+ *
+ * If the parameter does not exist, the $default value will be returned.
+ *
+ * @param string|int $name
+ * @param mixed $default
+ * @return mixed
+ */
+ public function getParam($name, $default = null)
+ {
+ // Check in params that are arrays or implement array access
+ if (is_array($this->params) || $this->params instanceof ArrayAccess) {
+ if (!isset($this->params[$name])) {
+ return $default;
+ }
+
+ return $this->params[$name];
+ }
+
+ // Check in normal objects
+ if (!isset($this->params->{$name})) {
+ return $default;
+ }
+ return $this->params->{$name};
+ }
+
+ /**
+ * Set the event name
+ *
+ * @param string $name
+ * @return Event
+ */
+ public function setName($name)
+ {
+ $this->name = (string) $name;
+ return $this;
+ }
+
+ /**
+ * Set the event target/context
+ *
+ * @param null|string|object $target
+ * @return Event
+ */
+ public function setTarget($target)
+ {
+ $this->target = $target;
+ return $this;
+ }
+
+ /**
+ * Set an individual parameter to a value
+ *
+ * @param string|int $name
+ * @param mixed $value
+ * @return Event
+ */
+ public function setParam($name, $value)
+ {
+ if (is_array($this->params) || $this->params instanceof ArrayAccess) {
+ // Arrays or objects implementing array access
+ $this->params[$name] = $value;
+ } else {
+ // Objects
+ $this->params->{$name} = $value;
+ }
+ return $this;
+ }
+
+ /**
+ * Stop further event propagation
+ *
+ * @param bool $flag
+ * @return void
+ */
+ public function stopPropagation($flag = true)
+ {
+ $this->stopPropagation = (bool) $flag;
+ }
+
+ /**
+ * Is propagation stopped?
+ *
+ * @return bool
+ */
+ public function propagationIsStopped()
+ {
+ return $this->stopPropagation;
+ }
+}
diff --git a/library/Zend/EventManager/EventInterface.php b/library/Zend/EventManager/EventInterface.php
new file mode 100755
index 0000000000..c1d0de701d
--- /dev/null
+++ b/library/Zend/EventManager/EventInterface.php
@@ -0,0 +1,96 @@
+setIdentifiers($identifiers);
+ }
+
+ /**
+ * Set the event class to utilize
+ *
+ * @param string $class
+ * @return EventManager
+ */
+ public function setEventClass($class)
+ {
+ $this->eventClass = $class;
+ return $this;
+ }
+
+ /**
+ * Set shared event manager
+ *
+ * @param SharedEventManagerInterface $sharedEventManager
+ * @return EventManager
+ */
+ public function setSharedManager(SharedEventManagerInterface $sharedEventManager)
+ {
+ $this->sharedManager = $sharedEventManager;
+ StaticEventManager::setInstance($sharedEventManager);
+ return $this;
+ }
+
+ /**
+ * Remove any shared event manager currently attached
+ *
+ * @return void
+ */
+ public function unsetSharedManager()
+ {
+ $this->sharedManager = false;
+ }
+
+ /**
+ * Get shared event manager
+ *
+ * If one is not defined, but we have a static instance in
+ * StaticEventManager, that one will be used and set in this instance.
+ *
+ * If none is available in the StaticEventManager, a boolean false is
+ * returned.
+ *
+ * @return false|SharedEventManagerInterface
+ */
+ public function getSharedManager()
+ {
+ // "false" means "I do not want a shared manager; don't try and fetch one"
+ if (false === $this->sharedManager
+ || $this->sharedManager instanceof SharedEventManagerInterface
+ ) {
+ return $this->sharedManager;
+ }
+
+ if (!StaticEventManager::hasInstance()) {
+ return false;
+ }
+
+ $this->sharedManager = StaticEventManager::getInstance();
+ return $this->sharedManager;
+ }
+
+ /**
+ * Get the identifier(s) for this EventManager
+ *
+ * @return array
+ */
+ public function getIdentifiers()
+ {
+ return $this->identifiers;
+ }
+
+ /**
+ * Set the identifiers (overrides any currently set identifiers)
+ *
+ * @param string|int|array|Traversable $identifiers
+ * @return EventManager Provides a fluent interface
+ */
+ public function setIdentifiers($identifiers)
+ {
+ if (is_array($identifiers) || $identifiers instanceof Traversable) {
+ $this->identifiers = array_unique((array) $identifiers);
+ } elseif ($identifiers !== null) {
+ $this->identifiers = array($identifiers);
+ }
+ return $this;
+ }
+
+ /**
+ * Add some identifier(s) (appends to any currently set identifiers)
+ *
+ * @param string|int|array|Traversable $identifiers
+ * @return EventManager Provides a fluent interface
+ */
+ public function addIdentifiers($identifiers)
+ {
+ if (is_array($identifiers) || $identifiers instanceof Traversable) {
+ $this->identifiers = array_unique(array_merge($this->identifiers, (array) $identifiers));
+ } elseif ($identifiers !== null) {
+ $this->identifiers = array_unique(array_merge($this->identifiers, array($identifiers)));
+ }
+ return $this;
+ }
+
+ /**
+ * Trigger all listeners for a given event
+ *
+ * Can emulate triggerUntil() if the last argument provided is a callback.
+ *
+ * @param string $event
+ * @param string|object $target Object calling emit, or symbol describing target (such as static method name)
+ * @param array|ArrayAccess $argv Array of arguments; typically, should be associative
+ * @param null|callable $callback
+ * @return ResponseCollection All listener return values
+ * @throws Exception\InvalidCallbackException
+ */
+ public function trigger($event, $target = null, $argv = array(), $callback = null)
+ {
+ if ($event instanceof EventInterface) {
+ $e = $event;
+ $event = $e->getName();
+ $callback = $target;
+ } elseif ($target instanceof EventInterface) {
+ $e = $target;
+ $e->setName($event);
+ $callback = $argv;
+ } elseif ($argv instanceof EventInterface) {
+ $e = $argv;
+ $e->setName($event);
+ $e->setTarget($target);
+ } else {
+ $e = new $this->eventClass();
+ $e->setName($event);
+ $e->setTarget($target);
+ $e->setParams($argv);
+ }
+
+ if ($callback && !is_callable($callback)) {
+ throw new Exception\InvalidCallbackException('Invalid callback provided');
+ }
+
+ // Initial value of stop propagation flag should be false
+ $e->stopPropagation(false);
+
+ return $this->triggerListeners($event, $e, $callback);
+ }
+
+ /**
+ * Trigger listeners until return value of one causes a callback to
+ * evaluate to true
+ *
+ * Triggers listeners until the provided callback evaluates the return
+ * value of one as true, or until all listeners have been executed.
+ *
+ * @param string $event
+ * @param string|object $target Object calling emit, or symbol describing target (such as static method name)
+ * @param array|ArrayAccess $argv Array of arguments; typically, should be associative
+ * @param callable $callback
+ * @return ResponseCollection
+ * @throws Exception\InvalidCallbackException if invalid callable provided
+ */
+ public function triggerUntil($event, $target, $argv = null, $callback = null)
+ {
+ if ($event instanceof EventInterface) {
+ $e = $event;
+ $event = $e->getName();
+ $callback = $target;
+ } elseif ($target instanceof EventInterface) {
+ $e = $target;
+ $e->setName($event);
+ $callback = $argv;
+ } elseif ($argv instanceof EventInterface) {
+ $e = $argv;
+ $e->setName($event);
+ $e->setTarget($target);
+ } else {
+ $e = new $this->eventClass();
+ $e->setName($event);
+ $e->setTarget($target);
+ $e->setParams($argv);
+ }
+
+ if (!is_callable($callback)) {
+ throw new Exception\InvalidCallbackException('Invalid callback provided');
+ }
+
+ // Initial value of stop propagation flag should be false
+ $e->stopPropagation(false);
+
+ return $this->triggerListeners($event, $e, $callback);
+ }
+
+ /**
+ * Attach a listener to an event
+ *
+ * The first argument is the event, and the next argument describes a
+ * callback that will respond to that event. A CallbackHandler instance
+ * describing the event listener combination will be returned.
+ *
+ * The last argument indicates a priority at which the event should be
+ * executed. By default, this value is 1; however, you may set it for any
+ * integer value. Higher values have higher priority (i.e., execute first).
+ *
+ * You can specify "*" for the event name. In such cases, the listener will
+ * be triggered for every event.
+ *
+ * @param string|array|ListenerAggregateInterface $event An event or array of event names. If a ListenerAggregateInterface, proxies to {@link attachAggregate()}.
+ * @param callable|int $callback If string $event provided, expects PHP callback; for a ListenerAggregateInterface $event, this will be the priority
+ * @param int $priority If provided, the priority at which to register the callable
+ * @return CallbackHandler|mixed CallbackHandler if attaching callable (to allow later unsubscribe); mixed if attaching aggregate
+ * @throws Exception\InvalidArgumentException
+ */
+ public function attach($event, $callback = null, $priority = 1)
+ {
+ // Proxy ListenerAggregateInterface arguments to attachAggregate()
+ if ($event instanceof ListenerAggregateInterface) {
+ return $this->attachAggregate($event, $callback);
+ }
+
+ // Null callback is invalid
+ if (null === $callback) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s: expects a callback; none provided',
+ __METHOD__
+ ));
+ }
+
+ // Array of events should be registered individually, and return an array of all listeners
+ if (is_array($event)) {
+ $listeners = array();
+ foreach ($event as $name) {
+ $listeners[] = $this->attach($name, $callback, $priority);
+ }
+ return $listeners;
+ }
+
+ // If we don't have a priority queue for the event yet, create one
+ if (empty($this->events[$event])) {
+ $this->events[$event] = new PriorityQueue();
+ }
+
+ // Create a callback handler, setting the event and priority in its metadata
+ $listener = new CallbackHandler($callback, array('event' => $event, 'priority' => $priority));
+
+ // Inject the callback handler into the queue
+ $this->events[$event]->insert($listener, $priority);
+ return $listener;
+ }
+
+ /**
+ * Attach a listener aggregate
+ *
+ * Listener aggregates accept an EventManagerInterface instance, and call attach()
+ * one or more times, typically to attach to multiple events using local
+ * methods.
+ *
+ * @param ListenerAggregateInterface $aggregate
+ * @param int $priority If provided, a suggested priority for the aggregate to use
+ * @return mixed return value of {@link ListenerAggregateInterface::attach()}
+ */
+ public function attachAggregate(ListenerAggregateInterface $aggregate, $priority = 1)
+ {
+ return $aggregate->attach($this, $priority);
+ }
+
+ /**
+ * Unsubscribe a listener from an event
+ *
+ * @param CallbackHandler|ListenerAggregateInterface $listener
+ * @return bool Returns true if event and listener found, and unsubscribed; returns false if either event or listener not found
+ * @throws Exception\InvalidArgumentException if invalid listener provided
+ */
+ public function detach($listener)
+ {
+ if ($listener instanceof ListenerAggregateInterface) {
+ return $this->detachAggregate($listener);
+ }
+
+ if (!$listener instanceof CallbackHandler) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s: expected a ListenerAggregateInterface or CallbackHandler; received "%s"',
+ __METHOD__,
+ (is_object($listener) ? get_class($listener) : gettype($listener))
+ ));
+ }
+
+ $event = $listener->getMetadatum('event');
+ if (!$event || empty($this->events[$event])) {
+ return false;
+ }
+ $return = $this->events[$event]->remove($listener);
+ if (!$return) {
+ return false;
+ }
+ if (!count($this->events[$event])) {
+ unset($this->events[$event]);
+ }
+ return true;
+ }
+
+ /**
+ * Detach a listener aggregate
+ *
+ * Listener aggregates accept an EventManagerInterface instance, and call detach()
+ * of all previously attached listeners.
+ *
+ * @param ListenerAggregateInterface $aggregate
+ * @return mixed return value of {@link ListenerAggregateInterface::detach()}
+ */
+ public function detachAggregate(ListenerAggregateInterface $aggregate)
+ {
+ return $aggregate->detach($this);
+ }
+
+ /**
+ * Retrieve all registered events
+ *
+ * @return array
+ */
+ public function getEvents()
+ {
+ return array_keys($this->events);
+ }
+
+ /**
+ * Retrieve all listeners for a given event
+ *
+ * @param string $event
+ * @return PriorityQueue
+ */
+ public function getListeners($event)
+ {
+ if (!array_key_exists($event, $this->events)) {
+ return new PriorityQueue();
+ }
+ return $this->events[$event];
+ }
+
+ /**
+ * Clear all listeners for a given event
+ *
+ * @param string $event
+ * @return void
+ */
+ public function clearListeners($event)
+ {
+ if (!empty($this->events[$event])) {
+ unset($this->events[$event]);
+ }
+ }
+
+ /**
+ * Prepare arguments
+ *
+ * Use this method if you want to be able to modify arguments from within a
+ * listener. It returns an ArrayObject of the arguments, which may then be
+ * passed to trigger() or triggerUntil().
+ *
+ * @param array $args
+ * @return ArrayObject
+ */
+ public function prepareArgs(array $args)
+ {
+ return new ArrayObject($args);
+ }
+
+ /**
+ * Trigger listeners
+ *
+ * Actual functionality for triggering listeners, to which both trigger() and triggerUntil()
+ * delegate.
+ *
+ * @param string $event Event name
+ * @param EventInterface $e
+ * @param null|callable $callback
+ * @return ResponseCollection
+ */
+ protected function triggerListeners($event, EventInterface $e, $callback = null)
+ {
+ $responses = new ResponseCollection;
+ $listeners = $this->getListeners($event);
+
+ // Add shared/wildcard listeners to the list of listeners,
+ // but don't modify the listeners object
+ $sharedListeners = $this->getSharedListeners($event);
+ $sharedWildcardListeners = $this->getSharedListeners('*');
+ $wildcardListeners = $this->getListeners('*');
+ if (count($sharedListeners) || count($sharedWildcardListeners) || count($wildcardListeners)) {
+ $listeners = clone $listeners;
+
+ // Shared listeners on this specific event
+ $this->insertListeners($listeners, $sharedListeners);
+
+ // Shared wildcard listeners
+ $this->insertListeners($listeners, $sharedWildcardListeners);
+
+ // Add wildcard listeners
+ $this->insertListeners($listeners, $wildcardListeners);
+ }
+
+ foreach ($listeners as $listener) {
+ $listenerCallback = $listener->getCallback();
+
+ // Trigger the listener's callback, and push its result onto the
+ // response collection
+ $responses->push(call_user_func($listenerCallback, $e));
+
+ // If the event was asked to stop propagating, do so
+ if ($e->propagationIsStopped()) {
+ $responses->setStopped(true);
+ break;
+ }
+
+ // If the result causes our validation callback to return true,
+ // stop propagation
+ if ($callback && call_user_func($callback, $responses->last())) {
+ $responses->setStopped(true);
+ break;
+ }
+ }
+
+ return $responses;
+ }
+
+ /**
+ * Get list of all listeners attached to the shared event manager for
+ * identifiers registered by this instance
+ *
+ * @param string $event
+ * @return array
+ */
+ protected function getSharedListeners($event)
+ {
+ if (!$sharedManager = $this->getSharedManager()) {
+ return array();
+ }
+
+ $identifiers = $this->getIdentifiers();
+ //Add wildcard id to the search, if not already added
+ if (!in_array('*', $identifiers)) {
+ $identifiers[] = '*';
+ }
+ $sharedListeners = array();
+
+ foreach ($identifiers as $id) {
+ if (!$listeners = $sharedManager->getListeners($id, $event)) {
+ continue;
+ }
+
+ if (!is_array($listeners) && !($listeners instanceof Traversable)) {
+ continue;
+ }
+
+ foreach ($listeners as $listener) {
+ if (!$listener instanceof CallbackHandler) {
+ continue;
+ }
+ $sharedListeners[] = $listener;
+ }
+ }
+
+ return $sharedListeners;
+ }
+
+ /**
+ * Add listeners to the master queue of listeners
+ *
+ * Used to inject shared listeners and wildcard listeners.
+ *
+ * @param PriorityQueue $masterListeners
+ * @param PriorityQueue $listeners
+ * @return void
+ */
+ protected function insertListeners($masterListeners, $listeners)
+ {
+ foreach ($listeners as $listener) {
+ $priority = $listener->getMetadatum('priority');
+ if (null === $priority) {
+ $priority = 1;
+ } elseif (is_array($priority)) {
+ // If we have an array, likely using PriorityQueue. Grab first
+ // element of the array, as that's the actual priority.
+ $priority = array_shift($priority);
+ }
+ $masterListeners->insert($listener, $priority);
+ }
+ }
+}
diff --git a/library/Zend/EventManager/EventManagerAwareInterface.php b/library/Zend/EventManager/EventManagerAwareInterface.php
new file mode 100755
index 0000000000..a5c25f25fa
--- /dev/null
+++ b/library/Zend/EventManager/EventManagerAwareInterface.php
@@ -0,0 +1,24 @@
+eventIdentifier property.
+ *
+ * @param EventManagerInterface $events
+ * @return mixed
+ */
+ public function setEventManager(EventManagerInterface $events)
+ {
+ $identifiers = array(__CLASS__, get_class($this));
+ if (isset($this->eventIdentifier)) {
+ if ((is_string($this->eventIdentifier))
+ || (is_array($this->eventIdentifier))
+ || ($this->eventIdentifier instanceof Traversable)
+ ) {
+ $identifiers = array_unique(array_merge($identifiers, (array) $this->eventIdentifier));
+ } elseif (is_object($this->eventIdentifier)) {
+ $identifiers[] = $this->eventIdentifier;
+ }
+ // silently ignore invalid eventIdentifier types
+ }
+ $events->setIdentifiers($identifiers);
+ $this->events = $events;
+ if (method_exists($this, 'attachDefaultListeners')) {
+ $this->attachDefaultListeners();
+ }
+ return $this;
+ }
+
+ /**
+ * Retrieve the event manager
+ *
+ * Lazy-loads an EventManager instance if none registered.
+ *
+ * @return EventManagerInterface
+ */
+ public function getEventManager()
+ {
+ if (!$this->events instanceof EventManagerInterface) {
+ $this->setEventManager(new EventManager());
+ }
+ return $this->events;
+ }
+}
diff --git a/library/Zend/EventManager/EventManagerInterface.php b/library/Zend/EventManager/EventManagerInterface.php
new file mode 100755
index 0000000000..6a2129f93a
--- /dev/null
+++ b/library/Zend/EventManager/EventManagerInterface.php
@@ -0,0 +1,144 @@
+setExtractFlags(self::EXTR_BOTH);
+
+ // Iterate and remove any matches
+ $removed = false;
+ $items = array();
+ $this->rewind();
+ while (!$this->isEmpty()) {
+ $item = $this->extract();
+ if ($item['data'] === $datum) {
+ $removed = true;
+ continue;
+ }
+ $items[] = $item;
+ }
+
+ // Repopulate
+ foreach ($items as $item) {
+ $this->insert($item['data'], $item['priority']);
+ }
+
+ $this->setExtractFlags(self::EXTR_DATA);
+ return $removed;
+ }
+
+ /**
+ * Iterate the next filter in the chain
+ *
+ * Iterates and calls the next filter in the chain.
+ *
+ * @param mixed $context
+ * @param array $params
+ * @param FilterIterator $chain
+ * @return mixed
+ */
+ public function next($context = null, array $params = array(), $chain = null)
+ {
+ if (empty($context) || $chain->isEmpty()) {
+ return;
+ }
+
+ $next = $this->extract();
+ if (!$next instanceof CallbackHandler) {
+ return;
+ }
+
+ $return = call_user_func($next->getCallback(), $context, $params, $chain);
+ return $return;
+ }
+}
diff --git a/library/Zend/EventManager/FilterChain.php b/library/Zend/EventManager/FilterChain.php
new file mode 100755
index 0000000000..d79a5de97c
--- /dev/null
+++ b/library/Zend/EventManager/FilterChain.php
@@ -0,0 +1,120 @@
+filters = new Filter\FilterIterator();
+ }
+
+ /**
+ * Apply the filters
+ *
+ * Begins iteration of the filters.
+ *
+ * @param mixed $context Object under observation
+ * @param mixed $argv Associative array of arguments
+ * @return mixed
+ */
+ public function run($context, array $argv = array())
+ {
+ $chain = clone $this->getFilters();
+
+ if ($chain->isEmpty()) {
+ return;
+ }
+
+ $next = $chain->extract();
+ if (!$next instanceof CallbackHandler) {
+ return;
+ }
+
+ return call_user_func($next->getCallback(), $context, $argv, $chain);
+ }
+
+ /**
+ * Connect a filter to the chain
+ *
+ * @param callable $callback PHP Callback
+ * @param int $priority Priority in the queue at which to execute; defaults to 1 (higher numbers == higher priority)
+ * @return CallbackHandler (to allow later unsubscribe)
+ * @throws Exception\InvalidCallbackException
+ */
+ public function attach($callback, $priority = 1)
+ {
+ if (empty($callback)) {
+ throw new Exception\InvalidCallbackException('No callback provided');
+ }
+ $filter = new CallbackHandler($callback, array('priority' => $priority));
+ $this->filters->insert($filter, $priority);
+ return $filter;
+ }
+
+ /**
+ * Detach a filter from the chain
+ *
+ * @param CallbackHandler $filter
+ * @return bool Returns true if filter found and unsubscribed; returns false otherwise
+ */
+ public function detach(CallbackHandler $filter)
+ {
+ return $this->filters->remove($filter);
+ }
+
+ /**
+ * Retrieve all filters
+ *
+ * @return Filter\FilterIterator
+ */
+ public function getFilters()
+ {
+ return $this->filters;
+ }
+
+ /**
+ * Clear all filters
+ *
+ * @return void
+ */
+ public function clearFilters()
+ {
+ $this->filters = new Filter\FilterIterator();
+ }
+
+ /**
+ * Return current responses
+ *
+ * Only available while the chain is still being iterated. Returns the
+ * current ResponseCollection.
+ *
+ * @return null|ResponseCollection
+ */
+ public function getResponses()
+ {
+ return null;
+ }
+}
diff --git a/library/Zend/EventManager/GlobalEventManager.php b/library/Zend/EventManager/GlobalEventManager.php
new file mode 100755
index 0000000000..4bac5b5741
--- /dev/null
+++ b/library/Zend/EventManager/GlobalEventManager.php
@@ -0,0 +1,135 @@
+trigger($event, $context, $argv);
+ }
+
+ /**
+ * Trigger listeners until return value of one causes a callback to evaluate
+ * to true.
+ *
+ * @param string $event
+ * @param string|object $context
+ * @param array|object $argv
+ * @param callable $callback
+ * @return ResponseCollection
+ */
+ public static function triggerUntil($event, $context, $argv, $callback)
+ {
+ return static::getEventCollection()->triggerUntil($event, $context, $argv, $callback);
+ }
+
+ /**
+ * Attach a listener to an event
+ *
+ * @param string $event
+ * @param callable $callback
+ * @param int $priority
+ * @return CallbackHandler
+ */
+ public static function attach($event, $callback, $priority = 1)
+ {
+ return static::getEventCollection()->attach($event, $callback, $priority);
+ }
+
+ /**
+ * Detach a callback from a listener
+ *
+ * @param CallbackHandler $listener
+ * @return bool
+ */
+ public static function detach(CallbackHandler $listener)
+ {
+ return static::getEventCollection()->detach($listener);
+ }
+
+ /**
+ * Retrieve list of events this object manages
+ *
+ * @return array
+ */
+ public static function getEvents()
+ {
+ return static::getEventCollection()->getEvents();
+ }
+
+ /**
+ * Retrieve all listeners for a given event
+ *
+ * @param string $event
+ * @return PriorityQueue|array
+ */
+ public static function getListeners($event)
+ {
+ return static::getEventCollection()->getListeners($event);
+ }
+
+ /**
+ * Clear all listeners for a given event
+ *
+ * @param string $event
+ * @return void
+ */
+ public static function clearListeners($event)
+ {
+ static::getEventCollection()->clearListeners($event);
+ }
+}
diff --git a/library/Zend/EventManager/ListenerAggregateInterface.php b/library/Zend/EventManager/ListenerAggregateInterface.php
new file mode 100755
index 0000000000..cd0eef4ce5
--- /dev/null
+++ b/library/Zend/EventManager/ListenerAggregateInterface.php
@@ -0,0 +1,42 @@
+listeners as $index => $callback) {
+ if ($events->detach($callback)) {
+ unset($this->listeners[$index]);
+ }
+ }
+ }
+}
diff --git a/library/Zend/EventManager/ProvidesEvents.php b/library/Zend/EventManager/ProvidesEvents.php
new file mode 100755
index 0000000000..0cfeb19753
--- /dev/null
+++ b/library/Zend/EventManager/ProvidesEvents.php
@@ -0,0 +1,23 @@
+stopped;
+ }
+
+ /**
+ * Mark the collection as stopped (or its opposite)
+ *
+ * @param bool $flag
+ * @return ResponseCollection
+ */
+ public function setStopped($flag)
+ {
+ $this->stopped = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Convenient access to the first handler return value.
+ *
+ * @return mixed The first handler return value
+ */
+ public function first()
+ {
+ return parent::bottom();
+ }
+
+ /**
+ * Convenient access to the last handler return value.
+ *
+ * If the collection is empty, returns null. Otherwise, returns value
+ * returned by last handler.
+ *
+ * @return mixed The last handler return value
+ */
+ public function last()
+ {
+ if (count($this) === 0) {
+ return null;
+ }
+ return parent::top();
+ }
+
+ /**
+ * Check if any of the responses match the given value.
+ *
+ * @param mixed $value The value to look for among responses
+ * @return bool
+ */
+ public function contains($value)
+ {
+ foreach ($this as $response) {
+ if ($response === $value) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/library/Zend/EventManager/SharedEventAggregateAwareInterface.php b/library/Zend/EventManager/SharedEventAggregateAwareInterface.php
new file mode 100755
index 0000000000..4cda8bc12b
--- /dev/null
+++ b/library/Zend/EventManager/SharedEventAggregateAwareInterface.php
@@ -0,0 +1,33 @@
+
+ * $sharedEventManager = new SharedEventManager();
+ * $sharedEventManager->attach(
+ * array('My\Resource\AbstractResource', 'My\Resource\EntityResource'),
+ * 'getAll',
+ * function ($e) use ($cache) {
+ * if (!$id = $e->getParam('id', false)) {
+ * return;
+ * }
+ * if (!$data = $cache->load(get_class($resource) . '::getOne::' . $id )) {
+ * return;
+ * }
+ * return $data;
+ * }
+ * );
+ *
+ *
+ * @param string|array $id Identifier(s) for event emitting component(s)
+ * @param string $event
+ * @param callable $callback PHP Callback
+ * @param int $priority Priority at which listener should execute
+ * @return CallbackHandler|array Either CallbackHandler or array of CallbackHandlers
+ */
+ public function attach($id, $event, $callback, $priority = 1)
+ {
+ $ids = (array) $id;
+ $listeners = array();
+ foreach ($ids as $id) {
+ if (!array_key_exists($id, $this->identifiers)) {
+ $this->identifiers[$id] = new EventManager($id);
+ }
+ $listeners[] = $this->identifiers[$id]->attach($event, $callback, $priority);
+ }
+ if (count($listeners) > 1) {
+ return $listeners;
+ }
+ return $listeners[0];
+ }
+
+ /**
+ * Attach a listener aggregate
+ *
+ * Listener aggregates accept an EventManagerInterface instance, and call attachShared()
+ * one or more times, typically to attach to multiple events using local
+ * methods.
+ *
+ * @param SharedListenerAggregateInterface $aggregate
+ * @param int $priority If provided, a suggested priority for the aggregate to use
+ * @return mixed return value of {@link ListenerAggregateInterface::attachShared()}
+ */
+ public function attachAggregate(SharedListenerAggregateInterface $aggregate, $priority = 1)
+ {
+ return $aggregate->attachShared($this, $priority);
+ }
+
+ /**
+ * Detach a listener from an event offered by a given resource
+ *
+ * @param string|int $id
+ * @param CallbackHandler $listener
+ * @return bool Returns true if event and listener found, and unsubscribed; returns false if either event or listener not found
+ */
+ public function detach($id, CallbackHandler $listener)
+ {
+ if (!array_key_exists($id, $this->identifiers)) {
+ return false;
+ }
+ return $this->identifiers[$id]->detach($listener);
+ }
+
+ /**
+ * Detach a listener aggregate
+ *
+ * Listener aggregates accept a SharedEventManagerInterface instance, and call detachShared()
+ * of all previously attached listeners.
+ *
+ * @param SharedListenerAggregateInterface $aggregate
+ * @return mixed return value of {@link SharedListenerAggregateInterface::detachShared()}
+ */
+ public function detachAggregate(SharedListenerAggregateInterface $aggregate)
+ {
+ return $aggregate->detachShared($this);
+ }
+
+ /**
+ * Retrieve all registered events for a given resource
+ *
+ * @param string|int $id
+ * @return array
+ */
+ public function getEvents($id)
+ {
+ if (!array_key_exists($id, $this->identifiers)) {
+ //Check if there are any id wildcards listeners
+ if ('*' != $id && array_key_exists('*', $this->identifiers)) {
+ return $this->identifiers['*']->getEvents();
+ }
+ return false;
+ }
+ return $this->identifiers[$id]->getEvents();
+ }
+
+ /**
+ * Retrieve all listeners for a given identifier and event
+ *
+ * @param string|int $id
+ * @param string|int $event
+ * @return false|PriorityQueue
+ */
+ public function getListeners($id, $event)
+ {
+ if (!array_key_exists($id, $this->identifiers)) {
+ return false;
+ }
+ return $this->identifiers[$id]->getListeners($event);
+ }
+
+ /**
+ * Clear all listeners for a given identifier, optionally for a specific event
+ *
+ * @param string|int $id
+ * @param null|string $event
+ * @return bool
+ */
+ public function clearListeners($id, $event = null)
+ {
+ if (!array_key_exists($id, $this->identifiers)) {
+ return false;
+ }
+
+ if (null === $event) {
+ unset($this->identifiers[$id]);
+ return true;
+ }
+
+ return $this->identifiers[$id]->clearListeners($event);
+ }
+}
diff --git a/library/Zend/EventManager/SharedEventManagerAwareInterface.php b/library/Zend/EventManager/SharedEventManagerAwareInterface.php
new file mode 100755
index 0000000000..09e5c98c12
--- /dev/null
+++ b/library/Zend/EventManager/SharedEventManagerAwareInterface.php
@@ -0,0 +1,38 @@
+=5.3.23",
+ "zendframework/zend-stdlib": "self.version"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev",
+ "dev-develop": "2.4-dev"
+ }
+ }
+}
diff --git a/library/Zend/Feed/CONTRIBUTING.md b/library/Zend/Feed/CONTRIBUTING.md
new file mode 100755
index 0000000000..e77f5d2d5b
--- /dev/null
+++ b/library/Zend/Feed/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# CONTRIBUTING
+
+Please don't open pull requests against this repository, please use https://github.com/zendframework/zf2.
\ No newline at end of file
diff --git a/library/Zend/Feed/Exception/BadMethodCallException.php b/library/Zend/Feed/Exception/BadMethodCallException.php
new file mode 100755
index 0000000000..107c3e64ef
--- /dev/null
+++ b/library/Zend/Feed/Exception/BadMethodCallException.php
@@ -0,0 +1,16 @@
+setOptions($options);
+ }
+ }
+
+ /**
+ * Process any injected configuration options
+ *
+ * @param array|Traversable $options Options array or Traversable object
+ * @return AbstractCallback
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setOptions($options)
+ {
+ if ($options instanceof Traversable) {
+ $options = ArrayUtils::iteratorToArray($options);
+ }
+
+ if (!is_array($options)) {
+ throw new Exception\InvalidArgumentException('Array or Traversable object'
+ . 'expected, got ' . gettype($options));
+ }
+
+ if (is_array($options)) {
+ $this->setOptions($options);
+ }
+
+ if (array_key_exists('storage', $options)) {
+ $this->setStorage($options['storage']);
+ }
+ return $this;
+ }
+
+ /**
+ * Send the response, including all headers.
+ * If you wish to handle this via Zend\Http, use the getter methods
+ * to retrieve any data needed to be set on your HTTP Response object, or
+ * simply give this object the HTTP Response instance to work with for you!
+ *
+ * @return void
+ */
+ public function sendResponse()
+ {
+ $this->getHttpResponse()->send();
+ }
+
+ /**
+ * Sets an instance of Zend\Feed\Pubsubhubbub\Model\SubscriptionPersistence used
+ * to background save any verification tokens associated with a subscription
+ * or other.
+ *
+ * @param Model\SubscriptionPersistenceInterface $storage
+ * @return AbstractCallback
+ */
+ public function setStorage(Model\SubscriptionPersistenceInterface $storage)
+ {
+ $this->storage = $storage;
+ return $this;
+ }
+
+ /**
+ * Gets an instance of Zend\Feed\Pubsubhubbub\Model\SubscriptionPersistence used
+ * to background save any verification tokens associated with a subscription
+ * or other.
+ *
+ * @return Model\SubscriptionPersistenceInterface
+ * @throws Exception\RuntimeException
+ */
+ public function getStorage()
+ {
+ if ($this->storage === null) {
+ throw new Exception\RuntimeException('No storage object has been'
+ . ' set that subclasses Zend\Feed\Pubsubhubbub\Model\SubscriptionPersistence');
+ }
+ return $this->storage;
+ }
+
+ /**
+ * An instance of a class handling Http Responses. This is implemented in
+ * Zend\Feed\Pubsubhubbub\HttpResponse which shares an unenforced interface with
+ * (i.e. not inherited from) Zend\Controller\Response\Http.
+ *
+ * @param HttpResponse|PhpResponse $httpResponse
+ * @return AbstractCallback
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setHttpResponse($httpResponse)
+ {
+ if (!$httpResponse instanceof HttpResponse && !$httpResponse instanceof PhpResponse) {
+ throw new Exception\InvalidArgumentException('HTTP Response object must'
+ . ' implement one of Zend\Feed\Pubsubhubbub\HttpResponse or'
+ . ' Zend\Http\PhpEnvironment\Response');
+ }
+ $this->httpResponse = $httpResponse;
+ return $this;
+ }
+
+ /**
+ * An instance of a class handling Http Responses. This is implemented in
+ * Zend\Feed\Pubsubhubbub\HttpResponse which shares an unenforced interface with
+ * (i.e. not inherited from) Zend\Controller\Response\Http.
+ *
+ * @return HttpResponse|PhpResponse
+ */
+ public function getHttpResponse()
+ {
+ if ($this->httpResponse === null) {
+ $this->httpResponse = new HttpResponse;
+ }
+ return $this->httpResponse;
+ }
+
+ /**
+ * Sets the number of Subscribers for which any updates are on behalf of.
+ * In other words, is this class serving one or more subscribers? How many?
+ * Defaults to 1 if left unchanged.
+ *
+ * @param string|int $count
+ * @return AbstractCallback
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setSubscriberCount($count)
+ {
+ $count = intval($count);
+ if ($count <= 0) {
+ throw new Exception\InvalidArgumentException('Subscriber count must be'
+ . ' greater than zero');
+ }
+ $this->subscriberCount = $count;
+ return $this;
+ }
+
+ /**
+ * Gets the number of Subscribers for which any updates are on behalf of.
+ * In other words, is this class serving one or more subscribers? How many?
+ *
+ * @return int
+ */
+ public function getSubscriberCount()
+ {
+ return $this->subscriberCount;
+ }
+
+ /**
+ * Attempt to detect the callback URL (specifically the path forward)
+ * @return string
+ */
+ protected function _detectCallbackUrl()
+ {
+ $callbackUrl = '';
+ if (isset($_SERVER['HTTP_X_ORIGINAL_URL'])) {
+ $callbackUrl = $_SERVER['HTTP_X_ORIGINAL_URL'];
+ } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) {
+ $callbackUrl = $_SERVER['HTTP_X_REWRITE_URL'];
+ } elseif (isset($_SERVER['REQUEST_URI'])) {
+ $callbackUrl = $_SERVER['REQUEST_URI'];
+ $scheme = 'http';
+ if ($_SERVER['HTTPS'] == 'on') {
+ $scheme = 'https';
+ }
+ $schemeAndHttpHost = $scheme . '://' . $this->_getHttpHost();
+ if (strpos($callbackUrl, $schemeAndHttpHost) === 0) {
+ $callbackUrl = substr($callbackUrl, strlen($schemeAndHttpHost));
+ }
+ } elseif (isset($_SERVER['ORIG_PATH_INFO'])) {
+ $callbackUrl= $_SERVER['ORIG_PATH_INFO'];
+ if (!empty($_SERVER['QUERY_STRING'])) {
+ $callbackUrl .= '?' . $_SERVER['QUERY_STRING'];
+ }
+ }
+ return $callbackUrl;
+ }
+
+ /**
+ * Get the HTTP host
+ *
+ * @return string
+ */
+ protected function _getHttpHost()
+ {
+ if (!empty($_SERVER['HTTP_HOST'])) {
+ return $_SERVER['HTTP_HOST'];
+ }
+ $scheme = 'http';
+ if ($_SERVER['HTTPS'] == 'on') {
+ $scheme = 'https';
+ }
+ $name = $_SERVER['SERVER_NAME'];
+ $port = $_SERVER['SERVER_PORT'];
+ if (($scheme == 'http' && $port == 80)
+ || ($scheme == 'https' && $port == 443)
+ ) {
+ return $name;
+ }
+
+ return $name . ':' . $port;
+ }
+
+ /**
+ * Retrieve a Header value from either $_SERVER or Apache
+ *
+ * @param string $header
+ * @return bool|string
+ */
+ protected function _getHeader($header)
+ {
+ $temp = strtoupper(str_replace('-', '_', $header));
+ if (!empty($_SERVER[$temp])) {
+ return $_SERVER[$temp];
+ }
+ $temp = 'HTTP_' . strtoupper(str_replace('-', '_', $header));
+ if (!empty($_SERVER[$temp])) {
+ return $_SERVER[$temp];
+ }
+ if (function_exists('apache_request_headers')) {
+ $headers = apache_request_headers();
+ if (!empty($headers[$header])) {
+ return $headers[$header];
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return the raw body of the request
+ *
+ * @return string|false Raw body, or false if not present
+ */
+ protected function _getRawBody()
+ {
+ $body = file_get_contents('php://input');
+ if (strlen(trim($body)) == 0 && isset($GLOBALS['HTTP_RAW_POST_DATA'])) {
+ $body = $GLOBALS['HTTP_RAW_POST_DATA'];
+ }
+ if (strlen(trim($body)) > 0) {
+ return $body;
+ }
+ return false;
+ }
+}
diff --git a/library/Zend/Feed/PubSubHubbub/CallbackInterface.php b/library/Zend/Feed/PubSubHubbub/CallbackInterface.php
new file mode 100755
index 0000000000..8873c3db48
--- /dev/null
+++ b/library/Zend/Feed/PubSubHubbub/CallbackInterface.php
@@ -0,0 +1,51 @@
+sendHeaders();
+ echo $this->getContent();
+ }
+
+ /**
+ * Send all headers
+ *
+ * Sends any headers specified. If an {@link setHttpResponseCode() HTTP response code}
+ * has been specified, it is sent with the first header.
+ *
+ * @return void
+ */
+ public function sendHeaders()
+ {
+ if (count($this->headers) || (200 != $this->statusCode)) {
+ $this->canSendHeaders(true);
+ } elseif (200 == $this->statusCode) {
+ return;
+ }
+ $httpCodeSent = false;
+ foreach ($this->headers as $header) {
+ if (!$httpCodeSent && $this->statusCode) {
+ header($header['name'] . ': ' . $header['value'], $header['replace'], $this->statusCode);
+ $httpCodeSent = true;
+ } else {
+ header($header['name'] . ': ' . $header['value'], $header['replace']);
+ }
+ }
+ if (!$httpCodeSent) {
+ header('HTTP/1.1 ' . $this->statusCode);
+ }
+ }
+
+ /**
+ * Set a header
+ *
+ * If $replace is true, replaces any headers already defined with that
+ * $name.
+ *
+ * @param string $name
+ * @param string $value
+ * @param bool $replace
+ * @return \Zend\Feed\PubSubHubbub\HttpResponse
+ */
+ public function setHeader($name, $value, $replace = false)
+ {
+ $name = $this->_normalizeHeader($name);
+ $value = (string) $value;
+ if ($replace) {
+ foreach ($this->headers as $key => $header) {
+ if ($name == $header['name']) {
+ unset($this->headers[$key]);
+ }
+ }
+ }
+ $this->headers[] = array(
+ 'name' => $name,
+ 'value' => $value,
+ 'replace' => $replace,
+ );
+
+ return $this;
+ }
+
+ /**
+ * Check if a specific Header is set and return its value
+ *
+ * @param string $name
+ * @return string|null
+ */
+ public function getHeader($name)
+ {
+ $name = $this->_normalizeHeader($name);
+ foreach ($this->headers as $header) {
+ if ($header['name'] == $name) {
+ return $header['value'];
+ }
+ }
+ }
+
+ /**
+ * Return array of headers; see {@link $headers} for format
+ *
+ * @return array
+ */
+ public function getHeaders()
+ {
+ return $this->headers;
+ }
+
+ /**
+ * Can we send headers?
+ *
+ * @param bool $throw Whether or not to throw an exception if headers have been sent; defaults to false
+ * @return HttpResponse
+ * @throws Exception\RuntimeException
+ */
+ public function canSendHeaders($throw = false)
+ {
+ $ok = headers_sent($file, $line);
+ if ($ok && $throw) {
+ throw new Exception\RuntimeException('Cannot send headers; headers already sent in ' . $file . ', line ' . $line);
+ }
+ return !$ok;
+ }
+
+ /**
+ * Set HTTP response code to use with headers
+ *
+ * @param int $code
+ * @return HttpResponse
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setStatusCode($code)
+ {
+ if (!is_int($code) || (100 > $code) || (599 < $code)) {
+ throw new Exception\InvalidArgumentException('Invalid HTTP response'
+ . ' code:' . $code);
+ }
+ $this->statusCode = $code;
+ return $this;
+ }
+
+ /**
+ * Retrieve HTTP response code
+ *
+ * @return int
+ */
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+
+ /**
+ * Set body content
+ *
+ * @param string $content
+ * @return \Zend\Feed\PubSubHubbub\HttpResponse
+ */
+ public function setContent($content)
+ {
+ $this->content = (string) $content;
+ $this->setHeader('content-length', strlen($content));
+ return $this;
+ }
+
+ /**
+ * Return the body content
+ *
+ * @return string
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * Normalizes a header name to X-Capitalized-Names
+ *
+ * @param string $name
+ * @return string
+ */
+ protected function _normalizeHeader($name)
+ {
+ $filtered = str_replace(array('-', '_'), ' ', (string) $name);
+ $filtered = ucwords(strtolower($filtered));
+ $filtered = str_replace(' ', '-', $filtered);
+ return $filtered;
+ }
+}
diff --git a/library/Zend/Feed/PubSubHubbub/Model/AbstractModel.php b/library/Zend/Feed/PubSubHubbub/Model/AbstractModel.php
new file mode 100755
index 0000000000..92e688133e
--- /dev/null
+++ b/library/Zend/Feed/PubSubHubbub/Model/AbstractModel.php
@@ -0,0 +1,39 @@
+db = new TableGateway($table, null);
+ } else {
+ $this->db = $tableGateway;
+ }
+ }
+}
diff --git a/library/Zend/Feed/PubSubHubbub/Model/Subscription.php b/library/Zend/Feed/PubSubHubbub/Model/Subscription.php
new file mode 100755
index 0000000000..9571106a40
--- /dev/null
+++ b/library/Zend/Feed/PubSubHubbub/Model/Subscription.php
@@ -0,0 +1,142 @@
+db->select(array('id' => $data['id']));
+ if ($result && (0 < count($result))) {
+ $data['created_time'] = $result->current()->created_time;
+ $now = $this->getNow();
+ if (array_key_exists('lease_seconds', $data)
+ && $data['lease_seconds']
+ ) {
+ $data['expiration_time'] = $now->add(new DateInterval('PT' . $data['lease_seconds'] . 'S'))
+ ->format('Y-m-d H:i:s');
+ }
+ $this->db->update(
+ $data,
+ array('id' => $data['id'])
+ );
+ return false;
+ }
+
+ $this->db->insert($data);
+ return true;
+ }
+
+ /**
+ * Get subscription by ID/key
+ *
+ * @param string $key
+ * @return array
+ * @throws PubSubHubbub\Exception\InvalidArgumentException
+ */
+ public function getSubscription($key)
+ {
+ if (empty($key) || !is_string($key)) {
+ throw new PubSubHubbub\Exception\InvalidArgumentException('Invalid parameter "key"'
+ .' of "' . $key . '" must be a non-empty string');
+ }
+ $result = $this->db->select(array('id' => $key));
+ if (count($result)) {
+ return $result->current()->getArrayCopy();
+ }
+ return false;
+ }
+
+ /**
+ * Determine if a subscription matching the key exists
+ *
+ * @param string $key
+ * @return bool
+ * @throws PubSubHubbub\Exception\InvalidArgumentException
+ */
+ public function hasSubscription($key)
+ {
+ if (empty($key) || !is_string($key)) {
+ throw new PubSubHubbub\Exception\InvalidArgumentException('Invalid parameter "key"'
+ .' of "' . $key . '" must be a non-empty string');
+ }
+ $result = $this->db->select(array('id' => $key));
+ if (count($result)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Delete a subscription
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function deleteSubscription($key)
+ {
+ $result = $this->db->select(array('id' => $key));
+ if (count($result)) {
+ $this->db->delete(
+ array('id' => $key)
+ );
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get a new DateTime or the one injected for testing
+ *
+ * @return DateTime
+ */
+ public function getNow()
+ {
+ if (null === $this->now) {
+ return new DateTime();
+ }
+ return $this->now;
+ }
+
+ /**
+ * Set a DateTime instance for assisting with unit testing
+ *
+ * @param DateTime $now
+ * @return Subscription
+ */
+ public function setNow(DateTime $now)
+ {
+ $this->now = $now;
+ return $this;
+ }
+}
diff --git a/library/Zend/Feed/PubSubHubbub/Model/SubscriptionPersistenceInterface.php b/library/Zend/Feed/PubSubHubbub/Model/SubscriptionPersistenceInterface.php
new file mode 100755
index 0000000000..ccd272329e
--- /dev/null
+++ b/library/Zend/Feed/PubSubHubbub/Model/SubscriptionPersistenceInterface.php
@@ -0,0 +1,45 @@
+getHubs();
+ }
+
+ /**
+ * Allows the external environment to make ZendOAuth use a specific
+ * Client instance.
+ *
+ * @param Http\Client $httpClient
+ * @return void
+ */
+ public static function setHttpClient(Http\Client $httpClient)
+ {
+ static::$httpClient = $httpClient;
+ }
+
+ /**
+ * Return the singleton instance of the HTTP Client. Note that
+ * the instance is reset and cleared of previous parameters GET/POST.
+ * Headers are NOT reset but handled by this component if applicable.
+ *
+ * @return Http\Client
+ */
+ public static function getHttpClient()
+ {
+ if (!isset(static::$httpClient)) {
+ static::$httpClient = new Http\Client;
+ } else {
+ static::$httpClient->resetParameters();
+ }
+ return static::$httpClient;
+ }
+
+ /**
+ * Simple mechanism to delete the entire singleton HTTP Client instance
+ * which forces a new instantiation for subsequent requests.
+ *
+ * @return void
+ */
+ public static function clearHttpClient()
+ {
+ static::$httpClient = null;
+ }
+
+ /**
+ * Set the Escaper instance
+ *
+ * If null, resets the instance
+ *
+ * @param null|Escaper $escaper
+ */
+ public static function setEscaper(Escaper $escaper = null)
+ {
+ static::$escaper = $escaper;
+ }
+
+ /**
+ * Get the Escaper instance
+ *
+ * If none registered, lazy-loads an instance.
+ *
+ * @return Escaper
+ */
+ public static function getEscaper()
+ {
+ if (null === static::$escaper) {
+ static::setEscaper(new Escaper());
+ }
+ return static::$escaper;
+ }
+
+ /**
+ * RFC 3986 safe url encoding method
+ *
+ * @param string $string
+ * @return string
+ */
+ public static function urlencode($string)
+ {
+ $escaper = static::getEscaper();
+ $rawencoded = $escaper->escapeUrl($string);
+ $rfcencoded = str_replace('%7E', '~', $rawencoded);
+ return $rfcencoded;
+ }
+}
diff --git a/library/Zend/Feed/PubSubHubbub/Publisher.php b/library/Zend/Feed/PubSubHubbub/Publisher.php
new file mode 100755
index 0000000000..916ffcad59
--- /dev/null
+++ b/library/Zend/Feed/PubSubHubbub/Publisher.php
@@ -0,0 +1,397 @@
+setOptions($options);
+ }
+ }
+
+ /**
+ * Process any injected configuration options
+ *
+ * @param array|Traversable $options Options array or Traversable object
+ * @return Publisher
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setOptions($options)
+ {
+ if ($options instanceof Traversable) {
+ $options = ArrayUtils::iteratorToArray($options);
+ }
+
+ if (!is_array($options)) {
+ throw new Exception\InvalidArgumentException('Array or Traversable object'
+ . 'expected, got ' . gettype($options));
+ }
+ if (array_key_exists('hubUrls', $options)) {
+ $this->addHubUrls($options['hubUrls']);
+ }
+ if (array_key_exists('updatedTopicUrls', $options)) {
+ $this->addUpdatedTopicUrls($options['updatedTopicUrls']);
+ }
+ if (array_key_exists('parameters', $options)) {
+ $this->setParameters($options['parameters']);
+ }
+ return $this;
+ }
+
+ /**
+ * Add a Hub Server URL supported by Publisher
+ *
+ * @param string $url
+ * @return Publisher
+ * @throws Exception\InvalidArgumentException
+ */
+ public function addHubUrl($url)
+ {
+ if (empty($url) || !is_string($url) || !Uri::factory($url)->isValid()) {
+ throw new Exception\InvalidArgumentException('Invalid parameter "url"'
+ . ' of "' . $url . '" must be a non-empty string and a valid'
+ . 'URL');
+ }
+ $this->hubUrls[] = $url;
+ return $this;
+ }
+
+ /**
+ * Add an array of Hub Server URLs supported by Publisher
+ *
+ * @param array $urls
+ * @return Publisher
+ */
+ public function addHubUrls(array $urls)
+ {
+ foreach ($urls as $url) {
+ $this->addHubUrl($url);
+ }
+ return $this;
+ }
+
+ /**
+ * Remove a Hub Server URL
+ *
+ * @param string $url
+ * @return Publisher
+ */
+ public function removeHubUrl($url)
+ {
+ if (!in_array($url, $this->getHubUrls())) {
+ return $this;
+ }
+ $key = array_search($url, $this->hubUrls);
+ unset($this->hubUrls[$key]);
+ return $this;
+ }
+
+ /**
+ * Return an array of unique Hub Server URLs currently available
+ *
+ * @return array
+ */
+ public function getHubUrls()
+ {
+ $this->hubUrls = array_unique($this->hubUrls);
+ return $this->hubUrls;
+ }
+
+ /**
+ * Add a URL to a topic (Atom or RSS feed) which has been updated
+ *
+ * @param string $url
+ * @return Publisher
+ * @throws Exception\InvalidArgumentException
+ */
+ public function addUpdatedTopicUrl($url)
+ {
+ if (empty($url) || !is_string($url) || !Uri::factory($url)->isValid()) {
+ throw new Exception\InvalidArgumentException('Invalid parameter "url"'
+ . ' of "' . $url . '" must be a non-empty string and a valid'
+ . 'URL');
+ }
+ $this->updatedTopicUrls[] = $url;
+ return $this;
+ }
+
+ /**
+ * Add an array of Topic URLs which have been updated
+ *
+ * @param array $urls
+ * @return Publisher
+ */
+ public function addUpdatedTopicUrls(array $urls)
+ {
+ foreach ($urls as $url) {
+ $this->addUpdatedTopicUrl($url);
+ }
+ return $this;
+ }
+
+ /**
+ * Remove an updated topic URL
+ *
+ * @param string $url
+ * @return Publisher
+ */
+ public function removeUpdatedTopicUrl($url)
+ {
+ if (!in_array($url, $this->getUpdatedTopicUrls())) {
+ return $this;
+ }
+ $key = array_search($url, $this->updatedTopicUrls);
+ unset($this->updatedTopicUrls[$key]);
+ return $this;
+ }
+
+ /**
+ * Return an array of unique updated topic URLs currently available
+ *
+ * @return array
+ */
+ public function getUpdatedTopicUrls()
+ {
+ $this->updatedTopicUrls = array_unique($this->updatedTopicUrls);
+ return $this->updatedTopicUrls;
+ }
+
+ /**
+ * Notifies a single Hub Server URL of changes
+ *
+ * @param string $url The Hub Server's URL
+ * @return void
+ * @throws Exception\InvalidArgumentException
+ * @throws Exception\RuntimeException
+ */
+ public function notifyHub($url)
+ {
+ if (empty($url) || !is_string($url) || !Uri::factory($url)->isValid()) {
+ throw new Exception\InvalidArgumentException('Invalid parameter "url"'
+ . ' of "' . $url . '" must be a non-empty string and a valid'
+ . 'URL');
+ }
+ $client = $this->_getHttpClient();
+ $client->setUri($url);
+ $response = $client->getResponse();
+ if ($response->getStatusCode() !== 204) {
+ throw new Exception\RuntimeException('Notification to Hub Server '
+ . 'at "' . $url . '" appears to have failed with a status code of "'
+ . $response->getStatusCode() . '" and message "'
+ . $response->getContent() . '"');
+ }
+ }
+
+ /**
+ * Notifies all Hub Server URLs of changes
+ *
+ * If a Hub notification fails, certain data will be retained in an
+ * an array retrieved using getErrors(), if a failure occurs for any Hubs
+ * the isSuccess() check will return FALSE. This method is designed not
+ * to needlessly fail with an Exception/Error unless from Zend\Http\Client.
+ *
+ * @return void
+ * @throws Exception\RuntimeException
+ */
+ public function notifyAll()
+ {
+ $client = $this->_getHttpClient();
+ $hubs = $this->getHubUrls();
+ if (empty($hubs)) {
+ throw new Exception\RuntimeException('No Hub Server URLs'
+ . ' have been set so no notifications can be sent');
+ }
+ $this->errors = array();
+ foreach ($hubs as $url) {
+ $client->setUri($url);
+ $response = $client->getResponse();
+ if ($response->getStatusCode() !== 204) {
+ $this->errors[] = array(
+ 'response' => $response,
+ 'hubUrl' => $url
+ );
+ }
+ }
+ }
+
+ /**
+ * Add an optional parameter to the update notification requests
+ *
+ * @param string $name
+ * @param string|null $value
+ * @return Publisher
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setParameter($name, $value = null)
+ {
+ if (is_array($name)) {
+ $this->setParameters($name);
+ return $this;
+ }
+ if (empty($name) || !is_string($name)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter "name"'
+ . ' of "' . $name . '" must be a non-empty string');
+ }
+ if ($value === null) {
+ $this->removeParameter($name);
+ return $this;
+ }
+ if (empty($value) || (!is_string($value) && $value !== null)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter "value"'
+ . ' of "' . $value . '" must be a non-empty string');
+ }
+ $this->parameters[$name] = $value;
+ return $this;
+ }
+
+ /**
+ * Add an optional parameter to the update notification requests
+ *
+ * @param array $parameters
+ * @return Publisher
+ */
+ public function setParameters(array $parameters)
+ {
+ foreach ($parameters as $name => $value) {
+ $this->setParameter($name, $value);
+ }
+ return $this;
+ }
+
+ /**
+ * Remove an optional parameter for the notification requests
+ *
+ * @param string $name
+ * @return Publisher
+ * @throws Exception\InvalidArgumentException
+ */
+ public function removeParameter($name)
+ {
+ if (empty($name) || !is_string($name)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter "name"'
+ . ' of "' . $name . '" must be a non-empty string');
+ }
+ if (array_key_exists($name, $this->parameters)) {
+ unset($this->parameters[$name]);
+ }
+ return $this;
+ }
+
+ /**
+ * Return an array of optional parameters for notification requests
+ *
+ * @return array
+ */
+ public function getParameters()
+ {
+ return $this->parameters;
+ }
+
+ /**
+ * Returns a boolean indicator of whether the notifications to Hub
+ * Servers were ALL successful. If even one failed, FALSE is returned.
+ *
+ * @return bool
+ */
+ public function isSuccess()
+ {
+ return !(count($this->errors) != 0);
+ }
+
+ /**
+ * Return an array of errors met from any failures, including keys:
+ * 'response' => the Zend\Http\Response object from the failure
+ * 'hubUrl' => the URL of the Hub Server whose notification failed
+ *
+ * @return array
+ */
+ public function getErrors()
+ {
+ return $this->errors;
+ }
+
+ /**
+ * Get a basic prepared HTTP client for use
+ *
+ * @return \Zend\Http\Client
+ * @throws Exception\RuntimeException
+ */
+ protected function _getHttpClient()
+ {
+ $client = PubSubHubbub::getHttpClient();
+ $client->setMethod(HttpRequest::METHOD_POST);
+ $client->setOptions(array(
+ 'useragent' => 'Zend_Feed_Pubsubhubbub_Publisher/' . Version::VERSION,
+ ));
+ $params = array();
+ $params[] = 'hub.mode=publish';
+ $topics = $this->getUpdatedTopicUrls();
+ if (empty($topics)) {
+ throw new Exception\RuntimeException('No updated topic URLs'
+ . ' have been set');
+ }
+ foreach ($topics as $topicUrl) {
+ $params[] = 'hub.url=' . urlencode($topicUrl);
+ }
+ $optParams = $this->getParameters();
+ foreach ($optParams as $name => $value) {
+ $params[] = urlencode($name) . '=' . urlencode($value);
+ }
+ $paramString = implode('&', $params);
+ $client->setRawBody($paramString);
+ return $client;
+ }
+}
diff --git a/library/Zend/Feed/PubSubHubbub/Subscriber.php b/library/Zend/Feed/PubSubHubbub/Subscriber.php
new file mode 100755
index 0000000000..265fe776b1
--- /dev/null
+++ b/library/Zend/Feed/PubSubHubbub/Subscriber.php
@@ -0,0 +1,837 @@
+setOptions($options);
+ }
+ }
+
+ /**
+ * Process any injected configuration options
+ *
+ * @param array|Traversable $options
+ * @return Subscriber
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setOptions($options)
+ {
+ if ($options instanceof Traversable) {
+ $options = ArrayUtils::iteratorToArray($options);
+ }
+
+ if (!is_array($options)) {
+ throw new Exception\InvalidArgumentException('Array or Traversable object'
+ . 'expected, got ' . gettype($options));
+ }
+ if (array_key_exists('hubUrls', $options)) {
+ $this->addHubUrls($options['hubUrls']);
+ }
+ if (array_key_exists('callbackUrl', $options)) {
+ $this->setCallbackUrl($options['callbackUrl']);
+ }
+ if (array_key_exists('topicUrl', $options)) {
+ $this->setTopicUrl($options['topicUrl']);
+ }
+ if (array_key_exists('storage', $options)) {
+ $this->setStorage($options['storage']);
+ }
+ if (array_key_exists('leaseSeconds', $options)) {
+ $this->setLeaseSeconds($options['leaseSeconds']);
+ }
+ if (array_key_exists('parameters', $options)) {
+ $this->setParameters($options['parameters']);
+ }
+ if (array_key_exists('authentications', $options)) {
+ $this->addAuthentications($options['authentications']);
+ }
+ if (array_key_exists('usePathParameter', $options)) {
+ $this->usePathParameter($options['usePathParameter']);
+ }
+ if (array_key_exists('preferredVerificationMode', $options)) {
+ $this->setPreferredVerificationMode(
+ $options['preferredVerificationMode']
+ );
+ }
+ return $this;
+ }
+
+ /**
+ * Set the topic URL (RSS or Atom feed) to which the intended (un)subscribe
+ * event will relate
+ *
+ * @param string $url
+ * @return Subscriber
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setTopicUrl($url)
+ {
+ if (empty($url) || !is_string($url) || !Uri::factory($url)->isValid()) {
+ throw new Exception\InvalidArgumentException('Invalid parameter "url"'
+ .' of "' . $url . '" must be a non-empty string and a valid'
+ .' URL');
+ }
+ $this->topicUrl = $url;
+ return $this;
+ }
+
+ /**
+ * Set the topic URL (RSS or Atom feed) to which the intended (un)subscribe
+ * event will relate
+ *
+ * @return string
+ * @throws Exception\RuntimeException
+ */
+ public function getTopicUrl()
+ {
+ if (empty($this->topicUrl)) {
+ throw new Exception\RuntimeException('A valid Topic (RSS or Atom'
+ . ' feed) URL MUST be set before attempting any operation');
+ }
+ return $this->topicUrl;
+ }
+
+ /**
+ * Set the number of seconds for which any subscription will remain valid
+ *
+ * @param int $seconds
+ * @return Subscriber
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setLeaseSeconds($seconds)
+ {
+ $seconds = intval($seconds);
+ if ($seconds <= 0) {
+ throw new Exception\InvalidArgumentException('Expected lease seconds'
+ . ' must be an integer greater than zero');
+ }
+ $this->leaseSeconds = $seconds;
+ return $this;
+ }
+
+ /**
+ * Get the number of lease seconds on subscriptions
+ *
+ * @return int
+ */
+ public function getLeaseSeconds()
+ {
+ return $this->leaseSeconds;
+ }
+
+ /**
+ * Set the callback URL to be used by Hub Servers when communicating with
+ * this Subscriber
+ *
+ * @param string $url
+ * @return Subscriber
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setCallbackUrl($url)
+ {
+ if (empty($url) || !is_string($url) || !Uri::factory($url)->isValid()) {
+ throw new Exception\InvalidArgumentException('Invalid parameter "url"'
+ . ' of "' . $url . '" must be a non-empty string and a valid'
+ . ' URL');
+ }
+ $this->callbackUrl = $url;
+ return $this;
+ }
+
+ /**
+ * Get the callback URL to be used by Hub Servers when communicating with
+ * this Subscriber
+ *
+ * @return string
+ * @throws Exception\RuntimeException
+ */
+ public function getCallbackUrl()
+ {
+ if (empty($this->callbackUrl)) {
+ throw new Exception\RuntimeException('A valid Callback URL MUST be'
+ . ' set before attempting any operation');
+ }
+ return $this->callbackUrl;
+ }
+
+ /**
+ * Set preferred verification mode (sync or async). By default, this
+ * Subscriber prefers synchronous verification, but does support
+ * asynchronous if that's the Hub Server's utilised mode.
+ *
+ * Zend\Feed\Pubsubhubbub\Subscriber will always send both modes, whose
+ * order of occurrence in the parameter list determines this preference.
+ *
+ * @param string $mode Should be 'sync' or 'async'
+ * @return Subscriber
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setPreferredVerificationMode($mode)
+ {
+ if ($mode !== PubSubHubbub::VERIFICATION_MODE_SYNC
+ && $mode !== PubSubHubbub::VERIFICATION_MODE_ASYNC
+ ) {
+ throw new Exception\InvalidArgumentException('Invalid preferred'
+ . ' mode specified: "' . $mode . '" but should be one of'
+ . ' Zend\Feed\Pubsubhubbub::VERIFICATION_MODE_SYNC or'
+ . ' Zend\Feed\Pubsubhubbub::VERIFICATION_MODE_ASYNC');
+ }
+ $this->preferredVerificationMode = $mode;
+ return $this;
+ }
+
+ /**
+ * Get preferred verification mode (sync or async).
+ *
+ * @return string
+ */
+ public function getPreferredVerificationMode()
+ {
+ return $this->preferredVerificationMode;
+ }
+
+ /**
+ * Add a Hub Server URL supported by Publisher
+ *
+ * @param string $url
+ * @return Subscriber
+ * @throws Exception\InvalidArgumentException
+ */
+ public function addHubUrl($url)
+ {
+ if (empty($url) || !is_string($url) || !Uri::factory($url)->isValid()) {
+ throw new Exception\InvalidArgumentException('Invalid parameter "url"'
+ . ' of "' . $url . '" must be a non-empty string and a valid'
+ . ' URL');
+ }
+ $this->hubUrls[] = $url;
+ return $this;
+ }
+
+ /**
+ * Add an array of Hub Server URLs supported by Publisher
+ *
+ * @param array $urls
+ * @return Subscriber
+ */
+ public function addHubUrls(array $urls)
+ {
+ foreach ($urls as $url) {
+ $this->addHubUrl($url);
+ }
+ return $this;
+ }
+
+ /**
+ * Remove a Hub Server URL
+ *
+ * @param string $url
+ * @return Subscriber
+ */
+ public function removeHubUrl($url)
+ {
+ if (!in_array($url, $this->getHubUrls())) {
+ return $this;
+ }
+ $key = array_search($url, $this->hubUrls);
+ unset($this->hubUrls[$key]);
+ return $this;
+ }
+
+ /**
+ * Return an array of unique Hub Server URLs currently available
+ *
+ * @return array
+ */
+ public function getHubUrls()
+ {
+ $this->hubUrls = array_unique($this->hubUrls);
+ return $this->hubUrls;
+ }
+
+ /**
+ * Add authentication credentials for a given URL
+ *
+ * @param string $url
+ * @param array $authentication
+ * @return Subscriber
+ * @throws Exception\InvalidArgumentException
+ */
+ public function addAuthentication($url, array $authentication)
+ {
+ if (empty($url) || !is_string($url) || !Uri::factory($url)->isValid()) {
+ throw new Exception\InvalidArgumentException('Invalid parameter "url"'
+ . ' of "' . $url . '" must be a non-empty string and a valid'
+ . ' URL');
+ }
+ $this->authentications[$url] = $authentication;
+ return $this;
+ }
+
+ /**
+ * Add authentication credentials for hub URLs
+ *
+ * @param array $authentications
+ * @return Subscriber
+ */
+ public function addAuthentications(array $authentications)
+ {
+ foreach ($authentications as $url => $authentication) {
+ $this->addAuthentication($url, $authentication);
+ }
+ return $this;
+ }
+
+ /**
+ * Get all hub URL authentication credentials
+ *
+ * @return array
+ */
+ public function getAuthentications()
+ {
+ return $this->authentications;
+ }
+
+ /**
+ * Set flag indicating whether or not to use a path parameter
+ *
+ * @param bool $bool
+ * @return Subscriber
+ */
+ public function usePathParameter($bool = true)
+ {
+ $this->usePathParameter = $bool;
+ return $this;
+ }
+
+ /**
+ * Add an optional parameter to the (un)subscribe requests
+ *
+ * @param string $name
+ * @param string|null $value
+ * @return Subscriber
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setParameter($name, $value = null)
+ {
+ if (is_array($name)) {
+ $this->setParameters($name);
+ return $this;
+ }
+ if (empty($name) || !is_string($name)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter "name"'
+ . ' of "' . $name . '" must be a non-empty string');
+ }
+ if ($value === null) {
+ $this->removeParameter($name);
+ return $this;
+ }
+ if (empty($value) || (!is_string($value) && $value !== null)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter "value"'
+ . ' of "' . $value . '" must be a non-empty string');
+ }
+ $this->parameters[$name] = $value;
+ return $this;
+ }
+
+ /**
+ * Add an optional parameter to the (un)subscribe requests
+ *
+ * @param array $parameters
+ * @return Subscriber
+ */
+ public function setParameters(array $parameters)
+ {
+ foreach ($parameters as $name => $value) {
+ $this->setParameter($name, $value);
+ }
+ return $this;
+ }
+
+ /**
+ * Remove an optional parameter for the (un)subscribe requests
+ *
+ * @param string $name
+ * @return Subscriber
+ * @throws Exception\InvalidArgumentException
+ */
+ public function removeParameter($name)
+ {
+ if (empty($name) || !is_string($name)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter "name"'
+ . ' of "' . $name . '" must be a non-empty string');
+ }
+ if (array_key_exists($name, $this->parameters)) {
+ unset($this->parameters[$name]);
+ }
+ return $this;
+ }
+
+ /**
+ * Return an array of optional parameters for (un)subscribe requests
+ *
+ * @return array
+ */
+ public function getParameters()
+ {
+ return $this->parameters;
+ }
+
+ /**
+ * Sets an instance of Zend\Feed\Pubsubhubbub\Model\SubscriptionPersistence used to background
+ * save any verification tokens associated with a subscription or other.
+ *
+ * @param Model\SubscriptionPersistenceInterface $storage
+ * @return Subscriber
+ */
+ public function setStorage(Model\SubscriptionPersistenceInterface $storage)
+ {
+ $this->storage = $storage;
+ return $this;
+ }
+
+ /**
+ * Gets an instance of Zend\Feed\Pubsubhubbub\Storage\StoragePersistence used
+ * to background save any verification tokens associated with a subscription
+ * or other.
+ *
+ * @return Model\SubscriptionPersistenceInterface
+ * @throws Exception\RuntimeException
+ */
+ public function getStorage()
+ {
+ if ($this->storage === null) {
+ throw new Exception\RuntimeException('No storage vehicle '
+ . 'has been set.');
+ }
+ return $this->storage;
+ }
+
+ /**
+ * Subscribe to one or more Hub Servers using the stored Hub URLs
+ * for the given Topic URL (RSS or Atom feed)
+ *
+ * @return void
+ */
+ public function subscribeAll()
+ {
+ $this->_doRequest('subscribe');
+ }
+
+ /**
+ * Unsubscribe from one or more Hub Servers using the stored Hub URLs
+ * for the given Topic URL (RSS or Atom feed)
+ *
+ * @return void
+ */
+ public function unsubscribeAll()
+ {
+ $this->_doRequest('unsubscribe');
+ }
+
+ /**
+ * Returns a boolean indicator of whether the notifications to Hub
+ * Servers were ALL successful. If even one failed, FALSE is returned.
+ *
+ * @return bool
+ */
+ public function isSuccess()
+ {
+ if (count($this->errors) > 0) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Return an array of errors met from any failures, including keys:
+ * 'response' => the Zend\Http\Response object from the failure
+ * 'hubUrl' => the URL of the Hub Server whose notification failed
+ *
+ * @return array
+ */
+ public function getErrors()
+ {
+ return $this->errors;
+ }
+
+ /**
+ * Return an array of Hub Server URLs who returned a response indicating
+ * operation in Asynchronous Verification Mode, i.e. they will not confirm
+ * any (un)subscription immediately but at a later time (Hubs may be
+ * doing this as a batch process when load balancing)
+ *
+ * @return array
+ */
+ public function getAsyncHubs()
+ {
+ return $this->asyncHubs;
+ }
+
+ /**
+ * Executes an (un)subscribe request
+ *
+ * @param string $mode
+ * @return void
+ * @throws Exception\RuntimeException
+ */
+ protected function _doRequest($mode)
+ {
+ $client = $this->_getHttpClient();
+ $hubs = $this->getHubUrls();
+ if (empty($hubs)) {
+ throw new Exception\RuntimeException('No Hub Server URLs'
+ . ' have been set so no subscriptions can be attempted');
+ }
+ $this->errors = array();
+ $this->asyncHubs = array();
+ foreach ($hubs as $url) {
+ if (array_key_exists($url, $this->authentications)) {
+ $auth = $this->authentications[$url];
+ $client->setAuth($auth[0], $auth[1]);
+ }
+ $client->setUri($url);
+ $client->setRawBody($params = $this->_getRequestParameters($url, $mode));
+ $response = $client->send();
+ if ($response->getStatusCode() !== 204
+ && $response->getStatusCode() !== 202
+ ) {
+ $this->errors[] = array(
+ 'response' => $response,
+ 'hubUrl' => $url,
+ );
+ /**
+ * At first I thought it was needed, but the backend storage will
+ * allow tracking async without any user interference. It's left
+ * here in case the user is interested in knowing what Hubs
+ * are using async verification modes so they may update Models and
+ * move these to asynchronous processes.
+ */
+ } elseif ($response->getStatusCode() == 202) {
+ $this->asyncHubs[] = array(
+ 'response' => $response,
+ 'hubUrl' => $url,
+ );
+ }
+ }
+ }
+
+ /**
+ * Get a basic prepared HTTP client for use
+ *
+ * @return \Zend\Http\Client
+ */
+ protected function _getHttpClient()
+ {
+ $client = PubSubHubbub::getHttpClient();
+ $client->setMethod(HttpRequest::METHOD_POST);
+ $client->setOptions(array('useragent' => 'Zend_Feed_Pubsubhubbub_Subscriber/'
+ . Version::VERSION));
+ return $client;
+ }
+
+ /**
+ * Return a list of standard protocol/optional parameters for addition to
+ * client's POST body that are specific to the current Hub Server URL
+ *
+ * @param string $hubUrl
+ * @param string $mode
+ * @return string
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function _getRequestParameters($hubUrl, $mode)
+ {
+ if (!in_array($mode, array('subscribe', 'unsubscribe'))) {
+ throw new Exception\InvalidArgumentException('Invalid mode specified: "'
+ . $mode . '" which should have been "subscribe" or "unsubscribe"');
+ }
+
+ $params = array(
+ 'hub.mode' => $mode,
+ 'hub.topic' => $this->getTopicUrl(),
+ );
+
+ if ($this->getPreferredVerificationMode()
+ == PubSubHubbub::VERIFICATION_MODE_SYNC
+ ) {
+ $vmodes = array(
+ PubSubHubbub::VERIFICATION_MODE_SYNC,
+ PubSubHubbub::VERIFICATION_MODE_ASYNC,
+ );
+ } else {
+ $vmodes = array(
+ PubSubHubbub::VERIFICATION_MODE_ASYNC,
+ PubSubHubbub::VERIFICATION_MODE_SYNC,
+ );
+ }
+ $params['hub.verify'] = array();
+ foreach ($vmodes as $vmode) {
+ $params['hub.verify'][] = $vmode;
+ }
+
+ /**
+ * Establish a persistent verify_token and attach key to callback
+ * URL's path/query_string
+ */
+ $key = $this->_generateSubscriptionKey($params, $hubUrl);
+ $token = $this->_generateVerifyToken();
+ $params['hub.verify_token'] = $token;
+
+ // Note: query string only usable with PuSH 0.2 Hubs
+ if (!$this->usePathParameter) {
+ $params['hub.callback'] = $this->getCallbackUrl()
+ . '?xhub.subscription=' . PubSubHubbub::urlencode($key);
+ } else {
+ $params['hub.callback'] = rtrim($this->getCallbackUrl(), '/')
+ . '/' . PubSubHubbub::urlencode($key);
+ }
+ if ($mode == 'subscribe' && $this->getLeaseSeconds() !== null) {
+ $params['hub.lease_seconds'] = $this->getLeaseSeconds();
+ }
+
+ // hub.secret not currently supported
+ $optParams = $this->getParameters();
+ foreach ($optParams as $name => $value) {
+ $params[$name] = $value;
+ }
+
+ // store subscription to storage
+ $now = new DateTime();
+ $expires = null;
+ if (isset($params['hub.lease_seconds'])) {
+ $expires = $now->add(new DateInterval('PT' . $params['hub.lease_seconds'] . 'S'))
+ ->format('Y-m-d H:i:s');
+ }
+ $data = array(
+ 'id' => $key,
+ 'topic_url' => $params['hub.topic'],
+ 'hub_url' => $hubUrl,
+ 'created_time' => $now->format('Y-m-d H:i:s'),
+ 'lease_seconds' => $params['hub.lease_seconds'],
+ 'verify_token' => hash('sha256', $params['hub.verify_token']),
+ 'secret' => null,
+ 'expiration_time' => $expires,
+ 'subscription_state' => ($mode == 'unsubscribe')? PubSubHubbub::SUBSCRIPTION_TODELETE : PubSubHubbub::SUBSCRIPTION_NOTVERIFIED,
+ );
+ $this->getStorage()->setSubscription($data);
+
+ return $this->_toByteValueOrderedString(
+ $this->_urlEncode($params)
+ );
+ }
+
+ /**
+ * Simple helper to generate a verification token used in (un)subscribe
+ * requests to a Hub Server. Follows no particular method, which means
+ * it might be improved/changed in future.
+ *
+ * @return string
+ */
+ protected function _generateVerifyToken()
+ {
+ if (!empty($this->testStaticToken)) {
+ return $this->testStaticToken;
+ }
+ return uniqid(rand(), true) . time();
+ }
+
+ /**
+ * Simple helper to generate a verification token used in (un)subscribe
+ * requests to a Hub Server.
+ *
+ * @param array $params
+ * @param string $hubUrl The Hub Server URL for which this token will apply
+ * @return string
+ */
+ protected function _generateSubscriptionKey(array $params, $hubUrl)
+ {
+ $keyBase = $params['hub.topic'] . $hubUrl;
+ $key = md5($keyBase);
+
+ return $key;
+ }
+
+ /**
+ * URL Encode an array of parameters
+ *
+ * @param array $params
+ * @return array
+ */
+ protected function _urlEncode(array $params)
+ {
+ $encoded = array();
+ foreach ($params as $key => $value) {
+ if (is_array($value)) {
+ $ekey = PubSubHubbub::urlencode($key);
+ $encoded[$ekey] = array();
+ foreach ($value as $duplicateKey) {
+ $encoded[$ekey][]
+ = PubSubHubbub::urlencode($duplicateKey);
+ }
+ } else {
+ $encoded[PubSubHubbub::urlencode($key)]
+ = PubSubHubbub::urlencode($value);
+ }
+ }
+ return $encoded;
+ }
+
+ /**
+ * Order outgoing parameters
+ *
+ * @param array $params
+ * @return array
+ */
+ protected function _toByteValueOrderedString(array $params)
+ {
+ $return = array();
+ uksort($params, 'strnatcmp');
+ foreach ($params as $key => $value) {
+ if (is_array($value)) {
+ foreach ($value as $keyduplicate) {
+ $return[] = $key . '=' . $keyduplicate;
+ }
+ } else {
+ $return[] = $key . '=' . $value;
+ }
+ }
+ return implode('&', $return);
+ }
+
+ /**
+ * This is STRICTLY for testing purposes only...
+ */
+ protected $testStaticToken = null;
+
+ final public function setTestStaticToken($token)
+ {
+ $this->testStaticToken = (string) $token;
+ }
+}
diff --git a/library/Zend/Feed/PubSubHubbub/Subscriber/Callback.php b/library/Zend/Feed/PubSubHubbub/Subscriber/Callback.php
new file mode 100755
index 0000000000..5ec8af2fe1
--- /dev/null
+++ b/library/Zend/Feed/PubSubHubbub/Subscriber/Callback.php
@@ -0,0 +1,316 @@
+subscriptionKey = $key;
+ return $this;
+ }
+
+ /**
+ * Handle any callback from a Hub Server responding to a subscription or
+ * unsubscription request. This should be the Hub Server confirming the
+ * the request prior to taking action on it.
+ *
+ * @param array $httpGetData GET data if available and not in $_GET
+ * @param bool $sendResponseNow Whether to send response now or when asked
+ * @return void
+ */
+ public function handle(array $httpGetData = null, $sendResponseNow = false)
+ {
+ if ($httpGetData === null) {
+ $httpGetData = $_GET;
+ }
+
+ /**
+ * Handle any feed updates (sorry for the mess :P)
+ *
+ * This DOES NOT attempt to process a feed update. Feed updates
+ * SHOULD be validated/processed by an asynchronous process so as
+ * to avoid holding up responses to the Hub.
+ */
+ $contentType = $this->_getHeader('Content-Type');
+ if (strtolower($_SERVER['REQUEST_METHOD']) == 'post'
+ && $this->_hasValidVerifyToken(null, false)
+ && (stripos($contentType, 'application/atom+xml') === 0
+ || stripos($contentType, 'application/rss+xml') === 0
+ || stripos($contentType, 'application/xml') === 0
+ || stripos($contentType, 'text/xml') === 0
+ || stripos($contentType, 'application/rdf+xml') === 0)
+ ) {
+ $this->setFeedUpdate($this->_getRawBody());
+ $this->getHttpResponse()->setHeader('X-Hub-On-Behalf-Of', $this->getSubscriberCount());
+ /**
+ * Handle any (un)subscribe confirmation requests
+ */
+ } elseif ($this->isValidHubVerification($httpGetData)) {
+ $this->getHttpResponse()->setContent($httpGetData['hub_challenge']);
+
+ switch (strtolower($httpGetData['hub_mode'])) {
+ case 'subscribe':
+ $data = $this->currentSubscriptionData;
+ $data['subscription_state'] = PubSubHubbub\PubSubHubbub::SUBSCRIPTION_VERIFIED;
+ if (isset($httpGetData['hub_lease_seconds'])) {
+ $data['lease_seconds'] = $httpGetData['hub_lease_seconds'];
+ }
+ $this->getStorage()->setSubscription($data);
+ break;
+ case 'unsubscribe':
+ $verifyTokenKey = $this->_detectVerifyTokenKey($httpGetData);
+ $this->getStorage()->deleteSubscription($verifyTokenKey);
+ break;
+ default:
+ throw new Exception\RuntimeException(sprintf(
+ 'Invalid hub_mode ("%s") provided',
+ $httpGetData['hub_mode']
+ ));
+ }
+ /**
+ * Hey, C'mon! We tried everything else!
+ */
+ } else {
+ $this->getHttpResponse()->setStatusCode(404);
+ }
+
+ if ($sendResponseNow) {
+ $this->sendResponse();
+ }
+ }
+
+ /**
+ * Checks validity of the request simply by making a quick pass and
+ * confirming the presence of all REQUIRED parameters.
+ *
+ * @param array $httpGetData
+ * @return bool
+ */
+ public function isValidHubVerification(array $httpGetData)
+ {
+ /**
+ * As per the specification, the hub.verify_token is OPTIONAL. This
+ * implementation of Pubsubhubbub considers it REQUIRED and will
+ * always send a hub.verify_token parameter to be echoed back
+ * by the Hub Server. Therefore, its absence is considered invalid.
+ */
+ if (strtolower($_SERVER['REQUEST_METHOD']) !== 'get') {
+ return false;
+ }
+ $required = array(
+ 'hub_mode',
+ 'hub_topic',
+ 'hub_challenge',
+ 'hub_verify_token',
+ );
+ foreach ($required as $key) {
+ if (!array_key_exists($key, $httpGetData)) {
+ return false;
+ }
+ }
+ if ($httpGetData['hub_mode'] !== 'subscribe'
+ && $httpGetData['hub_mode'] !== 'unsubscribe'
+ ) {
+ return false;
+ }
+ if ($httpGetData['hub_mode'] == 'subscribe'
+ && !array_key_exists('hub_lease_seconds', $httpGetData)
+ ) {
+ return false;
+ }
+ if (!Uri::factory($httpGetData['hub_topic'])->isValid()) {
+ return false;
+ }
+
+ /**
+ * Attempt to retrieve any Verification Token Key attached to Callback
+ * URL's path by our Subscriber implementation
+ */
+ if (!$this->_hasValidVerifyToken($httpGetData)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Sets a newly received feed (Atom/RSS) sent by a Hub as an update to a
+ * Topic we've subscribed to.
+ *
+ * @param string $feed
+ * @return \Zend\Feed\PubSubHubbub\Subscriber\Callback
+ */
+ public function setFeedUpdate($feed)
+ {
+ $this->feedUpdate = $feed;
+ return $this;
+ }
+
+ /**
+ * Check if any newly received feed (Atom/RSS) update was received
+ *
+ * @return bool
+ */
+ public function hasFeedUpdate()
+ {
+ if ($this->feedUpdate === null) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Gets a newly received feed (Atom/RSS) sent by a Hub as an update to a
+ * Topic we've subscribed to.
+ *
+ * @return string
+ */
+ public function getFeedUpdate()
+ {
+ return $this->feedUpdate;
+ }
+
+ /**
+ * Check for a valid verify_token. By default attempts to compare values
+ * with that sent from Hub, otherwise merely ascertains its existence.
+ *
+ * @param array $httpGetData
+ * @param bool $checkValue
+ * @return bool
+ */
+ protected function _hasValidVerifyToken(array $httpGetData = null, $checkValue = true)
+ {
+ $verifyTokenKey = $this->_detectVerifyTokenKey($httpGetData);
+ if (empty($verifyTokenKey)) {
+ return false;
+ }
+ $verifyTokenExists = $this->getStorage()->hasSubscription($verifyTokenKey);
+ if (!$verifyTokenExists) {
+ return false;
+ }
+ if ($checkValue) {
+ $data = $this->getStorage()->getSubscription($verifyTokenKey);
+ $verifyToken = $data['verify_token'];
+ if ($verifyToken !== hash('sha256', $httpGetData['hub_verify_token'])) {
+ return false;
+ }
+ $this->currentSubscriptionData = $data;
+ return true;
+ }
+ return true;
+ }
+
+ /**
+ * Attempt to detect the verification token key. This would be passed in
+ * the Callback URL (which we are handling with this class!) as a URI
+ * path part (the last part by convention).
+ *
+ * @param null|array $httpGetData
+ * @return false|string
+ */
+ protected function _detectVerifyTokenKey(array $httpGetData = null)
+ {
+ /**
+ * Available when sub keys encoding in Callback URL path
+ */
+ if (isset($this->subscriptionKey)) {
+ return $this->subscriptionKey;
+ }
+
+ /**
+ * Available only if allowed by PuSH 0.2 Hubs
+ */
+ if (is_array($httpGetData)
+ && isset($httpGetData['xhub_subscription'])
+ ) {
+ return $httpGetData['xhub_subscription'];
+ }
+
+ /**
+ * Available (possibly) if corrupted in transit and not part of $_GET
+ */
+ $params = $this->_parseQueryString();
+ if (isset($params['xhub.subscription'])) {
+ return rawurldecode($params['xhub.subscription']);
+ }
+
+ return false;
+ }
+
+ /**
+ * Build an array of Query String parameters.
+ * This bypasses $_GET which munges parameter names and cannot accept
+ * multiple parameters with the same key.
+ *
+ * @return array|void
+ */
+ protected function _parseQueryString()
+ {
+ $params = array();
+ $queryString = '';
+ if (isset($_SERVER['QUERY_STRING'])) {
+ $queryString = $_SERVER['QUERY_STRING'];
+ }
+ if (empty($queryString)) {
+ return array();
+ }
+ $parts = explode('&', $queryString);
+ foreach ($parts as $kvpair) {
+ $pair = explode('=', $kvpair);
+ $key = rawurldecode($pair[0]);
+ $value = rawurldecode($pair[1]);
+ if (isset($params[$key])) {
+ if (is_array($params[$key])) {
+ $params[$key][] = $value;
+ } else {
+ $params[$key] = array($params[$key], $value);
+ }
+ } else {
+ $params[$key] = $value;
+ }
+ }
+ return $params;
+ }
+}
diff --git a/library/Zend/Feed/PubSubHubbub/Version.php b/library/Zend/Feed/PubSubHubbub/Version.php
new file mode 100755
index 0000000000..edee6953bc
--- /dev/null
+++ b/library/Zend/Feed/PubSubHubbub/Version.php
@@ -0,0 +1,15 @@
+entry = $entry;
+ $this->entryKey = $entryKey;
+ $this->domDocument = $entry->ownerDocument;
+ if ($type !== null) {
+ $this->data['type'] = $type;
+ } else {
+ $this->data['type'] = Reader::detectType($entry);
+ }
+ $this->_loadExtensions();
+ }
+
+ /**
+ * Get the DOM
+ *
+ * @return DOMDocument
+ */
+ public function getDomDocument()
+ {
+ return $this->domDocument;
+ }
+
+ /**
+ * Get the entry element
+ *
+ * @return DOMElement
+ */
+ public function getElement()
+ {
+ return $this->entry;
+ }
+
+ /**
+ * Get the Entry's encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ $assumed = $this->getDomDocument()->encoding;
+ if (empty($assumed)) {
+ $assumed = 'UTF-8';
+ }
+ return $assumed;
+ }
+
+ /**
+ * Get entry as xml
+ *
+ * @return string
+ */
+ public function saveXml()
+ {
+ $dom = new DOMDocument('1.0', $this->getEncoding());
+ $entry = $dom->importNode($this->getElement(), true);
+ $dom->appendChild($entry);
+ return $dom->saveXml();
+ }
+
+ /**
+ * Get the entry type
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->data['type'];
+ }
+
+ /**
+ * Get the XPath query object
+ *
+ * @return DOMXPath
+ */
+ public function getXpath()
+ {
+ if (!$this->xpath) {
+ $this->setXpath(new DOMXPath($this->getDomDocument()));
+ }
+ return $this->xpath;
+ }
+
+ /**
+ * Set the XPath query
+ *
+ * @param DOMXPath $xpath
+ * @return \Zend\Feed\Reader\AbstractEntry
+ */
+ public function setXpath(DOMXPath $xpath)
+ {
+ $this->xpath = $xpath;
+ return $this;
+ }
+
+ /**
+ * Get registered extensions
+ *
+ * @return array
+ */
+ public function getExtensions()
+ {
+ return $this->extensions;
+ }
+
+ /**
+ * Return an Extension object with the matching name (postfixed with _Entry)
+ *
+ * @param string $name
+ * @return \Zend\Feed\Reader\Extension\AbstractEntry
+ */
+ public function getExtension($name)
+ {
+ if (array_key_exists($name . '\Entry', $this->extensions)) {
+ return $this->extensions[$name . '\Entry'];
+ }
+ return null;
+ }
+
+ /**
+ * Method overloading: call given method on first extension implementing it
+ *
+ * @param string $method
+ * @param array $args
+ * @return mixed
+ * @throws Exception\BadMethodCallException if no extensions implements the method
+ */
+ public function __call($method, $args)
+ {
+ foreach ($this->extensions as $extension) {
+ if (method_exists($extension, $method)) {
+ return call_user_func_array(array($extension, $method), $args);
+ }
+ }
+ throw new Exception\BadMethodCallException('Method: ' . $method
+ . 'does not exist and could not be located on a registered Extension');
+ }
+
+ /**
+ * Load extensions from Zend\Feed\Reader\Reader
+ *
+ * @return void
+ */
+ protected function _loadExtensions()
+ {
+ $all = Reader::getExtensions();
+ $feed = $all['entry'];
+ foreach ($feed as $extension) {
+ if (in_array($extension, $all['core'])) {
+ continue;
+ }
+ $className = Reader::getPluginLoader()->getClassName($extension);
+ $this->extensions[$extension] = new $className(
+ $this->getElement(), $this->entryKey, $this->data['type']
+ );
+ }
+ }
+}
diff --git a/library/Zend/Feed/Reader/AbstractFeed.php b/library/Zend/Feed/Reader/AbstractFeed.php
new file mode 100755
index 0000000000..f8aa49d81e
--- /dev/null
+++ b/library/Zend/Feed/Reader/AbstractFeed.php
@@ -0,0 +1,300 @@
+domDocument = $domDocument;
+ $this->xpath = new DOMXPath($this->domDocument);
+
+ if ($type !== null) {
+ $this->data['type'] = $type;
+ } else {
+ $this->data['type'] = Reader::detectType($this->domDocument);
+ }
+ $this->registerNamespaces();
+ $this->indexEntries();
+ $this->loadExtensions();
+ }
+
+ /**
+ * Set an original source URI for the feed being parsed. This value
+ * is returned from getFeedLink() method if the feed does not carry
+ * a self-referencing URI.
+ *
+ * @param string $uri
+ */
+ public function setOriginalSourceUri($uri)
+ {
+ $this->originalSourceUri = $uri;
+ }
+
+ /**
+ * Get an original source URI for the feed being parsed. Returns null if
+ * unset or the feed was not imported from a URI.
+ *
+ * @return string|null
+ */
+ public function getOriginalSourceUri()
+ {
+ return $this->originalSourceUri;
+ }
+
+ /**
+ * Get the number of feed entries.
+ * Required by the Iterator interface.
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->entries);
+ }
+
+ /**
+ * Return the current entry
+ *
+ * @return \Zend\Feed\Reader\AbstractEntry
+ */
+ public function current()
+ {
+ if (substr($this->getType(), 0, 3) == 'rss') {
+ $reader = new Entry\RSS($this->entries[$this->key()], $this->key(), $this->getType());
+ } else {
+ $reader = new Entry\Atom($this->entries[$this->key()], $this->key(), $this->getType());
+ }
+
+ $reader->setXpath($this->xpath);
+
+ return $reader;
+ }
+
+ /**
+ * Get the DOM
+ *
+ * @return DOMDocument
+ */
+ public function getDomDocument()
+ {
+ return $this->domDocument;
+ }
+
+ /**
+ * Get the Feed's encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ $assumed = $this->getDomDocument()->encoding;
+ if (empty($assumed)) {
+ $assumed = 'UTF-8';
+ }
+ return $assumed;
+ }
+
+ /**
+ * Get feed as xml
+ *
+ * @return string
+ */
+ public function saveXml()
+ {
+ return $this->getDomDocument()->saveXml();
+ }
+
+ /**
+ * Get the DOMElement representing the items/feed element
+ *
+ * @return DOMElement
+ */
+ public function getElement()
+ {
+ return $this->getDomDocument()->documentElement;
+ }
+
+ /**
+ * Get the DOMXPath object for this feed
+ *
+ * @return DOMXPath
+ */
+ public function getXpath()
+ {
+ return $this->xpath;
+ }
+
+ /**
+ * Get the feed type
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->data['type'];
+ }
+
+ /**
+ * Return the current feed key
+ *
+ * @return int
+ */
+ public function key()
+ {
+ return $this->entriesKey;
+ }
+
+ /**
+ * Move the feed pointer forward
+ *
+ */
+ public function next()
+ {
+ ++$this->entriesKey;
+ }
+
+ /**
+ * Reset the pointer in the feed object
+ *
+ */
+ public function rewind()
+ {
+ $this->entriesKey = 0;
+ }
+
+ /**
+ * Check to see if the iterator is still valid
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return 0 <= $this->entriesKey && $this->entriesKey < $this->count();
+ }
+
+ public function getExtensions()
+ {
+ return $this->extensions;
+ }
+
+ public function __call($method, $args)
+ {
+ foreach ($this->extensions as $extension) {
+ if (method_exists($extension, $method)) {
+ return call_user_func_array(array($extension, $method), $args);
+ }
+ }
+ throw new Exception\BadMethodCallException('Method: ' . $method
+ . 'does not exist and could not be located on a registered Extension');
+ }
+
+ /**
+ * Return an Extension object with the matching name (postfixed with _Feed)
+ *
+ * @param string $name
+ * @return \Zend\Feed\Reader\Extension\AbstractFeed
+ */
+ public function getExtension($name)
+ {
+ if (array_key_exists($name . '\Feed', $this->extensions)) {
+ return $this->extensions[$name . '\Feed'];
+ }
+ return null;
+ }
+
+ protected function loadExtensions()
+ {
+ $all = Reader::getExtensions();
+ $manager = Reader::getExtensionManager();
+ $feed = $all['feed'];
+ foreach ($feed as $extension) {
+ if (in_array($extension, $all['core'])) {
+ continue;
+ }
+ $plugin = $manager->get($extension);
+ $plugin->setDomDocument($this->getDomDocument());
+ $plugin->setType($this->data['type']);
+ $plugin->setXpath($this->xpath);
+ $this->extensions[$extension] = $plugin;
+ }
+ }
+
+ /**
+ * Read all entries to the internal entries array
+ *
+ */
+ abstract protected function indexEntries();
+
+ /**
+ * Register the default namespaces for the current feed format
+ *
+ */
+ abstract protected function registerNamespaces();
+}
diff --git a/library/Zend/Feed/Reader/Collection.php b/library/Zend/Feed/Reader/Collection.php
new file mode 100755
index 0000000000..ac1c96384a
--- /dev/null
+++ b/library/Zend/Feed/Reader/Collection.php
@@ -0,0 +1,16 @@
+getIterator() as $element) {
+ $authors[] = $element['name'];
+ }
+ return array_unique($authors);
+ }
+}
diff --git a/library/Zend/Feed/Reader/Collection/Category.php b/library/Zend/Feed/Reader/Collection/Category.php
new file mode 100755
index 0000000000..34b8fdedb4
--- /dev/null
+++ b/library/Zend/Feed/Reader/Collection/Category.php
@@ -0,0 +1,34 @@
+getIterator() as $element) {
+ if (isset($element['label']) && !empty($element['label'])) {
+ $categories[] = $element['label'];
+ } else {
+ $categories[] = $element['term'];
+ }
+ }
+ return array_unique($categories);
+ }
+}
diff --git a/library/Zend/Feed/Reader/Collection/Collection.php b/library/Zend/Feed/Reader/Collection/Collection.php
new file mode 100755
index 0000000000..86a29276ac
--- /dev/null
+++ b/library/Zend/Feed/Reader/Collection/Collection.php
@@ -0,0 +1,16 @@
+entry = $entry;
+ $this->entryKey = $entryKey;
+ $this->domDocument = $entry->ownerDocument;
+ if ($type !== null) {
+ $this->data['type'] = $type;
+ } elseif ($this->domDocument !== null) {
+ $this->data['type'] = Reader\Reader::detectType($this->domDocument);
+ } else {
+ $this->data['type'] = Reader\Reader::TYPE_ANY;
+ }
+ $this->loadExtensions();
+ }
+
+ /**
+ * Get the DOM
+ *
+ * @return DOMDocument
+ */
+ public function getDomDocument()
+ {
+ return $this->domDocument;
+ }
+
+ /**
+ * Get the entry element
+ *
+ * @return DOMElement
+ */
+ public function getElement()
+ {
+ return $this->entry;
+ }
+
+ /**
+ * Get the Entry's encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ $assumed = $this->getDomDocument()->encoding;
+ if (empty($assumed)) {
+ $assumed = 'UTF-8';
+ }
+ return $assumed;
+ }
+
+ /**
+ * Get entry as xml
+ *
+ * @return string
+ */
+ public function saveXml()
+ {
+ $dom = new DOMDocument('1.0', $this->getEncoding());
+ $entry = $dom->importNode($this->getElement(), true);
+ $dom->appendChild($entry);
+ return $dom->saveXml();
+ }
+
+ /**
+ * Get the entry type
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->data['type'];
+ }
+
+ /**
+ * Get the XPath query object
+ *
+ * @return DOMXPath
+ */
+ public function getXpath()
+ {
+ if (!$this->xpath) {
+ $this->setXpath(new DOMXPath($this->getDomDocument()));
+ }
+ return $this->xpath;
+ }
+
+ /**
+ * Set the XPath query
+ *
+ * @param DOMXPath $xpath
+ * @return AbstractEntry
+ */
+ public function setXpath(DOMXPath $xpath)
+ {
+ $this->xpath = $xpath;
+ return $this;
+ }
+
+ /**
+ * Get registered extensions
+ *
+ * @return array
+ */
+ public function getExtensions()
+ {
+ return $this->extensions;
+ }
+
+ /**
+ * Return an Extension object with the matching name (postfixed with _Entry)
+ *
+ * @param string $name
+ * @return Reader\Extension\AbstractEntry
+ */
+ public function getExtension($name)
+ {
+ if (array_key_exists($name . '\\Entry', $this->extensions)) {
+ return $this->extensions[$name . '\\Entry'];
+ }
+ return null;
+ }
+
+ /**
+ * Method overloading: call given method on first extension implementing it
+ *
+ * @param string $method
+ * @param array $args
+ * @return mixed
+ * @throws Exception\RuntimeException if no extensions implements the method
+ */
+ public function __call($method, $args)
+ {
+ foreach ($this->extensions as $extension) {
+ if (method_exists($extension, $method)) {
+ return call_user_func_array(array($extension, $method), $args);
+ }
+ }
+ throw new Exception\RuntimeException('Method: ' . $method
+ . ' does not exist and could not be located on a registered Extension');
+ }
+
+ /**
+ * Load extensions from Zend\Feed\Reader\Reader
+ *
+ * @return void
+ */
+ protected function loadExtensions()
+ {
+ $all = Reader\Reader::getExtensions();
+ $manager = Reader\Reader::getExtensionManager();
+ $feed = $all['entry'];
+ foreach ($feed as $extension) {
+ if (in_array($extension, $all['core'])) {
+ continue;
+ }
+ $plugin = $manager->get($extension);
+ $plugin->setEntryElement($this->getElement());
+ $plugin->setEntryKey($this->entryKey);
+ $plugin->setType($this->data['type']);
+ $this->extensions[$extension] = $plugin;
+ }
+ }
+}
diff --git a/library/Zend/Feed/Reader/Entry/Atom.php b/library/Zend/Feed/Reader/Entry/Atom.php
new file mode 100755
index 0000000000..ed61a21e5f
--- /dev/null
+++ b/library/Zend/Feed/Reader/Entry/Atom.php
@@ -0,0 +1,370 @@
+xpathQuery = '//atom:entry[' . ($this->entryKey + 1) . ']';
+
+ $manager = Reader\Reader::getExtensionManager();
+ $extensions = array('Atom\Entry', 'Thread\Entry', 'DublinCore\Entry');
+
+ foreach ($extensions as $name) {
+ $extension = $manager->get($name);
+ $extension->setEntryElement($entry);
+ $extension->setEntryKey($entryKey);
+ $extension->setType($type);
+ $this->extensions[$name] = $extension;
+ }
+ }
+
+ /**
+ * Get the specified author
+ *
+ * @param int $index
+ * @return string|null
+ */
+ public function getAuthor($index = 0)
+ {
+ $authors = $this->getAuthors();
+
+ if (isset($authors[$index])) {
+ return $authors[$index];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get an array with feed authors
+ *
+ * @return array
+ */
+ public function getAuthors()
+ {
+ if (array_key_exists('authors', $this->data)) {
+ return $this->data['authors'];
+ }
+
+ $people = $this->getExtension('Atom')->getAuthors();
+
+ $this->data['authors'] = $people;
+
+ return $this->data['authors'];
+ }
+
+ /**
+ * Get the entry content
+ *
+ * @return string
+ */
+ public function getContent()
+ {
+ if (array_key_exists('content', $this->data)) {
+ return $this->data['content'];
+ }
+
+ $content = $this->getExtension('Atom')->getContent();
+
+ $this->data['content'] = $content;
+
+ return $this->data['content'];
+ }
+
+ /**
+ * Get the entry creation date
+ *
+ * @return string
+ */
+ public function getDateCreated()
+ {
+ if (array_key_exists('datecreated', $this->data)) {
+ return $this->data['datecreated'];
+ }
+
+ $dateCreated = $this->getExtension('Atom')->getDateCreated();
+
+ $this->data['datecreated'] = $dateCreated;
+
+ return $this->data['datecreated'];
+ }
+
+ /**
+ * Get the entry modification date
+ *
+ * @return string
+ */
+ public function getDateModified()
+ {
+ if (array_key_exists('datemodified', $this->data)) {
+ return $this->data['datemodified'];
+ }
+
+ $dateModified = $this->getExtension('Atom')->getDateModified();
+
+ $this->data['datemodified'] = $dateModified;
+
+ return $this->data['datemodified'];
+ }
+
+ /**
+ * Get the entry description
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ if (array_key_exists('description', $this->data)) {
+ return $this->data['description'];
+ }
+
+ $description = $this->getExtension('Atom')->getDescription();
+
+ $this->data['description'] = $description;
+
+ return $this->data['description'];
+ }
+
+ /**
+ * Get the entry enclosure
+ *
+ * @return string
+ */
+ public function getEnclosure()
+ {
+ if (array_key_exists('enclosure', $this->data)) {
+ return $this->data['enclosure'];
+ }
+
+ $enclosure = $this->getExtension('Atom')->getEnclosure();
+
+ $this->data['enclosure'] = $enclosure;
+
+ return $this->data['enclosure'];
+ }
+
+ /**
+ * Get the entry ID
+ *
+ * @return string
+ */
+ public function getId()
+ {
+ if (array_key_exists('id', $this->data)) {
+ return $this->data['id'];
+ }
+
+ $id = $this->getExtension('Atom')->getId();
+
+ $this->data['id'] = $id;
+
+ return $this->data['id'];
+ }
+
+ /**
+ * Get a specific link
+ *
+ * @param int $index
+ * @return string
+ */
+ public function getLink($index = 0)
+ {
+ if (!array_key_exists('links', $this->data)) {
+ $this->getLinks();
+ }
+
+ if (isset($this->data['links'][$index])) {
+ return $this->data['links'][$index];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get all links
+ *
+ * @return array
+ */
+ public function getLinks()
+ {
+ if (array_key_exists('links', $this->data)) {
+ return $this->data['links'];
+ }
+
+ $links = $this->getExtension('Atom')->getLinks();
+
+ $this->data['links'] = $links;
+
+ return $this->data['links'];
+ }
+
+ /**
+ * Get a permalink to the entry
+ *
+ * @return string
+ */
+ public function getPermalink()
+ {
+ return $this->getLink(0);
+ }
+
+ /**
+ * Get the entry title
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ if (array_key_exists('title', $this->data)) {
+ return $this->data['title'];
+ }
+
+ $title = $this->getExtension('Atom')->getTitle();
+
+ $this->data['title'] = $title;
+
+ return $this->data['title'];
+ }
+
+ /**
+ * Get the number of comments/replies for current entry
+ *
+ * @return int
+ */
+ public function getCommentCount()
+ {
+ if (array_key_exists('commentcount', $this->data)) {
+ return $this->data['commentcount'];
+ }
+
+ $commentcount = $this->getExtension('Thread')->getCommentCount();
+
+ if (!$commentcount) {
+ $commentcount = $this->getExtension('Atom')->getCommentCount();
+ }
+
+ $this->data['commentcount'] = $commentcount;
+
+ return $this->data['commentcount'];
+ }
+
+ /**
+ * Returns a URI pointing to the HTML page where comments can be made on this entry
+ *
+ * @return string
+ */
+ public function getCommentLink()
+ {
+ if (array_key_exists('commentlink', $this->data)) {
+ return $this->data['commentlink'];
+ }
+
+ $commentlink = $this->getExtension('Atom')->getCommentLink();
+
+ $this->data['commentlink'] = $commentlink;
+
+ return $this->data['commentlink'];
+ }
+
+ /**
+ * Returns a URI pointing to a feed of all comments for this entry
+ *
+ * @return string
+ */
+ public function getCommentFeedLink()
+ {
+ if (array_key_exists('commentfeedlink', $this->data)) {
+ return $this->data['commentfeedlink'];
+ }
+
+ $commentfeedlink = $this->getExtension('Atom')->getCommentFeedLink();
+
+ $this->data['commentfeedlink'] = $commentfeedlink;
+
+ return $this->data['commentfeedlink'];
+ }
+
+ /**
+ * Get category data as a Reader\Reader_Collection_Category object
+ *
+ * @return Reader\Collection\Category
+ */
+ public function getCategories()
+ {
+ if (array_key_exists('categories', $this->data)) {
+ return $this->data['categories'];
+ }
+
+ $categoryCollection = $this->getExtension('Atom')->getCategories();
+
+ if (count($categoryCollection) == 0) {
+ $categoryCollection = $this->getExtension('DublinCore')->getCategories();
+ }
+
+ $this->data['categories'] = $categoryCollection;
+
+ return $this->data['categories'];
+ }
+
+ /**
+ * Get source feed metadata from the entry
+ *
+ * @return Reader\Feed\Atom\Source|null
+ */
+ public function getSource()
+ {
+ if (array_key_exists('source', $this->data)) {
+ return $this->data['source'];
+ }
+
+ $source = $this->getExtension('Atom')->getSource();
+
+ $this->data['source'] = $source;
+
+ return $this->data['source'];
+ }
+
+ /**
+ * Set the XPath query (incl. on all Extensions)
+ *
+ * @param DOMXPath $xpath
+ * @return void
+ */
+ public function setXpath(DOMXPath $xpath)
+ {
+ parent::setXpath($xpath);
+ foreach ($this->extensions as $extension) {
+ $extension->setXpath($this->xpath);
+ }
+ }
+}
diff --git a/library/Zend/Feed/Reader/Entry/EntryInterface.php b/library/Zend/Feed/Reader/Entry/EntryInterface.php
new file mode 100755
index 0000000000..86fea3ec58
--- /dev/null
+++ b/library/Zend/Feed/Reader/Entry/EntryInterface.php
@@ -0,0 +1,129 @@
+xpathQueryRss = '//item[' . ($this->entryKey+1) . ']';
+ $this->xpathQueryRdf = '//rss:item[' . ($this->entryKey+1) . ']';
+
+ $manager = Reader\Reader::getExtensionManager();
+ $extensions = array(
+ 'DublinCore\Entry',
+ 'Content\Entry',
+ 'Atom\Entry',
+ 'WellFormedWeb\Entry',
+ 'Slash\Entry',
+ 'Thread\Entry',
+ );
+ foreach ($extensions as $name) {
+ $extension = $manager->get($name);
+ $extension->setEntryElement($entry);
+ $extension->setEntryKey($entryKey);
+ $extension->setType($type);
+ $this->extensions[$name] = $extension;
+ }
+ }
+
+ /**
+ * Get an author entry
+ *
+ * @param int $index
+ * @return string
+ */
+ public function getAuthor($index = 0)
+ {
+ $authors = $this->getAuthors();
+
+ if (isset($authors[$index])) {
+ return $authors[$index];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get an array with feed authors
+ *
+ * @return array
+ */
+ public function getAuthors()
+ {
+ if (array_key_exists('authors', $this->data)) {
+ return $this->data['authors'];
+ }
+
+ $authors = array();
+ $authorsDc = $this->getExtension('DublinCore')->getAuthors();
+ if (!empty($authorsDc)) {
+ foreach ($authorsDc as $author) {
+ $authors[] = array(
+ 'name' => $author['name']
+ );
+ }
+ }
+
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10
+ && $this->getType() !== Reader\Reader::TYPE_RSS_090) {
+ $list = $this->xpath->query($this->xpathQueryRss . '//author');
+ } else {
+ $list = $this->xpath->query($this->xpathQueryRdf . '//rss:author');
+ }
+ if ($list->length) {
+ foreach ($list as $author) {
+ $string = trim($author->nodeValue);
+ $email = null;
+ $name = null;
+ $data = array();
+ // Pretty rough parsing - but it's a catchall
+ if (preg_match("/^.*@[^ ]*/", $string, $matches)) {
+ $data['email'] = trim($matches[0]);
+ if (preg_match("/\((.*)\)$/", $string, $matches)) {
+ $data['name'] = $matches[1];
+ }
+ $authors[] = $data;
+ }
+ }
+ }
+
+ if (count($authors) == 0) {
+ $authors = $this->getExtension('Atom')->getAuthors();
+ } else {
+ $authors = new Reader\Collection\Author(
+ Reader\Reader::arrayUnique($authors)
+ );
+ }
+
+ if (count($authors) == 0) {
+ $authors = null;
+ }
+
+ $this->data['authors'] = $authors;
+
+ return $this->data['authors'];
+ }
+
+ /**
+ * Get the entry content
+ *
+ * @return string
+ */
+ public function getContent()
+ {
+ if (array_key_exists('content', $this->data)) {
+ return $this->data['content'];
+ }
+
+ $content = $this->getExtension('Content')->getContent();
+
+ if (!$content) {
+ $content = $this->getDescription();
+ }
+
+ if (empty($content)) {
+ $content = $this->getExtension('Atom')->getContent();
+ }
+
+ $this->data['content'] = $content;
+
+ return $this->data['content'];
+ }
+
+ /**
+ * Get the entry's date of creation
+ *
+ * @return string
+ */
+ public function getDateCreated()
+ {
+ return $this->getDateModified();
+ }
+
+ /**
+ * Get the entry's date of modification
+ *
+ * @throws Exception\RuntimeException
+ * @return string
+ */
+ public function getDateModified()
+ {
+ if (array_key_exists('datemodified', $this->data)) {
+ return $this->data['datemodified'];
+ }
+
+ $dateModified = null;
+ $date = null;
+
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10
+ && $this->getType() !== Reader\Reader::TYPE_RSS_090
+ ) {
+ $dateModified = $this->xpath->evaluate('string(' . $this->xpathQueryRss . '/pubDate)');
+ if ($dateModified) {
+ $dateModifiedParsed = strtotime($dateModified);
+ if ($dateModifiedParsed) {
+ $date = new DateTime('@' . $dateModifiedParsed);
+ } else {
+ $dateStandards = array(DateTime::RSS, DateTime::RFC822,
+ DateTime::RFC2822, null);
+ foreach ($dateStandards as $standard) {
+ try {
+ $date = date_create_from_format($standard, $dateModified);
+ break;
+ } catch (\Exception $e) {
+ if ($standard == null) {
+ throw new Exception\RuntimeException(
+ 'Could not load date due to unrecognised'
+ .' format (should follow RFC 822 or 2822):'
+ . $e->getMessage(),
+ 0, $e
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (!$date) {
+ $date = $this->getExtension('DublinCore')->getDate();
+ }
+
+ if (!$date) {
+ $date = $this->getExtension('Atom')->getDateModified();
+ }
+
+ if (!$date) {
+ $date = null;
+ }
+
+ $this->data['datemodified'] = $date;
+
+ return $this->data['datemodified'];
+ }
+
+ /**
+ * Get the entry description
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ if (array_key_exists('description', $this->data)) {
+ return $this->data['description'];
+ }
+
+ $description = null;
+
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10
+ && $this->getType() !== Reader\Reader::TYPE_RSS_090
+ ) {
+ $description = $this->xpath->evaluate('string(' . $this->xpathQueryRss . '/description)');
+ } else {
+ $description = $this->xpath->evaluate('string(' . $this->xpathQueryRdf . '/rss:description)');
+ }
+
+ if (!$description) {
+ $description = $this->getExtension('DublinCore')->getDescription();
+ }
+
+ if (empty($description)) {
+ $description = $this->getExtension('Atom')->getDescription();
+ }
+
+ if (!$description) {
+ $description = null;
+ }
+
+ $this->data['description'] = $description;
+
+ return $this->data['description'];
+ }
+
+ /**
+ * Get the entry enclosure
+ * @return string
+ */
+ public function getEnclosure()
+ {
+ if (array_key_exists('enclosure', $this->data)) {
+ return $this->data['enclosure'];
+ }
+
+ $enclosure = null;
+
+ if ($this->getType() == Reader\Reader::TYPE_RSS_20) {
+ $nodeList = $this->xpath->query($this->xpathQueryRss . '/enclosure');
+
+ if ($nodeList->length > 0) {
+ $enclosure = new \stdClass();
+ $enclosure->url = $nodeList->item(0)->getAttribute('url');
+ $enclosure->length = $nodeList->item(0)->getAttribute('length');
+ $enclosure->type = $nodeList->item(0)->getAttribute('type');
+ }
+ }
+
+ if (!$enclosure) {
+ $enclosure = $this->getExtension('Atom')->getEnclosure();
+ }
+
+ $this->data['enclosure'] = $enclosure;
+
+ return $this->data['enclosure'];
+ }
+
+ /**
+ * Get the entry ID
+ *
+ * @return string
+ */
+ public function getId()
+ {
+ if (array_key_exists('id', $this->data)) {
+ return $this->data['id'];
+ }
+
+ $id = null;
+
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10
+ && $this->getType() !== Reader\Reader::TYPE_RSS_090
+ ) {
+ $id = $this->xpath->evaluate('string(' . $this->xpathQueryRss . '/guid)');
+ }
+
+ if (!$id) {
+ $id = $this->getExtension('DublinCore')->getId();
+ }
+
+ if (empty($id)) {
+ $id = $this->getExtension('Atom')->getId();
+ }
+
+ if (!$id) {
+ if ($this->getPermalink()) {
+ $id = $this->getPermalink();
+ } elseif ($this->getTitle()) {
+ $id = $this->getTitle();
+ } else {
+ $id = null;
+ }
+ }
+
+ $this->data['id'] = $id;
+
+ return $this->data['id'];
+ }
+
+ /**
+ * Get a specific link
+ *
+ * @param int $index
+ * @return string
+ */
+ public function getLink($index = 0)
+ {
+ if (!array_key_exists('links', $this->data)) {
+ $this->getLinks();
+ }
+
+ if (isset($this->data['links'][$index])) {
+ return $this->data['links'][$index];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get all links
+ *
+ * @return array
+ */
+ public function getLinks()
+ {
+ if (array_key_exists('links', $this->data)) {
+ return $this->data['links'];
+ }
+
+ $links = array();
+
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
+ $this->getType() !== Reader\Reader::TYPE_RSS_090) {
+ $list = $this->xpath->query($this->xpathQueryRss . '//link');
+ } else {
+ $list = $this->xpath->query($this->xpathQueryRdf . '//rss:link');
+ }
+
+ if (!$list->length) {
+ $links = $this->getExtension('Atom')->getLinks();
+ } else {
+ foreach ($list as $link) {
+ $links[] = $link->nodeValue;
+ }
+ }
+
+ $this->data['links'] = $links;
+
+ return $this->data['links'];
+ }
+
+ /**
+ * Get all categories
+ *
+ * @return Reader\Collection\Category
+ */
+ public function getCategories()
+ {
+ if (array_key_exists('categories', $this->data)) {
+ return $this->data['categories'];
+ }
+
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
+ $this->getType() !== Reader\Reader::TYPE_RSS_090) {
+ $list = $this->xpath->query($this->xpathQueryRss . '//category');
+ } else {
+ $list = $this->xpath->query($this->xpathQueryRdf . '//rss:category');
+ }
+
+ if ($list->length) {
+ $categoryCollection = new Reader\Collection\Category;
+ foreach ($list as $category) {
+ $categoryCollection[] = array(
+ 'term' => $category->nodeValue,
+ 'scheme' => $category->getAttribute('domain'),
+ 'label' => $category->nodeValue,
+ );
+ }
+ } else {
+ $categoryCollection = $this->getExtension('DublinCore')->getCategories();
+ }
+
+ if (count($categoryCollection) == 0) {
+ $categoryCollection = $this->getExtension('Atom')->getCategories();
+ }
+
+ $this->data['categories'] = $categoryCollection;
+
+ return $this->data['categories'];
+ }
+
+ /**
+ * Get a permalink to the entry
+ *
+ * @return string
+ */
+ public function getPermalink()
+ {
+ return $this->getLink(0);
+ }
+
+ /**
+ * Get the entry title
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ if (array_key_exists('title', $this->data)) {
+ return $this->data['title'];
+ }
+
+ $title = null;
+
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10
+ && $this->getType() !== Reader\Reader::TYPE_RSS_090
+ ) {
+ $title = $this->xpath->evaluate('string(' . $this->xpathQueryRss . '/title)');
+ } else {
+ $title = $this->xpath->evaluate('string(' . $this->xpathQueryRdf . '/rss:title)');
+ }
+
+ if (!$title) {
+ $title = $this->getExtension('DublinCore')->getTitle();
+ }
+
+ if (!$title) {
+ $title = $this->getExtension('Atom')->getTitle();
+ }
+
+ if (!$title) {
+ $title = null;
+ }
+
+ $this->data['title'] = $title;
+
+ return $this->data['title'];
+ }
+
+ /**
+ * Get the number of comments/replies for current entry
+ *
+ * @return string|null
+ */
+ public function getCommentCount()
+ {
+ if (array_key_exists('commentcount', $this->data)) {
+ return $this->data['commentcount'];
+ }
+
+ $commentcount = $this->getExtension('Slash')->getCommentCount();
+
+ if (!$commentcount) {
+ $commentcount = $this->getExtension('Thread')->getCommentCount();
+ }
+
+ if (!$commentcount) {
+ $commentcount = $this->getExtension('Atom')->getCommentCount();
+ }
+
+ if (!$commentcount) {
+ $commentcount = null;
+ }
+
+ $this->data['commentcount'] = $commentcount;
+
+ return $this->data['commentcount'];
+ }
+
+ /**
+ * Returns a URI pointing to the HTML page where comments can be made on this entry
+ *
+ * @return string
+ */
+ public function getCommentLink()
+ {
+ if (array_key_exists('commentlink', $this->data)) {
+ return $this->data['commentlink'];
+ }
+
+ $commentlink = null;
+
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10
+ && $this->getType() !== Reader\Reader::TYPE_RSS_090
+ ) {
+ $commentlink = $this->xpath->evaluate('string(' . $this->xpathQueryRss . '/comments)');
+ }
+
+ if (!$commentlink) {
+ $commentlink = $this->getExtension('Atom')->getCommentLink();
+ }
+
+ if (!$commentlink) {
+ $commentlink = null;
+ }
+
+ $this->data['commentlink'] = $commentlink;
+
+ return $this->data['commentlink'];
+ }
+
+ /**
+ * Returns a URI pointing to a feed of all comments for this entry
+ *
+ * @return string
+ */
+ public function getCommentFeedLink()
+ {
+ if (array_key_exists('commentfeedlink', $this->data)) {
+ return $this->data['commentfeedlink'];
+ }
+
+ $commentfeedlink = $this->getExtension('WellFormedWeb')->getCommentFeedLink();
+
+ if (!$commentfeedlink) {
+ $commentfeedlink = $this->getExtension('Atom')->getCommentFeedLink('rss');
+ }
+
+ if (!$commentfeedlink) {
+ $commentfeedlink = $this->getExtension('Atom')->getCommentFeedLink('rdf');
+ }
+
+ if (!$commentfeedlink) {
+ $commentfeedlink = null;
+ }
+
+ $this->data['commentfeedlink'] = $commentfeedlink;
+
+ return $this->data['commentfeedlink'];
+ }
+
+ /**
+ * Set the XPath query (incl. on all Extensions)
+ *
+ * @param DOMXPath $xpath
+ * @return void
+ */
+ public function setXpath(DOMXPath $xpath)
+ {
+ parent::setXpath($xpath);
+ foreach ($this->extensions as $extension) {
+ $extension->setXpath($this->xpath);
+ }
+ }
+}
diff --git a/library/Zend/Feed/Reader/Exception/BadMethodCallException.php b/library/Zend/Feed/Reader/Exception/BadMethodCallException.php
new file mode 100755
index 0000000000..3e265088a5
--- /dev/null
+++ b/library/Zend/Feed/Reader/Exception/BadMethodCallException.php
@@ -0,0 +1,18 @@
+entry = $entry;
+ $this->domDocument = $entry->ownerDocument;
+ return $this;
+ }
+
+ /**
+ * Get the entry DOMElement
+ *
+ * @return DOMElement
+ */
+ public function getEntryElement()
+ {
+ return $this->entry;
+ }
+
+ /**
+ * Set the entry key
+ *
+ * @param string $entryKey
+ * @return AbstractEntry
+ */
+ public function setEntryKey($entryKey)
+ {
+ $this->entryKey = $entryKey;
+ return $this;
+ }
+
+ /**
+ * Get the DOM
+ *
+ * @return DOMDocument
+ */
+ public function getDomDocument()
+ {
+ return $this->domDocument;
+ }
+
+ /**
+ * Get the Entry's encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ $assumed = $this->getDomDocument()->encoding;
+ return $assumed;
+ }
+
+ /**
+ * Set the entry type
+ *
+ * Has side effect of setting xpath prefix
+ *
+ * @param string $type
+ * @return AbstractEntry
+ */
+ public function setType($type)
+ {
+ if (null === $type) {
+ $this->data['type'] = null;
+ return $this;
+ }
+
+ $this->data['type'] = $type;
+ if ($type === Reader\Reader::TYPE_RSS_10
+ || $type === Reader\Reader::TYPE_RSS_090
+ ) {
+ $this->setXpathPrefix('//rss:item[' . ($this->entryKey + 1) . ']');
+ return $this;
+ }
+
+ if ($type === Reader\Reader::TYPE_ATOM_10
+ || $type === Reader\Reader::TYPE_ATOM_03
+ ) {
+ $this->setXpathPrefix('//atom:entry[' . ($this->entryKey + 1) . ']');
+ return $this;
+ }
+
+ $this->setXpathPrefix('//item[' . ($this->entryKey + 1) . ']');
+ return $this;
+ }
+
+ /**
+ * Get the entry type
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ $type = $this->data['type'];
+ if ($type === null) {
+ $type = Reader\Reader::detectType($this->getEntryElement(), true);
+ $this->setType($type);
+ }
+
+ return $type;
+ }
+
+ /**
+ * Set the XPath query
+ *
+ * @param DOMXPath $xpath
+ * @return AbstractEntry
+ */
+ public function setXpath(DOMXPath $xpath)
+ {
+ $this->xpath = $xpath;
+ $this->registerNamespaces();
+ return $this;
+ }
+
+ /**
+ * Get the XPath query object
+ *
+ * @return DOMXPath
+ */
+ public function getXpath()
+ {
+ if (!$this->xpath) {
+ $this->setXpath(new DOMXPath($this->getDomDocument()));
+ }
+ return $this->xpath;
+ }
+
+ /**
+ * Serialize the entry to an array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Get the XPath prefix
+ *
+ * @return string
+ */
+ public function getXpathPrefix()
+ {
+ return $this->xpathPrefix;
+ }
+
+ /**
+ * Set the XPath prefix
+ *
+ * @param string $prefix
+ * @return AbstractEntry
+ */
+ public function setXpathPrefix($prefix)
+ {
+ $this->xpathPrefix = $prefix;
+ return $this;
+ }
+
+ /**
+ * Register XML namespaces
+ *
+ * @return void
+ */
+ abstract protected function registerNamespaces();
+}
diff --git a/library/Zend/Feed/Reader/Extension/AbstractFeed.php b/library/Zend/Feed/Reader/Extension/AbstractFeed.php
new file mode 100755
index 0000000000..1bea2e4980
--- /dev/null
+++ b/library/Zend/Feed/Reader/Extension/AbstractFeed.php
@@ -0,0 +1,176 @@
+domDocument = $dom;
+ return $this;
+ }
+
+ /**
+ * Get the DOM
+ *
+ * @return DOMDocument
+ */
+ public function getDomDocument()
+ {
+ return $this->domDocument;
+ }
+
+ /**
+ * Get the Feed's encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ $assumed = $this->getDomDocument()->encoding;
+ return $assumed;
+ }
+
+ /**
+ * Set the feed type
+ *
+ * @param string $type
+ * @return AbstractFeed
+ */
+ public function setType($type)
+ {
+ $this->data['type'] = $type;
+ return $this;
+ }
+
+ /**
+ * Get the feed type
+ *
+ * If null, it will attempt to autodetect the type.
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ $type = $this->data['type'];
+ if (null === $type) {
+ $type = Reader\Reader::detectType($this->getDomDocument());
+ $this->setType($type);
+ }
+ return $type;
+ }
+
+
+ /**
+ * Return the feed as an array
+ *
+ * @return array
+ */
+ public function toArray() // untested
+ {
+ return $this->data;
+ }
+
+ /**
+ * Set the XPath query
+ *
+ * @param DOMXPath $xpath
+ * @return AbstractEntry
+ */
+ public function setXpath(DOMXPath $xpath = null)
+ {
+ if (null === $xpath) {
+ $this->xpath = null;
+ return $this;
+ }
+
+ $this->xpath = $xpath;
+ $this->registerNamespaces();
+ return $this;
+ }
+
+ /**
+ * Get the DOMXPath object
+ *
+ * @return string
+ */
+ public function getXpath()
+ {
+ if (null === $this->xpath) {
+ $this->setXpath(new DOMXPath($this->getDomDocument()));
+ }
+
+ return $this->xpath;
+ }
+
+ /**
+ * Get the XPath prefix
+ *
+ * @return string
+ */
+ public function getXpathPrefix()
+ {
+ return $this->xpathPrefix;
+ }
+
+ /**
+ * Set the XPath prefix
+ *
+ * @param string $prefix
+ * @return void
+ */
+ public function setXpathPrefix($prefix)
+ {
+ $this->xpathPrefix = $prefix;
+ }
+
+ /**
+ * Register the default namespaces for the current feed format
+ */
+ abstract protected function registerNamespaces();
+}
diff --git a/library/Zend/Feed/Reader/Extension/Atom/Entry.php b/library/Zend/Feed/Reader/Extension/Atom/Entry.php
new file mode 100755
index 0000000000..9e20321cde
--- /dev/null
+++ b/library/Zend/Feed/Reader/Extension/Atom/Entry.php
@@ -0,0 +1,630 @@
+getAuthors();
+
+ if (isset($authors[$index])) {
+ return $authors[$index];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get an array with feed authors
+ *
+ * @return Collection\Author
+ */
+ public function getAuthors()
+ {
+ if (array_key_exists('authors', $this->data)) {
+ return $this->data['authors'];
+ }
+
+ $authors = array();
+ $list = $this->getXpath()->query($this->getXpathPrefix() . '//atom:author');
+
+ if (!$list->length) {
+ /**
+ * TODO: Limit query to feed level els only!
+ */
+ $list = $this->getXpath()->query('//atom:author');
+ }
+
+ if ($list->length) {
+ foreach ($list as $author) {
+ $author = $this->getAuthorFromElement($author);
+ if (!empty($author)) {
+ $authors[] = $author;
+ }
+ }
+ }
+
+ if (count($authors) == 0) {
+ $authors = new Collection\Author();
+ } else {
+ $authors = new Collection\Author(
+ Reader\Reader::arrayUnique($authors)
+ );
+ }
+
+ $this->data['authors'] = $authors;
+ return $this->data['authors'];
+ }
+
+ /**
+ * Get the entry content
+ *
+ * @return string
+ */
+ public function getContent()
+ {
+ if (array_key_exists('content', $this->data)) {
+ return $this->data['content'];
+ }
+
+ $content = null;
+
+ $el = $this->getXpath()->query($this->getXpathPrefix() . '/atom:content');
+ if ($el->length > 0) {
+ $el = $el->item(0);
+ $type = $el->getAttribute('type');
+ switch ($type) {
+ case '':
+ case 'text':
+ case 'text/plain':
+ case 'html':
+ case 'text/html':
+ $content = $el->nodeValue;
+ break;
+ case 'xhtml':
+ $this->getXpath()->registerNamespace('xhtml', 'http://www.w3.org/1999/xhtml');
+ $xhtml = $this->getXpath()->query(
+ $this->getXpathPrefix() . '/atom:content/xhtml:div'
+ )->item(0);
+ $d = new DOMDocument('1.0', $this->getEncoding());
+ $xhtmls = $d->importNode($xhtml, true);
+ $d->appendChild($xhtmls);
+ $content = $this->collectXhtml(
+ $d->saveXML(),
+ $d->lookupPrefix('http://www.w3.org/1999/xhtml')
+ );
+ break;
+ }
+ }
+
+ if (!$content) {
+ $content = $this->getDescription();
+ }
+
+ $this->data['content'] = trim($content);
+
+ return $this->data['content'];
+ }
+
+ /**
+ * Parse out XHTML to remove the namespacing
+ *
+ * @param $xhtml
+ * @param $prefix
+ * @return mixed
+ */
+ protected function collectXhtml($xhtml, $prefix)
+ {
+ if (!empty($prefix)) {
+ $prefix = $prefix . ':';
+ }
+ $matches = array(
+ "/<\?xml[^<]*>[^<]*<" . $prefix . "div[^<]*/",
+ "/<\/" . $prefix . "div>\s*$/"
+ );
+ $xhtml = preg_replace($matches, '', $xhtml);
+ if (!empty($prefix)) {
+ $xhtml = preg_replace("/(<[\/]?)" . $prefix . "([a-zA-Z]+)/", '$1$2', $xhtml);
+ }
+ return $xhtml;
+ }
+
+ /**
+ * Get the entry creation date
+ *
+ * @return string
+ */
+ public function getDateCreated()
+ {
+ if (array_key_exists('datecreated', $this->data)) {
+ return $this->data['datecreated'];
+ }
+
+ $date = null;
+
+ if ($this->getAtomType() === Reader\Reader::TYPE_ATOM_03) {
+ $dateCreated = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/atom:created)');
+ } else {
+ $dateCreated = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/atom:published)');
+ }
+
+ if ($dateCreated) {
+ $date = new DateTime($dateCreated);
+ }
+
+ $this->data['datecreated'] = $date;
+
+ return $this->data['datecreated'];
+ }
+
+ /**
+ * Get the entry modification date
+ *
+ * @return string
+ */
+ public function getDateModified()
+ {
+ if (array_key_exists('datemodified', $this->data)) {
+ return $this->data['datemodified'];
+ }
+
+ $date = null;
+
+ if ($this->getAtomType() === Reader\Reader::TYPE_ATOM_03) {
+ $dateModified = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/atom:modified)');
+ } else {
+ $dateModified = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/atom:updated)');
+ }
+
+ if ($dateModified) {
+ $date = new DateTime($dateModified);
+ }
+
+ $this->data['datemodified'] = $date;
+
+ return $this->data['datemodified'];
+ }
+
+ /**
+ * Get the entry description
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ if (array_key_exists('description', $this->data)) {
+ return $this->data['description'];
+ }
+
+ $description = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/atom:summary)');
+
+ if (!$description) {
+ $description = null;
+ }
+
+ $this->data['description'] = $description;
+
+ return $this->data['description'];
+ }
+
+ /**
+ * Get the entry enclosure
+ *
+ * @return string
+ */
+ public function getEnclosure()
+ {
+ if (array_key_exists('enclosure', $this->data)) {
+ return $this->data['enclosure'];
+ }
+
+ $enclosure = null;
+
+ $nodeList = $this->getXpath()->query($this->getXpathPrefix() . '/atom:link[@rel="enclosure"]');
+
+ if ($nodeList->length > 0) {
+ $enclosure = new stdClass();
+ $enclosure->url = $nodeList->item(0)->getAttribute('href');
+ $enclosure->length = $nodeList->item(0)->getAttribute('length');
+ $enclosure->type = $nodeList->item(0)->getAttribute('type');
+ }
+
+ $this->data['enclosure'] = $enclosure;
+
+ return $this->data['enclosure'];
+ }
+
+ /**
+ * Get the entry ID
+ *
+ * @return string
+ */
+ public function getId()
+ {
+ if (array_key_exists('id', $this->data)) {
+ return $this->data['id'];
+ }
+
+ $id = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/atom:id)');
+
+ if (!$id) {
+ if ($this->getPermalink()) {
+ $id = $this->getPermalink();
+ } elseif ($this->getTitle()) {
+ $id = $this->getTitle();
+ } else {
+ $id = null;
+ }
+ }
+
+ $this->data['id'] = $id;
+
+ return $this->data['id'];
+ }
+
+ /**
+ * Get the base URI of the feed (if set).
+ *
+ * @return string|null
+ */
+ public function getBaseUrl()
+ {
+ if (array_key_exists('baseUrl', $this->data)) {
+ return $this->data['baseUrl'];
+ }
+
+ $baseUrl = $this->getXpath()->evaluate('string('
+ . $this->getXpathPrefix() . '/@xml:base[1]'
+ . ')');
+
+ if (!$baseUrl) {
+ $baseUrl = $this->getXpath()->evaluate('string(//@xml:base[1])');
+ }
+
+ if (!$baseUrl) {
+ $baseUrl = null;
+ }
+
+ $this->data['baseUrl'] = $baseUrl;
+
+ return $this->data['baseUrl'];
+ }
+
+ /**
+ * Get a specific link
+ *
+ * @param int $index
+ * @return string
+ */
+ public function getLink($index = 0)
+ {
+ if (!array_key_exists('links', $this->data)) {
+ $this->getLinks();
+ }
+
+ if (isset($this->data['links'][$index])) {
+ return $this->data['links'][$index];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get all links
+ *
+ * @return array
+ */
+ public function getLinks()
+ {
+ if (array_key_exists('links', $this->data)) {
+ return $this->data['links'];
+ }
+
+ $links = array();
+
+ $list = $this->getXpath()->query(
+ $this->getXpathPrefix() . '//atom:link[@rel="alternate"]/@href' . '|' .
+ $this->getXpathPrefix() . '//atom:link[not(@rel)]/@href'
+ );
+
+ if ($list->length) {
+ foreach ($list as $link) {
+ $links[] = $this->absolutiseUri($link->value);
+ }
+ }
+
+ $this->data['links'] = $links;
+
+ return $this->data['links'];
+ }
+
+ /**
+ * Get a permalink to the entry
+ *
+ * @return string
+ */
+ public function getPermalink()
+ {
+ return $this->getLink(0);
+ }
+
+ /**
+ * Get the entry title
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ if (array_key_exists('title', $this->data)) {
+ return $this->data['title'];
+ }
+
+ $title = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/atom:title)');
+
+ if (!$title) {
+ $title = null;
+ }
+
+ $this->data['title'] = $title;
+
+ return $this->data['title'];
+ }
+
+ /**
+ * Get the number of comments/replies for current entry
+ *
+ * @return int
+ */
+ public function getCommentCount()
+ {
+ if (array_key_exists('commentcount', $this->data)) {
+ return $this->data['commentcount'];
+ }
+
+ $count = null;
+
+ $this->getXpath()->registerNamespace('thread10', 'http://purl.org/syndication/thread/1.0');
+ $list = $this->getXpath()->query(
+ $this->getXpathPrefix() . '//atom:link[@rel="replies"]/@thread10:count'
+ );
+
+ if ($list->length) {
+ $count = $list->item(0)->value;
+ }
+
+ $this->data['commentcount'] = $count;
+
+ return $this->data['commentcount'];
+ }
+
+ /**
+ * Returns a URI pointing to the HTML page where comments can be made on this entry
+ *
+ * @return string
+ */
+ public function getCommentLink()
+ {
+ if (array_key_exists('commentlink', $this->data)) {
+ return $this->data['commentlink'];
+ }
+
+ $link = null;
+
+ $list = $this->getXpath()->query(
+ $this->getXpathPrefix() . '//atom:link[@rel="replies" and @type="text/html"]/@href'
+ );
+
+ if ($list->length) {
+ $link = $list->item(0)->value;
+ $link = $this->absolutiseUri($link);
+ }
+
+ $this->data['commentlink'] = $link;
+
+ return $this->data['commentlink'];
+ }
+
+ /**
+ * Returns a URI pointing to a feed of all comments for this entry
+ *
+ * @param string $type
+ * @return string
+ */
+ public function getCommentFeedLink($type = 'atom')
+ {
+ if (array_key_exists('commentfeedlink', $this->data)) {
+ return $this->data['commentfeedlink'];
+ }
+
+ $link = null;
+
+ $list = $this->getXpath()->query(
+ $this->getXpathPrefix() . '//atom:link[@rel="replies" and @type="application/' . $type.'+xml"]/@href'
+ );
+
+ if ($list->length) {
+ $link = $list->item(0)->value;
+ $link = $this->absolutiseUri($link);
+ }
+
+ $this->data['commentfeedlink'] = $link;
+
+ return $this->data['commentfeedlink'];
+ }
+
+ /**
+ * Get all categories
+ *
+ * @return Collection\Category
+ */
+ public function getCategories()
+ {
+ if (array_key_exists('categories', $this->data)) {
+ return $this->data['categories'];
+ }
+
+ if ($this->getAtomType() == Reader\Reader::TYPE_ATOM_10) {
+ $list = $this->getXpath()->query($this->getXpathPrefix() . '//atom:category');
+ } else {
+ /**
+ * Since Atom 0.3 did not support categories, it would have used the
+ * Dublin Core extension. However there is a small possibility Atom 0.3
+ * may have been retrofitted to use Atom 1.0 instead.
+ */
+ $this->getXpath()->registerNamespace('atom10', Reader\Reader::NAMESPACE_ATOM_10);
+ $list = $this->getXpath()->query($this->getXpathPrefix() . '//atom10:category');
+ }
+
+ if ($list->length) {
+ $categoryCollection = new Collection\Category;
+ foreach ($list as $category) {
+ $categoryCollection[] = array(
+ 'term' => $category->getAttribute('term'),
+ 'scheme' => $category->getAttribute('scheme'),
+ 'label' => $category->getAttribute('label')
+ );
+ }
+ } else {
+ return new Collection\Category;
+ }
+
+ $this->data['categories'] = $categoryCollection;
+
+ return $this->data['categories'];
+ }
+
+ /**
+ * Get source feed metadata from the entry
+ *
+ * @return Reader\Feed\Atom\Source|null
+ */
+ public function getSource()
+ {
+ if (array_key_exists('source', $this->data)) {
+ return $this->data['source'];
+ }
+
+ $source = null;
+ // TODO: Investigate why _getAtomType() fails here. Is it even needed?
+ if ($this->getType() == Reader\Reader::TYPE_ATOM_10) {
+ $list = $this->getXpath()->query($this->getXpathPrefix() . '/atom:source[1]');
+ if ($list->length) {
+ $element = $list->item(0);
+ $source = new Reader\Feed\Atom\Source($element, $this->getXpathPrefix());
+ }
+ }
+
+ $this->data['source'] = $source;
+ return $this->data['source'];
+ }
+
+ /**
+ * Attempt to absolutise the URI, i.e. if a relative URI apply the
+ * xml:base value as a prefix to turn into an absolute URI.
+ *
+ * @param $link
+ * @return string
+ */
+ protected function absolutiseUri($link)
+ {
+ if (!Uri::factory($link)->isAbsolute()) {
+ if ($this->getBaseUrl() !== null) {
+ $link = $this->getBaseUrl() . $link;
+ if (!Uri::factory($link)->isValid()) {
+ $link = null;
+ }
+ }
+ }
+ return $link;
+ }
+
+ /**
+ * Get an author entry
+ *
+ * @param DOMElement $element
+ * @return string
+ */
+ protected function getAuthorFromElement(DOMElement $element)
+ {
+ $author = array();
+
+ $emailNode = $element->getElementsByTagName('email');
+ $nameNode = $element->getElementsByTagName('name');
+ $uriNode = $element->getElementsByTagName('uri');
+
+ if ($emailNode->length && strlen($emailNode->item(0)->nodeValue) > 0) {
+ $author['email'] = $emailNode->item(0)->nodeValue;
+ }
+
+ if ($nameNode->length && strlen($nameNode->item(0)->nodeValue) > 0) {
+ $author['name'] = $nameNode->item(0)->nodeValue;
+ }
+
+ if ($uriNode->length && strlen($uriNode->item(0)->nodeValue) > 0) {
+ $author['uri'] = $uriNode->item(0)->nodeValue;
+ }
+
+ if (empty($author)) {
+ return null;
+ }
+ return $author;
+ }
+
+ /**
+ * Register the default namespaces for the current feed format
+ */
+ protected function registerNamespaces()
+ {
+ switch ($this->getAtomType()) {
+ case Reader\Reader::TYPE_ATOM_03:
+ $this->getXpath()->registerNamespace('atom', Reader\Reader::NAMESPACE_ATOM_03);
+ break;
+ default:
+ $this->getXpath()->registerNamespace('atom', Reader\Reader::NAMESPACE_ATOM_10);
+ break;
+ }
+ }
+
+ /**
+ * Detect the presence of any Atom namespaces in use
+ *
+ * @return string
+ */
+ protected function getAtomType()
+ {
+ $dom = $this->getDomDocument();
+ $prefixAtom03 = $dom->lookupPrefix(Reader\Reader::NAMESPACE_ATOM_03);
+ $prefixAtom10 = $dom->lookupPrefix(Reader\Reader::NAMESPACE_ATOM_10);
+ if ($dom->isDefaultNamespace(Reader\Reader::NAMESPACE_ATOM_03)
+ || !empty($prefixAtom03)) {
+ return Reader\Reader::TYPE_ATOM_03;
+ }
+ if ($dom->isDefaultNamespace(Reader\Reader::NAMESPACE_ATOM_10)
+ || !empty($prefixAtom10)) {
+ return Reader\Reader::TYPE_ATOM_10;
+ }
+ }
+}
diff --git a/library/Zend/Feed/Reader/Extension/Atom/Feed.php b/library/Zend/Feed/Reader/Extension/Atom/Feed.php
new file mode 100755
index 0000000000..986d23fdb3
--- /dev/null
+++ b/library/Zend/Feed/Reader/Extension/Atom/Feed.php
@@ -0,0 +1,536 @@
+getAuthors();
+
+ if (isset($authors[$index])) {
+ return $authors[$index];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get an array with feed authors
+ *
+ * @return Collection\Author
+ */
+ public function getAuthors()
+ {
+ if (array_key_exists('authors', $this->data)) {
+ return $this->data['authors'];
+ }
+
+ $list = $this->xpath->query('//atom:author');
+
+ $authors = array();
+
+ if ($list->length) {
+ foreach ($list as $author) {
+ $author = $this->getAuthorFromElement($author);
+ if (!empty($author)) {
+ $authors[] = $author;
+ }
+ }
+ }
+
+ if (count($authors) == 0) {
+ $authors = new Collection\Author();
+ } else {
+ $authors = new Collection\Author(
+ Reader\Reader::arrayUnique($authors)
+ );
+ }
+
+ $this->data['authors'] = $authors;
+
+ return $this->data['authors'];
+ }
+
+ /**
+ * Get the copyright entry
+ *
+ * @return string|null
+ */
+ public function getCopyright()
+ {
+ if (array_key_exists('copyright', $this->data)) {
+ return $this->data['copyright'];
+ }
+
+ $copyright = null;
+
+ if ($this->getType() === Reader\Reader::TYPE_ATOM_03) {
+ $copyright = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:copyright)');
+ } else {
+ $copyright = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:rights)');
+ }
+
+ if (!$copyright) {
+ $copyright = null;
+ }
+
+ $this->data['copyright'] = $copyright;
+
+ return $this->data['copyright'];
+ }
+
+ /**
+ * Get the feed creation date
+ *
+ * @return DateTime|null
+ */
+ public function getDateCreated()
+ {
+ if (array_key_exists('datecreated', $this->data)) {
+ return $this->data['datecreated'];
+ }
+
+ $date = null;
+
+ if ($this->getType() === Reader\Reader::TYPE_ATOM_03) {
+ $dateCreated = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:created)');
+ } else {
+ $dateCreated = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:published)');
+ }
+
+ if ($dateCreated) {
+ $date = new DateTime($dateCreated);
+ }
+
+ $this->data['datecreated'] = $date;
+
+ return $this->data['datecreated'];
+ }
+
+ /**
+ * Get the feed modification date
+ *
+ * @return DateTime|null
+ */
+ public function getDateModified()
+ {
+ if (array_key_exists('datemodified', $this->data)) {
+ return $this->data['datemodified'];
+ }
+
+ $date = null;
+
+ if ($this->getType() === Reader\Reader::TYPE_ATOM_03) {
+ $dateModified = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:modified)');
+ } else {
+ $dateModified = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:updated)');
+ }
+
+ if ($dateModified) {
+ $date = new DateTime($dateModified);
+ }
+
+ $this->data['datemodified'] = $date;
+
+ return $this->data['datemodified'];
+ }
+
+ /**
+ * Get the feed description
+ *
+ * @return string|null
+ */
+ public function getDescription()
+ {
+ if (array_key_exists('description', $this->data)) {
+ return $this->data['description'];
+ }
+
+ $description = null;
+
+ if ($this->getType() === Reader\Reader::TYPE_ATOM_03) {
+ $description = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:tagline)');
+ } else {
+ $description = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:subtitle)');
+ }
+
+ if (!$description) {
+ $description = null;
+ }
+
+ $this->data['description'] = $description;
+
+ return $this->data['description'];
+ }
+
+ /**
+ * Get the feed generator entry
+ *
+ * @return string|null
+ */
+ public function getGenerator()
+ {
+ if (array_key_exists('generator', $this->data)) {
+ return $this->data['generator'];
+ }
+ // TODO: Add uri support
+ $generator = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:generator)');
+
+ if (!$generator) {
+ $generator = null;
+ }
+
+ $this->data['generator'] = $generator;
+
+ return $this->data['generator'];
+ }
+
+ /**
+ * Get the feed ID
+ *
+ * @return string|null
+ */
+ public function getId()
+ {
+ if (array_key_exists('id', $this->data)) {
+ return $this->data['id'];
+ }
+
+ $id = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:id)');
+
+ if (!$id) {
+ if ($this->getLink()) {
+ $id = $this->getLink();
+ } elseif ($this->getTitle()) {
+ $id = $this->getTitle();
+ } else {
+ $id = null;
+ }
+ }
+
+ $this->data['id'] = $id;
+
+ return $this->data['id'];
+ }
+
+ /**
+ * Get the feed language
+ *
+ * @return string|null
+ */
+ public function getLanguage()
+ {
+ if (array_key_exists('language', $this->data)) {
+ return $this->data['language'];
+ }
+
+ $language = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:lang)');
+
+ if (!$language) {
+ $language = $this->xpath->evaluate('string(//@xml:lang[1])');
+ }
+
+ if (!$language) {
+ $language = null;
+ }
+
+ $this->data['language'] = $language;
+
+ return $this->data['language'];
+ }
+
+ /**
+ * Get the feed image
+ *
+ * @return array|null
+ */
+ public function getImage()
+ {
+ if (array_key_exists('image', $this->data)) {
+ return $this->data['image'];
+ }
+
+ $imageUrl = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:logo)');
+
+ if (!$imageUrl) {
+ $image = null;
+ } else {
+ $image = array('uri' => $imageUrl);
+ }
+
+ $this->data['image'] = $image;
+
+ return $this->data['image'];
+ }
+
+ /**
+ * Get the base URI of the feed (if set).
+ *
+ * @return string|null
+ */
+ public function getBaseUrl()
+ {
+ if (array_key_exists('baseUrl', $this->data)) {
+ return $this->data['baseUrl'];
+ }
+
+ $baseUrl = $this->xpath->evaluate('string(//@xml:base[1])');
+
+ if (!$baseUrl) {
+ $baseUrl = null;
+ }
+ $this->data['baseUrl'] = $baseUrl;
+
+ return $this->data['baseUrl'];
+ }
+
+ /**
+ * Get a link to the source website
+ *
+ * @return string|null
+ */
+ public function getLink()
+ {
+ if (array_key_exists('link', $this->data)) {
+ return $this->data['link'];
+ }
+
+ $link = null;
+
+ $list = $this->xpath->query(
+ $this->getXpathPrefix() . '/atom:link[@rel="alternate"]/@href' . '|' .
+ $this->getXpathPrefix() . '/atom:link[not(@rel)]/@href'
+ );
+
+ if ($list->length) {
+ $link = $list->item(0)->nodeValue;
+ $link = $this->absolutiseUri($link);
+ }
+
+ $this->data['link'] = $link;
+
+ return $this->data['link'];
+ }
+
+ /**
+ * Get a link to the feed's XML Url
+ *
+ * @return string|null
+ */
+ public function getFeedLink()
+ {
+ if (array_key_exists('feedlink', $this->data)) {
+ return $this->data['feedlink'];
+ }
+
+ $link = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:link[@rel="self"]/@href)');
+
+ $link = $this->absolutiseUri($link);
+
+ $this->data['feedlink'] = $link;
+
+ return $this->data['feedlink'];
+ }
+
+ /**
+ * Get an array of any supported Pusubhubbub endpoints
+ *
+ * @return array|null
+ */
+ public function getHubs()
+ {
+ if (array_key_exists('hubs', $this->data)) {
+ return $this->data['hubs'];
+ }
+ $hubs = array();
+
+ $list = $this->xpath->query($this->getXpathPrefix()
+ . '//atom:link[@rel="hub"]/@href');
+
+ if ($list->length) {
+ foreach ($list as $uri) {
+ $hubs[] = $this->absolutiseUri($uri->nodeValue);
+ }
+ } else {
+ $hubs = null;
+ }
+
+ $this->data['hubs'] = $hubs;
+
+ return $this->data['hubs'];
+ }
+
+ /**
+ * Get the feed title
+ *
+ * @return string|null
+ */
+ public function getTitle()
+ {
+ if (array_key_exists('title', $this->data)) {
+ return $this->data['title'];
+ }
+
+ $title = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:title)');
+
+ if (!$title) {
+ $title = null;
+ }
+
+ $this->data['title'] = $title;
+
+ return $this->data['title'];
+ }
+
+ /**
+ * Get all categories
+ *
+ * @return Collection\Category
+ */
+ public function getCategories()
+ {
+ if (array_key_exists('categories', $this->data)) {
+ return $this->data['categories'];
+ }
+
+ if ($this->getType() == Reader\Reader::TYPE_ATOM_10) {
+ $list = $this->xpath->query($this->getXpathPrefix() . '/atom:category');
+ } else {
+ /**
+ * Since Atom 0.3 did not support categories, it would have used the
+ * Dublin Core extension. However there is a small possibility Atom 0.3
+ * may have been retrofittied to use Atom 1.0 instead.
+ */
+ $this->xpath->registerNamespace('atom10', Reader\Reader::NAMESPACE_ATOM_10);
+ $list = $this->xpath->query($this->getXpathPrefix() . '/atom10:category');
+ }
+
+ if ($list->length) {
+ $categoryCollection = new Collection\Category;
+ foreach ($list as $category) {
+ $categoryCollection[] = array(
+ 'term' => $category->getAttribute('term'),
+ 'scheme' => $category->getAttribute('scheme'),
+ 'label' => $category->getAttribute('label')
+ );
+ }
+ } else {
+ return new Collection\Category;
+ }
+
+ $this->data['categories'] = $categoryCollection;
+
+ return $this->data['categories'];
+ }
+
+ /**
+ * Get an author entry in RSS format
+ *
+ * @param DOMElement $element
+ * @return string
+ */
+ protected function getAuthorFromElement(DOMElement $element)
+ {
+ $author = array();
+
+ $emailNode = $element->getElementsByTagName('email');
+ $nameNode = $element->getElementsByTagName('name');
+ $uriNode = $element->getElementsByTagName('uri');
+
+ if ($emailNode->length && strlen($emailNode->item(0)->nodeValue) > 0) {
+ $author['email'] = $emailNode->item(0)->nodeValue;
+ }
+
+ if ($nameNode->length && strlen($nameNode->item(0)->nodeValue) > 0) {
+ $author['name'] = $nameNode->item(0)->nodeValue;
+ }
+
+ if ($uriNode->length && strlen($uriNode->item(0)->nodeValue) > 0) {
+ $author['uri'] = $uriNode->item(0)->nodeValue;
+ }
+
+ if (empty($author)) {
+ return null;
+ }
+ return $author;
+ }
+
+ /**
+ * Attempt to absolutise the URI, i.e. if a relative URI apply the
+ * xml:base value as a prefix to turn into an absolute URI.
+ */
+ protected function absolutiseUri($link)
+ {
+ if (!Uri::factory($link)->isAbsolute()) {
+ if ($this->getBaseUrl() !== null) {
+ $link = $this->getBaseUrl() . $link;
+ if (!Uri::factory($link)->isValid()) {
+ $link = null;
+ }
+ }
+ }
+ return $link;
+ }
+
+ /**
+ * Register the default namespaces for the current feed format
+ */
+ protected function registerNamespaces()
+ {
+ if ($this->getType() == Reader\Reader::TYPE_ATOM_10
+ || $this->getType() == Reader\Reader::TYPE_ATOM_03
+ ) {
+ return; // pre-registered at Feed level
+ }
+ $atomDetected = $this->getAtomType();
+ switch ($atomDetected) {
+ case Reader\Reader::TYPE_ATOM_03:
+ $this->xpath->registerNamespace('atom', Reader\Reader::NAMESPACE_ATOM_03);
+ break;
+ default:
+ $this->xpath->registerNamespace('atom', Reader\Reader::NAMESPACE_ATOM_10);
+ break;
+ }
+ }
+
+ /**
+ * Detect the presence of any Atom namespaces in use
+ */
+ protected function getAtomType()
+ {
+ $dom = $this->getDomDocument();
+ $prefixAtom03 = $dom->lookupPrefix(Reader\Reader::NAMESPACE_ATOM_03);
+ $prefixAtom10 = $dom->lookupPrefix(Reader\Reader::NAMESPACE_ATOM_10);
+ if ($dom->isDefaultNamespace(Reader\Reader::NAMESPACE_ATOM_10)
+ || !empty($prefixAtom10)
+ ) {
+ return Reader\Reader::TYPE_ATOM_10;
+ }
+ if ($dom->isDefaultNamespace(Reader\Reader::NAMESPACE_ATOM_03)
+ || !empty($prefixAtom03)
+ ) {
+ return Reader\Reader::TYPE_ATOM_03;
+ }
+ }
+}
diff --git a/library/Zend/Feed/Reader/Extension/Content/Entry.php b/library/Zend/Feed/Reader/Extension/Content/Entry.php
new file mode 100755
index 0000000000..9b5f7cb355
--- /dev/null
+++ b/library/Zend/Feed/Reader/Extension/Content/Entry.php
@@ -0,0 +1,36 @@
+getType() !== Reader\Reader::TYPE_RSS_10
+ && $this->getType() !== Reader\Reader::TYPE_RSS_090
+ ) {
+ $content = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/content:encoded)');
+ } else {
+ $content = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/content:encoded)');
+ }
+ return $content;
+ }
+
+ /**
+ * Register RSS Content Module namespace
+ */
+ protected function registerNamespaces()
+ {
+ $this->xpath->registerNamespace('content', 'http://purl.org/rss/1.0/modules/content/');
+ }
+}
diff --git a/library/Zend/Feed/Reader/Extension/CreativeCommons/Entry.php b/library/Zend/Feed/Reader/Extension/CreativeCommons/Entry.php
new file mode 100755
index 0000000000..1883dc6bed
--- /dev/null
+++ b/library/Zend/Feed/Reader/Extension/CreativeCommons/Entry.php
@@ -0,0 +1,72 @@
+getLicenses();
+
+ if (isset($licenses[$index])) {
+ return $licenses[$index];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the entry licenses
+ *
+ * @return array
+ */
+ public function getLicenses()
+ {
+ $name = 'licenses';
+ if (array_key_exists($name, $this->data)) {
+ return $this->data[$name];
+ }
+
+ $licenses = array();
+ $list = $this->xpath->evaluate($this->getXpathPrefix() . '//cc:license');
+
+ if ($list->length) {
+ foreach ($list as $license) {
+ $licenses[] = $license->nodeValue;
+ }
+
+ $licenses = array_unique($licenses);
+ } else {
+ $cc = new Feed();
+ $licenses = $cc->getLicenses();
+ }
+
+ $this->data[$name] = $licenses;
+
+ return $this->data[$name];
+ }
+
+ /**
+ * Register Creative Commons namespaces
+ *
+ */
+ protected function registerNamespaces()
+ {
+ $this->xpath->registerNamespace('cc', 'http://backend.userland.com/creativeCommonsRssModule');
+ }
+}
diff --git a/library/Zend/Feed/Reader/Extension/CreativeCommons/Feed.php b/library/Zend/Feed/Reader/Extension/CreativeCommons/Feed.php
new file mode 100755
index 0000000000..99977fd44a
--- /dev/null
+++ b/library/Zend/Feed/Reader/Extension/CreativeCommons/Feed.php
@@ -0,0 +1,70 @@
+getLicenses();
+
+ if (isset($licenses[$index])) {
+ return $licenses[$index];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the entry licenses
+ *
+ * @return array
+ */
+ public function getLicenses()
+ {
+ $name = 'licenses';
+ if (array_key_exists($name, $this->data)) {
+ return $this->data[$name];
+ }
+
+ $licenses = array();
+ $list = $this->xpath->evaluate('channel/cc:license');
+
+ if ($list->length) {
+ foreach ($list as $license) {
+ $licenses[] = $license->nodeValue;
+ }
+
+ $licenses = array_unique($licenses);
+ }
+
+ $this->data[$name] = $licenses;
+
+ return $this->data[$name];
+ }
+
+ /**
+ * Register Creative Commons namespaces
+ *
+ * @return void
+ */
+ protected function registerNamespaces()
+ {
+ $this->xpath->registerNamespace('cc', 'http://backend.userland.com/creativeCommonsRssModule');
+ }
+}
diff --git a/library/Zend/Feed/Reader/Extension/DublinCore/Entry.php b/library/Zend/Feed/Reader/Extension/DublinCore/Entry.php
new file mode 100755
index 0000000000..2713353cad
--- /dev/null
+++ b/library/Zend/Feed/Reader/Extension/DublinCore/Entry.php
@@ -0,0 +1,238 @@
+getAuthors();
+
+ if (isset($authors[$index])) {
+ return $authors[$index];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get an array with feed authors
+ *
+ * @return array
+ */
+ public function getAuthors()
+ {
+ if (array_key_exists('authors', $this->data)) {
+ return $this->data['authors'];
+ }
+
+ $authors = array();
+ $list = $this->getXpath()->evaluate($this->getXpathPrefix() . '//dc11:creator');
+
+ if (!$list->length) {
+ $list = $this->getXpath()->evaluate($this->getXpathPrefix() . '//dc10:creator');
+ }
+ if (!$list->length) {
+ $list = $this->getXpath()->evaluate($this->getXpathPrefix() . '//dc11:publisher');
+
+ if (!$list->length) {
+ $list = $this->getXpath()->evaluate($this->getXpathPrefix() . '//dc10:publisher');
+ }
+ }
+
+ if ($list->length) {
+ foreach ($list as $author) {
+ $authors[] = array(
+ 'name' => $author->nodeValue
+ );
+ }
+ $authors = new Collection\Author(
+ Reader\Reader::arrayUnique($authors)
+ );
+ } else {
+ $authors = null;
+ }
+
+ $this->data['authors'] = $authors;
+
+ return $this->data['authors'];
+ }
+
+ /**
+ * Get categories (subjects under DC)
+ *
+ * @return Collection\Category
+ */
+ public function getCategories()
+ {
+ if (array_key_exists('categories', $this->data)) {
+ return $this->data['categories'];
+ }
+
+ $list = $this->getXpath()->evaluate($this->getXpathPrefix() . '//dc11:subject');
+
+ if (!$list->length) {
+ $list = $this->getXpath()->evaluate($this->getXpathPrefix() . '//dc10:subject');
+ }
+
+ if ($list->length) {
+ $categoryCollection = new Collection\Category;
+ foreach ($list as $category) {
+ $categoryCollection[] = array(
+ 'term' => $category->nodeValue,
+ 'scheme' => null,
+ 'label' => $category->nodeValue,
+ );
+ }
+ } else {
+ $categoryCollection = new Collection\Category;
+ }
+
+ $this->data['categories'] = $categoryCollection;
+ return $this->data['categories'];
+ }
+
+
+ /**
+ * Get the entry content
+ *
+ * @return string
+ */
+ public function getContent()
+ {
+ return $this->getDescription();
+ }
+
+ /**
+ * Get the entry description
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ if (array_key_exists('description', $this->data)) {
+ return $this->data['description'];
+ }
+
+ $description = null;
+ $description = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/dc11:description)');
+
+ if (!$description) {
+ $description = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/dc10:description)');
+ }
+
+ if (!$description) {
+ $description = null;
+ }
+
+ $this->data['description'] = $description;
+
+ return $this->data['description'];
+ }
+
+ /**
+ * Get the entry ID
+ *
+ * @return string
+ */
+ public function getId()
+ {
+ if (array_key_exists('id', $this->data)) {
+ return $this->data['id'];
+ }
+
+ $id = null;
+ $id = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/dc11:identifier)');
+
+ if (!$id) {
+ $id = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/dc10:identifier)');
+ }
+
+ $this->data['id'] = $id;
+
+ return $this->data['id'];
+ }
+
+ /**
+ * Get the entry title
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ if (array_key_exists('title', $this->data)) {
+ return $this->data['title'];
+ }
+
+ $title = null;
+ $title = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/dc11:title)');
+
+ if (!$title) {
+ $title = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/dc10:title)');
+ }
+
+ if (!$title) {
+ $title = null;
+ }
+
+ $this->data['title'] = $title;
+
+ return $this->data['title'];
+ }
+
+ /**
+ *
+ *
+ * @return DateTime|null
+ */
+ public function getDate()
+ {
+ if (array_key_exists('date', $this->data)) {
+ return $this->data['date'];
+ }
+
+ $d = null;
+ $date = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/dc11:date)');
+
+ if (!$date) {
+ $date = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/dc10:date)');
+ }
+
+ if ($date) {
+ $d = new DateTime($date);
+ }
+
+ $this->data['date'] = $d;
+
+ return $this->data['date'];
+ }
+
+ /**
+ * Register DC namespaces
+ *
+ * @return void
+ */
+ protected function registerNamespaces()
+ {
+ $this->getXpath()->registerNamespace('dc10', 'http://purl.org/dc/elements/1.0/');
+ $this->getXpath()->registerNamespace('dc11', 'http://purl.org/dc/elements/1.1/');
+ }
+}
diff --git a/library/Zend/Feed/Reader/Extension/DublinCore/Feed.php b/library/Zend/Feed/Reader/Extension/DublinCore/Feed.php
new file mode 100755
index 0000000000..2738ac732b
--- /dev/null
+++ b/library/Zend/Feed/Reader/Extension/DublinCore/Feed.php
@@ -0,0 +1,281 @@
+getAuthors();
+
+ if (isset($authors[$index])) {
+ return $authors[$index];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get an array with feed authors
+ *
+ * @return array
+ */
+ public function getAuthors()
+ {
+ if (array_key_exists('authors', $this->data)) {
+ return $this->data['authors'];
+ }
+
+ $authors = array();
+ $list = $this->getXpath()->query('//dc11:creator');
+
+ if (!$list->length) {
+ $list = $this->getXpath()->query('//dc10:creator');
+ }
+ if (!$list->length) {
+ $list = $this->getXpath()->query('//dc11:publisher');
+
+ if (!$list->length) {
+ $list = $this->getXpath()->query('//dc10:publisher');
+ }
+ }
+
+ if ($list->length) {
+ foreach ($list as $author) {
+ $authors[] = array(
+ 'name' => $author->nodeValue
+ );
+ }
+ $authors = new Collection\Author(
+ Reader\Reader::arrayUnique($authors)
+ );
+ } else {
+ $authors = null;
+ }
+
+ $this->data['authors'] = $authors;
+
+ return $this->data['authors'];
+ }
+
+ /**
+ * Get the copyright entry
+ *
+ * @return string|null
+ */
+ public function getCopyright()
+ {
+ if (array_key_exists('copyright', $this->data)) {
+ return $this->data['copyright'];
+ }
+
+ $copyright = null;
+ $copyright = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/dc11:rights)');
+
+ if (!$copyright) {
+ $copyright = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/dc10:rights)');
+ }
+
+ if (!$copyright) {
+ $copyright = null;
+ }
+
+ $this->data['copyright'] = $copyright;
+
+ return $this->data['copyright'];
+ }
+
+ /**
+ * Get the feed description
+ *
+ * @return string|null
+ */
+ public function getDescription()
+ {
+ if (array_key_exists('description', $this->data)) {
+ return $this->data['description'];
+ }
+
+ $description = null;
+ $description = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/dc11:description)');
+
+ if (!$description) {
+ $description = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/dc10:description)');
+ }
+
+ if (!$description) {
+ $description = null;
+ }
+
+ $this->data['description'] = $description;
+
+ return $this->data['description'];
+ }
+
+ /**
+ * Get the feed ID
+ *
+ * @return string|null
+ */
+ public function getId()
+ {
+ if (array_key_exists('id', $this->data)) {
+ return $this->data['id'];
+ }
+
+ $id = null;
+ $id = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/dc11:identifier)');
+
+ if (!$id) {
+ $id = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/dc10:identifier)');
+ }
+
+ $this->data['id'] = $id;
+
+ return $this->data['id'];
+ }
+
+ /**
+ * Get the feed language
+ *
+ * @return string|null
+ */
+ public function getLanguage()
+ {
+ if (array_key_exists('language', $this->data)) {
+ return $this->data['language'];
+ }
+
+ $language = null;
+ $language = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/dc11:language)');
+
+ if (!$language) {
+ $language = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/dc10:language)');
+ }
+
+ if (!$language) {
+ $language = null;
+ }
+
+ $this->data['language'] = $language;
+
+ return $this->data['language'];
+ }
+
+ /**
+ * Get the feed title
+ *
+ * @return string|null
+ */
+ public function getTitle()
+ {
+ if (array_key_exists('title', $this->data)) {
+ return $this->data['title'];
+ }
+
+ $title = null;
+ $title = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/dc11:title)');
+
+ if (!$title) {
+ $title = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/dc10:title)');
+ }
+
+ if (!$title) {
+ $title = null;
+ }
+
+ $this->data['title'] = $title;
+
+ return $this->data['title'];
+ }
+
+ /**
+ *
+ *
+ * @return DateTime|null
+ */
+ public function getDate()
+ {
+ if (array_key_exists('date', $this->data)) {
+ return $this->data['date'];
+ }
+
+ $d = null;
+ $date = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/dc11:date)');
+
+ if (!$date) {
+ $date = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/dc10:date)');
+ }
+
+ if ($date) {
+ $d = new DateTime($date);
+ }
+
+ $this->data['date'] = $d;
+
+ return $this->data['date'];
+ }
+
+ /**
+ * Get categories (subjects under DC)
+ *
+ * @return Collection\Category
+ */
+ public function getCategories()
+ {
+ if (array_key_exists('categories', $this->data)) {
+ return $this->data['categories'];
+ }
+
+ $list = $this->getXpath()->evaluate($this->getXpathPrefix() . '//dc11:subject');
+
+ if (!$list->length) {
+ $list = $this->getXpath()->evaluate($this->getXpathPrefix() . '//dc10:subject');
+ }
+
+ if ($list->length) {
+ $categoryCollection = new Collection\Category;
+ foreach ($list as $category) {
+ $categoryCollection[] = array(
+ 'term' => $category->nodeValue,
+ 'scheme' => null,
+ 'label' => $category->nodeValue,
+ );
+ }
+ } else {
+ $categoryCollection = new Collection\Category;
+ }
+
+ $this->data['categories'] = $categoryCollection;
+ return $this->data['categories'];
+ }
+
+ /**
+ * Register the default namespaces for the current feed format
+ *
+ * @return void
+ */
+ protected function registerNamespaces()
+ {
+ $this->getXpath()->registerNamespace('dc10', 'http://purl.org/dc/elements/1.0/');
+ $this->getXpath()->registerNamespace('dc11', 'http://purl.org/dc/elements/1.1/');
+ }
+}
diff --git a/library/Zend/Feed/Reader/Extension/Podcast/Entry.php b/library/Zend/Feed/Reader/Extension/Podcast/Entry.php
new file mode 100755
index 0000000000..c97e64ff47
--- /dev/null
+++ b/library/Zend/Feed/Reader/Extension/Podcast/Entry.php
@@ -0,0 +1,180 @@
+data['author'])) {
+ return $this->data['author'];
+ }
+
+ $author = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:author)');
+
+ if (!$author) {
+ $author = null;
+ }
+
+ $this->data['author'] = $author;
+
+ return $this->data['author'];
+ }
+
+ /**
+ * Get the entry block
+ *
+ * @return string
+ */
+ public function getBlock()
+ {
+ if (isset($this->data['block'])) {
+ return $this->data['block'];
+ }
+
+ $block = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:block)');
+
+ if (!$block) {
+ $block = null;
+ }
+
+ $this->data['block'] = $block;
+
+ return $this->data['block'];
+ }
+
+ /**
+ * Get the entry duration
+ *
+ * @return string
+ */
+ public function getDuration()
+ {
+ if (isset($this->data['duration'])) {
+ return $this->data['duration'];
+ }
+
+ $duration = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:duration)');
+
+ if (!$duration) {
+ $duration = null;
+ }
+
+ $this->data['duration'] = $duration;
+
+ return $this->data['duration'];
+ }
+
+ /**
+ * Get the entry explicit
+ *
+ * @return string
+ */
+ public function getExplicit()
+ {
+ if (isset($this->data['explicit'])) {
+ return $this->data['explicit'];
+ }
+
+ $explicit = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:explicit)');
+
+ if (!$explicit) {
+ $explicit = null;
+ }
+
+ $this->data['explicit'] = $explicit;
+
+ return $this->data['explicit'];
+ }
+
+ /**
+ * Get the entry keywords
+ *
+ * @return string
+ */
+ public function getKeywords()
+ {
+ if (isset($this->data['keywords'])) {
+ return $this->data['keywords'];
+ }
+
+ $keywords = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:keywords)');
+
+ if (!$keywords) {
+ $keywords = null;
+ }
+
+ $this->data['keywords'] = $keywords;
+
+ return $this->data['keywords'];
+ }
+
+ /**
+ * Get the entry subtitle
+ *
+ * @return string
+ */
+ public function getSubtitle()
+ {
+ if (isset($this->data['subtitle'])) {
+ return $this->data['subtitle'];
+ }
+
+ $subtitle = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:subtitle)');
+
+ if (!$subtitle) {
+ $subtitle = null;
+ }
+
+ $this->data['subtitle'] = $subtitle;
+
+ return $this->data['subtitle'];
+ }
+
+ /**
+ * Get the entry summary
+ *
+ * @return string
+ */
+ public function getSummary()
+ {
+ if (isset($this->data['summary'])) {
+ return $this->data['summary'];
+ }
+
+ $summary = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:summary)');
+
+ if (!$summary) {
+ $summary = null;
+ }
+
+ $this->data['summary'] = $summary;
+
+ return $this->data['summary'];
+ }
+
+ /**
+ * Register iTunes namespace
+ *
+ */
+ protected function registerNamespaces()
+ {
+ $this->xpath->registerNamespace('itunes', 'http://www.itunes.com/dtds/podcast-1.0.dtd');
+ }
+}
diff --git a/library/Zend/Feed/Reader/Extension/Podcast/Feed.php b/library/Zend/Feed/Reader/Extension/Podcast/Feed.php
new file mode 100755
index 0000000000..66b13a48b2
--- /dev/null
+++ b/library/Zend/Feed/Reader/Extension/Podcast/Feed.php
@@ -0,0 +1,277 @@
+data['author'])) {
+ return $this->data['author'];
+ }
+
+ $author = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:author)');
+
+ if (!$author) {
+ $author = null;
+ }
+
+ $this->data['author'] = $author;
+
+ return $this->data['author'];
+ }
+
+ /**
+ * Get the entry block
+ *
+ * @return string
+ */
+ public function getBlock()
+ {
+ if (isset($this->data['block'])) {
+ return $this->data['block'];
+ }
+
+ $block = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:block)');
+
+ if (!$block) {
+ $block = null;
+ }
+
+ $this->data['block'] = $block;
+
+ return $this->data['block'];
+ }
+
+ /**
+ * Get the entry category
+ *
+ * @return string
+ */
+ public function getItunesCategories()
+ {
+ if (isset($this->data['categories'])) {
+ return $this->data['categories'];
+ }
+
+ $categoryList = $this->xpath->query($this->getXpathPrefix() . '/itunes:category');
+
+ $categories = array();
+
+ if ($categoryList->length > 0) {
+ foreach ($categoryList as $node) {
+ $children = null;
+
+ if ($node->childNodes->length > 0) {
+ $children = array();
+
+ foreach ($node->childNodes as $childNode) {
+ if (!($childNode instanceof DOMText)) {
+ $children[$childNode->getAttribute('text')] = null;
+ }
+ }
+ }
+
+ $categories[$node->getAttribute('text')] = $children;
+ }
+ }
+
+
+ if (!$categories) {
+ $categories = null;
+ }
+
+ $this->data['categories'] = $categories;
+
+ return $this->data['categories'];
+ }
+
+ /**
+ * Get the entry explicit
+ *
+ * @return string
+ */
+ public function getExplicit()
+ {
+ if (isset($this->data['explicit'])) {
+ return $this->data['explicit'];
+ }
+
+ $explicit = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:explicit)');
+
+ if (!$explicit) {
+ $explicit = null;
+ }
+
+ $this->data['explicit'] = $explicit;
+
+ return $this->data['explicit'];
+ }
+
+ /**
+ * Get the entry image
+ *
+ * @return string
+ */
+ public function getItunesImage()
+ {
+ if (isset($this->data['image'])) {
+ return $this->data['image'];
+ }
+
+ $image = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:image/@href)');
+
+ if (!$image) {
+ $image = null;
+ }
+
+ $this->data['image'] = $image;
+
+ return $this->data['image'];
+ }
+
+ /**
+ * Get the entry keywords
+ *
+ * @return string
+ */
+ public function getKeywords()
+ {
+ if (isset($this->data['keywords'])) {
+ return $this->data['keywords'];
+ }
+
+ $keywords = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:keywords)');
+
+ if (!$keywords) {
+ $keywords = null;
+ }
+
+ $this->data['keywords'] = $keywords;
+
+ return $this->data['keywords'];
+ }
+
+ /**
+ * Get the entry's new feed url
+ *
+ * @return string
+ */
+ public function getNewFeedUrl()
+ {
+ if (isset($this->data['new-feed-url'])) {
+ return $this->data['new-feed-url'];
+ }
+
+ $newFeedUrl = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:new-feed-url)');
+
+ if (!$newFeedUrl) {
+ $newFeedUrl = null;
+ }
+
+ $this->data['new-feed-url'] = $newFeedUrl;
+
+ return $this->data['new-feed-url'];
+ }
+
+ /**
+ * Get the entry owner
+ *
+ * @return string
+ */
+ public function getOwner()
+ {
+ if (isset($this->data['owner'])) {
+ return $this->data['owner'];
+ }
+
+ $owner = null;
+
+ $email = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:owner/itunes:email)');
+ $name = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:owner/itunes:name)');
+
+ if (!empty($email)) {
+ $owner = $email . (empty($name) ? '' : ' (' . $name . ')');
+ } elseif (!empty($name)) {
+ $owner = $name;
+ }
+
+ if (!$owner) {
+ $owner = null;
+ }
+
+ $this->data['owner'] = $owner;
+
+ return $this->data['owner'];
+ }
+
+ /**
+ * Get the entry subtitle
+ *
+ * @return string
+ */
+ public function getSubtitle()
+ {
+ if (isset($this->data['subtitle'])) {
+ return $this->data['subtitle'];
+ }
+
+ $subtitle = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:subtitle)');
+
+ if (!$subtitle) {
+ $subtitle = null;
+ }
+
+ $this->data['subtitle'] = $subtitle;
+
+ return $this->data['subtitle'];
+ }
+
+ /**
+ * Get the entry summary
+ *
+ * @return string
+ */
+ public function getSummary()
+ {
+ if (isset($this->data['summary'])) {
+ return $this->data['summary'];
+ }
+
+ $summary = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:summary)');
+
+ if (!$summary) {
+ $summary = null;
+ }
+
+ $this->data['summary'] = $summary;
+
+ return $this->data['summary'];
+ }
+
+ /**
+ * Register iTunes namespace
+ *
+ */
+ protected function registerNamespaces()
+ {
+ $this->xpath->registerNamespace('itunes', 'http://www.itunes.com/dtds/podcast-1.0.dtd');
+ }
+}
diff --git a/library/Zend/Feed/Reader/Extension/Slash/Entry.php b/library/Zend/Feed/Reader/Extension/Slash/Entry.php
new file mode 100755
index 0000000000..9ddb862e23
--- /dev/null
+++ b/library/Zend/Feed/Reader/Extension/Slash/Entry.php
@@ -0,0 +1,122 @@
+getData('section');
+ }
+
+ /**
+ * Get the entry department
+ *
+ * @return string|null
+ */
+ public function getDepartment()
+ {
+ return $this->getData('department');
+ }
+
+ /**
+ * Get the entry hit_parade
+ *
+ * @return array
+ */
+ public function getHitParade()
+ {
+ $name = 'hit_parade';
+
+ if (isset($this->data[$name])) {
+ return $this->data[$name];
+ }
+
+ $stringParade = $this->getData($name);
+ $hitParade = array();
+
+ if (!empty($stringParade)) {
+ $stringParade = explode(',', $stringParade);
+
+ foreach ($stringParade as $hit) {
+ $hitParade[] = $hit + 0; //cast to integer
+ }
+ }
+
+ $this->data[$name] = $hitParade;
+ return $hitParade;
+ }
+
+ /**
+ * Get the entry comments
+ *
+ * @return int
+ */
+ public function getCommentCount()
+ {
+ $name = 'comments';
+
+ if (isset($this->data[$name])) {
+ return $this->data[$name];
+ }
+
+ $comments = $this->getData($name, 'string');
+
+ if (!$comments) {
+ $this->data[$name] = null;
+ return $this->data[$name];
+ }
+
+ return $comments;
+ }
+
+ /**
+ * Get the entry data specified by name
+ * @param string $name
+ * @param string $type
+ *
+ * @return mixed|null
+ */
+ protected function getData($name, $type = 'string')
+ {
+ if (array_key_exists($name, $this->data)) {
+ return $this->data[$name];
+ }
+
+ $data = $this->xpath->evaluate($type . '(' . $this->getXpathPrefix() . '/slash10:' . $name . ')');
+
+ if (!$data) {
+ $data = null;
+ }
+
+ $this->data[$name] = $data;
+
+ return $data;
+ }
+
+ /**
+ * Register Slash namespaces
+ *
+ * @return void
+ */
+ protected function registerNamespaces()
+ {
+ $this->xpath->registerNamespace('slash10', 'http://purl.org/rss/1.0/modules/slash/');
+ }
+}
diff --git a/library/Zend/Feed/Reader/Extension/Syndication/Feed.php b/library/Zend/Feed/Reader/Extension/Syndication/Feed.php
new file mode 100755
index 0000000000..db1724c14c
--- /dev/null
+++ b/library/Zend/Feed/Reader/Extension/Syndication/Feed.php
@@ -0,0 +1,151 @@
+getData($name);
+
+ if ($period === null) {
+ $this->data[$name] = 'daily';
+ return 'daily'; //Default specified by spec
+ }
+
+ switch ($period) {
+ case 'hourly':
+ case 'daily':
+ case 'weekly':
+ case 'yearly':
+ return $period;
+ default:
+ throw new Reader\Exception\InvalidArgumentException("Feed specified invalid update period: '$period'."
+ . " Must be one of hourly, daily, weekly or yearly"
+ );
+ }
+ }
+
+ /**
+ * Get update frequency
+ *
+ * @return int
+ */
+ public function getUpdateFrequency()
+ {
+ $name = 'updateFrequency';
+ $freq = $this->getData($name, 'number');
+
+ if (!$freq || $freq < 1) {
+ $this->data[$name] = 1;
+ return 1;
+ }
+
+ return $freq;
+ }
+
+ /**
+ * Get update frequency as ticks
+ *
+ * @return int
+ */
+ public function getUpdateFrequencyAsTicks()
+ {
+ $name = 'updateFrequency';
+ $freq = $this->getData($name, 'number');
+
+ if (!$freq || $freq < 1) {
+ $this->data[$name] = 1;
+ $freq = 1;
+ }
+
+ $period = $this->getUpdatePeriod();
+ $ticks = 1;
+
+ switch ($period) {
+ case 'yearly':
+ $ticks *= 52; //TODO: fix generalisation, how?
+ // no break
+ case 'weekly':
+ $ticks *= 7;
+ // no break
+ case 'daily':
+ $ticks *= 24;
+ // no break
+ case 'hourly':
+ $ticks *= 3600;
+ break;
+ default: //Never arrive here, exception thrown in getPeriod()
+ break;
+ }
+
+ return $ticks / $freq;
+ }
+
+ /**
+ * Get update base
+ *
+ * @return DateTime|null
+ */
+ public function getUpdateBase()
+ {
+ $updateBase = $this->getData('updateBase');
+ $date = null;
+ if ($updateBase) {
+ $date = DateTime::createFromFormat(DateTime::W3C, $updateBase);
+ }
+ return $date;
+ }
+
+ /**
+ * Get the entry data specified by name
+ *
+ * @param string $name
+ * @param string $type
+ * @return mixed|null
+ */
+ private function getData($name, $type = 'string')
+ {
+ if (array_key_exists($name, $this->data)) {
+ return $this->data[$name];
+ }
+
+ $data = $this->xpath->evaluate($type . '(' . $this->getXpathPrefix() . '/syn10:' . $name . ')');
+
+ if (!$data) {
+ $data = null;
+ }
+
+ $this->data[$name] = $data;
+
+ return $data;
+ }
+
+ /**
+ * Register Syndication namespaces
+ *
+ * @return void
+ */
+ protected function registerNamespaces()
+ {
+ $this->xpath->registerNamespace('syn10', 'http://purl.org/rss/1.0/modules/syndication/');
+ }
+}
diff --git a/library/Zend/Feed/Reader/Extension/Thread/Entry.php b/library/Zend/Feed/Reader/Extension/Thread/Entry.php
new file mode 100755
index 0000000000..d3bc315871
--- /dev/null
+++ b/library/Zend/Feed/Reader/Extension/Thread/Entry.php
@@ -0,0 +1,72 @@
+getData('total');
+ }
+
+ /**
+ * Get the entry data specified by name
+ *
+ * @param string $name
+ * @return mixed|null
+ */
+ protected function getData($name)
+ {
+ if (array_key_exists($name, $this->data)) {
+ return $this->data[$name];
+ }
+
+ $data = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/thread10:' . $name . ')');
+
+ if (!$data) {
+ $data = null;
+ }
+
+ $this->data[$name] = $data;
+
+ return $data;
+ }
+
+ /**
+ * Register Atom Thread Extension 1.0 namespace
+ *
+ * @return void
+ */
+ protected function registerNamespaces()
+ {
+ $this->xpath->registerNamespace('thread10', 'http://purl.org/syndication/thread/1.0');
+ }
+}
diff --git a/library/Zend/Feed/Reader/Extension/WellFormedWeb/Entry.php b/library/Zend/Feed/Reader/Extension/WellFormedWeb/Entry.php
new file mode 100755
index 0000000000..6d5a977053
--- /dev/null
+++ b/library/Zend/Feed/Reader/Extension/WellFormedWeb/Entry.php
@@ -0,0 +1,50 @@
+data)) {
+ return $this->data[$name];
+ }
+
+ $data = $this->xpath->evaluate('string(' . $this->getXpathPrefix() . '/wfw:' . $name . ')');
+
+ if (!$data) {
+ $data = null;
+ }
+
+ $this->data[$name] = $data;
+
+ return $data;
+ }
+
+ /**
+ * Register Slash namespaces
+ *
+ * @return void
+ */
+ protected function registerNamespaces()
+ {
+ $this->xpath->registerNamespace('wfw', 'http://wellformedweb.org/CommentAPI/');
+ }
+}
diff --git a/library/Zend/Feed/Reader/ExtensionManager.php b/library/Zend/Feed/Reader/ExtensionManager.php
new file mode 100755
index 0000000000..9103643a30
--- /dev/null
+++ b/library/Zend/Feed/Reader/ExtensionManager.php
@@ -0,0 +1,80 @@
+pluginManager = $pluginManager;
+ }
+
+ /**
+ * Method overloading
+ *
+ * Proxy to composed ExtensionPluginManager instance.
+ *
+ * @param string $method
+ * @param array $args
+ * @return mixed
+ * @throws Exception\BadMethodCallException
+ */
+ public function __call($method, $args)
+ {
+ if (!method_exists($this->pluginManager, $method)) {
+ throw new Exception\BadMethodCallException(sprintf(
+ 'Method by name of %s does not exist in %s',
+ $method,
+ __CLASS__
+ ));
+ }
+ return call_user_func_array(array($this->pluginManager, $method), $args);
+ }
+
+ /**
+ * Get the named extension
+ *
+ * @param string $name
+ * @return Extension\AbstractEntry|Extension\AbstractFeed
+ */
+ public function get($name)
+ {
+ return $this->pluginManager->get($name);
+ }
+
+ /**
+ * Do we have the named extension?
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function has($name)
+ {
+ return $this->pluginManager->has($name);
+ }
+}
diff --git a/library/Zend/Feed/Reader/ExtensionManagerInterface.php b/library/Zend/Feed/Reader/ExtensionManagerInterface.php
new file mode 100755
index 0000000000..4bbb91d9e9
--- /dev/null
+++ b/library/Zend/Feed/Reader/ExtensionManagerInterface.php
@@ -0,0 +1,29 @@
+ 'Zend\Feed\Reader\Extension\Atom\Entry',
+ 'atomfeed' => 'Zend\Feed\Reader\Extension\Atom\Feed',
+ 'contententry' => 'Zend\Feed\Reader\Extension\Content\Entry',
+ 'creativecommonsentry' => 'Zend\Feed\Reader\Extension\CreativeCommons\Entry',
+ 'creativecommonsfeed' => 'Zend\Feed\Reader\Extension\CreativeCommons\Feed',
+ 'dublincoreentry' => 'Zend\Feed\Reader\Extension\DublinCore\Entry',
+ 'dublincorefeed' => 'Zend\Feed\Reader\Extension\DublinCore\Feed',
+ 'podcastentry' => 'Zend\Feed\Reader\Extension\Podcast\Entry',
+ 'podcastfeed' => 'Zend\Feed\Reader\Extension\Podcast\Feed',
+ 'slashentry' => 'Zend\Feed\Reader\Extension\Slash\Entry',
+ 'syndicationfeed' => 'Zend\Feed\Reader\Extension\Syndication\Feed',
+ 'threadentry' => 'Zend\Feed\Reader\Extension\Thread\Entry',
+ 'wellformedwebentry' => 'Zend\Feed\Reader\Extension\WellFormedWeb\Entry',
+ );
+
+ /**
+ * Do not share instances
+ *
+ * @var bool
+ */
+ protected $shareByDefault = false;
+
+ /**
+ * Validate the plugin
+ *
+ * Checks that the extension loaded is of a valid type.
+ *
+ * @param mixed $plugin
+ * @return void
+ * @throws Exception\InvalidArgumentException if invalid
+ */
+ public function validatePlugin($plugin)
+ {
+ if ($plugin instanceof Extension\AbstractEntry
+ || $plugin instanceof Extension\AbstractFeed
+ ) {
+ // we're okay
+ return;
+ }
+
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Plugin of type %s is invalid; must implement %s\Extension\AbstractFeed '
+ . 'or %s\Extension\AbstractEntry',
+ (is_object($plugin) ? get_class($plugin) : gettype($plugin)),
+ __NAMESPACE__,
+ __NAMESPACE__
+ ));
+ }
+}
diff --git a/library/Zend/Feed/Reader/Feed/AbstractFeed.php b/library/Zend/Feed/Reader/Feed/AbstractFeed.php
new file mode 100755
index 0000000000..dd616bef7a
--- /dev/null
+++ b/library/Zend/Feed/Reader/Feed/AbstractFeed.php
@@ -0,0 +1,307 @@
+domDocument = $domDocument;
+ $this->xpath = new DOMXPath($this->domDocument);
+
+ if ($type !== null) {
+ $this->data['type'] = $type;
+ } else {
+ $this->data['type'] = Reader\Reader::detectType($this->domDocument);
+ }
+ $this->registerNamespaces();
+ $this->indexEntries();
+ $this->loadExtensions();
+ }
+
+ /**
+ * Set an original source URI for the feed being parsed. This value
+ * is returned from getFeedLink() method if the feed does not carry
+ * a self-referencing URI.
+ *
+ * @param string $uri
+ */
+ public function setOriginalSourceUri($uri)
+ {
+ $this->originalSourceUri = $uri;
+ }
+
+ /**
+ * Get an original source URI for the feed being parsed. Returns null if
+ * unset or the feed was not imported from a URI.
+ *
+ * @return string|null
+ */
+ public function getOriginalSourceUri()
+ {
+ return $this->originalSourceUri;
+ }
+
+ /**
+ * Get the number of feed entries.
+ * Required by the Iterator interface.
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->entries);
+ }
+
+ /**
+ * Return the current entry
+ *
+ * @return \Zend\Feed\Reader\Entry\EntryInterface
+ */
+ public function current()
+ {
+ if (substr($this->getType(), 0, 3) == 'rss') {
+ $reader = new Reader\Entry\Rss($this->entries[$this->key()], $this->key(), $this->getType());
+ } else {
+ $reader = new Reader\Entry\Atom($this->entries[$this->key()], $this->key(), $this->getType());
+ }
+
+ $reader->setXpath($this->xpath);
+
+ return $reader;
+ }
+
+ /**
+ * Get the DOM
+ *
+ * @return DOMDocument
+ */
+ public function getDomDocument()
+ {
+ return $this->domDocument;
+ }
+
+ /**
+ * Get the Feed's encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ $assumed = $this->getDomDocument()->encoding;
+ if (empty($assumed)) {
+ $assumed = 'UTF-8';
+ }
+ return $assumed;
+ }
+
+ /**
+ * Get feed as xml
+ *
+ * @return string
+ */
+ public function saveXml()
+ {
+ return $this->getDomDocument()->saveXml();
+ }
+
+ /**
+ * Get the DOMElement representing the items/feed element
+ *
+ * @return DOMElement
+ */
+ public function getElement()
+ {
+ return $this->getDomDocument()->documentElement;
+ }
+
+ /**
+ * Get the DOMXPath object for this feed
+ *
+ * @return DOMXPath
+ */
+ public function getXpath()
+ {
+ return $this->xpath;
+ }
+
+ /**
+ * Get the feed type
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->data['type'];
+ }
+
+ /**
+ * Return the current feed key
+ *
+ * @return int
+ */
+ public function key()
+ {
+ return $this->entriesKey;
+ }
+
+ /**
+ * Move the feed pointer forward
+ *
+ */
+ public function next()
+ {
+ ++$this->entriesKey;
+ }
+
+ /**
+ * Reset the pointer in the feed object
+ *
+ */
+ public function rewind()
+ {
+ $this->entriesKey = 0;
+ }
+
+ /**
+ * Check to see if the iterator is still valid
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return 0 <= $this->entriesKey && $this->entriesKey < $this->count();
+ }
+
+ public function getExtensions()
+ {
+ return $this->extensions;
+ }
+
+ public function __call($method, $args)
+ {
+ foreach ($this->extensions as $extension) {
+ if (method_exists($extension, $method)) {
+ return call_user_func_array(array($extension, $method), $args);
+ }
+ }
+ throw new Exception\BadMethodCallException('Method: ' . $method
+ . 'does not exist and could not be located on a registered Extension');
+ }
+
+ /**
+ * Return an Extension object with the matching name (postfixed with _Feed)
+ *
+ * @param string $name
+ * @return \Zend\Feed\Reader\Extension\AbstractFeed
+ */
+ public function getExtension($name)
+ {
+ if (array_key_exists($name . '\\Feed', $this->extensions)) {
+ return $this->extensions[$name . '\\Feed'];
+ }
+ return null;
+ }
+
+ protected function loadExtensions()
+ {
+ $all = Reader\Reader::getExtensions();
+ $manager = Reader\Reader::getExtensionManager();
+ $feed = $all['feed'];
+ foreach ($feed as $extension) {
+ if (in_array($extension, $all['core'])) {
+ continue;
+ }
+ if (!$manager->has($extension)) {
+ throw new Exception\RuntimeException(sprintf('Unable to load extension "%s"; cannot find class', $extension));
+ }
+ $plugin = $manager->get($extension);
+ $plugin->setDomDocument($this->getDomDocument());
+ $plugin->setType($this->data['type']);
+ $plugin->setXpath($this->xpath);
+ $this->extensions[$extension] = $plugin;
+ }
+ }
+
+ /**
+ * Read all entries to the internal entries array
+ *
+ */
+ abstract protected function indexEntries();
+
+ /**
+ * Register the default namespaces for the current feed format
+ *
+ */
+ abstract protected function registerNamespaces();
+}
diff --git a/library/Zend/Feed/Reader/Feed/Atom.php b/library/Zend/Feed/Reader/Feed/Atom.php
new file mode 100755
index 0000000000..72efcf7609
--- /dev/null
+++ b/library/Zend/Feed/Reader/Feed/Atom.php
@@ -0,0 +1,408 @@
+get('Atom\Feed');
+ $atomFeed->setDomDocument($dom);
+ $atomFeed->setType($this->data['type']);
+ $atomFeed->setXpath($this->xpath);
+ $this->extensions['Atom\\Feed'] = $atomFeed;
+
+ $atomFeed = $manager->get('DublinCore\Feed');
+ $atomFeed->setDomDocument($dom);
+ $atomFeed->setType($this->data['type']);
+ $atomFeed->setXpath($this->xpath);
+ $this->extensions['DublinCore\\Feed'] = $atomFeed;
+
+ foreach ($this->extensions as $extension) {
+ $extension->setXpathPrefix('/atom:feed');
+ }
+ }
+
+ /**
+ * Get a single author
+ *
+ * @param int $index
+ * @return string|null
+ */
+ public function getAuthor($index = 0)
+ {
+ $authors = $this->getAuthors();
+
+ if (isset($authors[$index])) {
+ return $authors[$index];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get an array with feed authors
+ *
+ * @return array
+ */
+ public function getAuthors()
+ {
+ if (array_key_exists('authors', $this->data)) {
+ return $this->data['authors'];
+ }
+
+ $authors = $this->getExtension('Atom')->getAuthors();
+
+ $this->data['authors'] = $authors;
+
+ return $this->data['authors'];
+ }
+
+ /**
+ * Get the copyright entry
+ *
+ * @return string|null
+ */
+ public function getCopyright()
+ {
+ if (array_key_exists('copyright', $this->data)) {
+ return $this->data['copyright'];
+ }
+
+ $copyright = $this->getExtension('Atom')->getCopyright();
+
+ if (!$copyright) {
+ $copyright = null;
+ }
+
+ $this->data['copyright'] = $copyright;
+
+ return $this->data['copyright'];
+ }
+
+ /**
+ * Get the feed creation date
+ *
+ * @return string|null
+ */
+ public function getDateCreated()
+ {
+ if (array_key_exists('datecreated', $this->data)) {
+ return $this->data['datecreated'];
+ }
+
+ $dateCreated = $this->getExtension('Atom')->getDateCreated();
+
+ if (!$dateCreated) {
+ $dateCreated = null;
+ }
+
+ $this->data['datecreated'] = $dateCreated;
+
+ return $this->data['datecreated'];
+ }
+
+ /**
+ * Get the feed modification date
+ *
+ * @return string|null
+ */
+ public function getDateModified()
+ {
+ if (array_key_exists('datemodified', $this->data)) {
+ return $this->data['datemodified'];
+ }
+
+ $dateModified = $this->getExtension('Atom')->getDateModified();
+
+ if (!$dateModified) {
+ $dateModified = null;
+ }
+
+ $this->data['datemodified'] = $dateModified;
+
+ return $this->data['datemodified'];
+ }
+
+ /**
+ * Get the feed lastBuild date. This is not implemented in Atom.
+ *
+ * @return string|null
+ */
+ public function getLastBuildDate()
+ {
+ return null;
+ }
+
+ /**
+ * Get the feed description
+ *
+ * @return string|null
+ */
+ public function getDescription()
+ {
+ if (array_key_exists('description', $this->data)) {
+ return $this->data['description'];
+ }
+
+ $description = $this->getExtension('Atom')->getDescription();
+
+ if (!$description) {
+ $description = null;
+ }
+
+ $this->data['description'] = $description;
+
+ return $this->data['description'];
+ }
+
+ /**
+ * Get the feed generator entry
+ *
+ * @return string|null
+ */
+ public function getGenerator()
+ {
+ if (array_key_exists('generator', $this->data)) {
+ return $this->data['generator'];
+ }
+
+ $generator = $this->getExtension('Atom')->getGenerator();
+
+ $this->data['generator'] = $generator;
+
+ return $this->data['generator'];
+ }
+
+ /**
+ * Get the feed ID
+ *
+ * @return string|null
+ */
+ public function getId()
+ {
+ if (array_key_exists('id', $this->data)) {
+ return $this->data['id'];
+ }
+
+ $id = $this->getExtension('Atom')->getId();
+
+ $this->data['id'] = $id;
+
+ return $this->data['id'];
+ }
+
+ /**
+ * Get the feed language
+ *
+ * @return string|null
+ */
+ public function getLanguage()
+ {
+ if (array_key_exists('language', $this->data)) {
+ return $this->data['language'];
+ }
+
+ $language = $this->getExtension('Atom')->getLanguage();
+
+ if (!$language) {
+ $language = $this->xpath->evaluate('string(//@xml:lang[1])');
+ }
+
+ if (!$language) {
+ $language = null;
+ }
+
+ $this->data['language'] = $language;
+
+ return $this->data['language'];
+ }
+
+ /**
+ * Get a link to the source website
+ *
+ * @return string|null
+ */
+ public function getBaseUrl()
+ {
+ if (array_key_exists('baseUrl', $this->data)) {
+ return $this->data['baseUrl'];
+ }
+
+ $baseUrl = $this->getExtension('Atom')->getBaseUrl();
+
+ $this->data['baseUrl'] = $baseUrl;
+
+ return $this->data['baseUrl'];
+ }
+
+ /**
+ * Get a link to the source website
+ *
+ * @return string|null
+ */
+ public function getLink()
+ {
+ if (array_key_exists('link', $this->data)) {
+ return $this->data['link'];
+ }
+
+ $link = $this->getExtension('Atom')->getLink();
+
+ $this->data['link'] = $link;
+
+ return $this->data['link'];
+ }
+
+ /**
+ * Get feed image data
+ *
+ * @return array|null
+ */
+ public function getImage()
+ {
+ if (array_key_exists('image', $this->data)) {
+ return $this->data['image'];
+ }
+
+ $link = $this->getExtension('Atom')->getImage();
+
+ $this->data['image'] = $link;
+
+ return $this->data['image'];
+ }
+
+ /**
+ * Get a link to the feed's XML Url
+ *
+ * @return string|null
+ */
+ public function getFeedLink()
+ {
+ if (array_key_exists('feedlink', $this->data)) {
+ return $this->data['feedlink'];
+ }
+
+ $link = $this->getExtension('Atom')->getFeedLink();
+
+ if ($link === null || empty($link)) {
+ $link = $this->getOriginalSourceUri();
+ }
+
+ $this->data['feedlink'] = $link;
+
+ return $this->data['feedlink'];
+ }
+
+ /**
+ * Get the feed title
+ *
+ * @return string|null
+ */
+ public function getTitle()
+ {
+ if (array_key_exists('title', $this->data)) {
+ return $this->data['title'];
+ }
+
+ $title = $this->getExtension('Atom')->getTitle();
+
+ $this->data['title'] = $title;
+
+ return $this->data['title'];
+ }
+
+ /**
+ * Get an array of any supported Pusubhubbub endpoints
+ *
+ * @return array|null
+ */
+ public function getHubs()
+ {
+ if (array_key_exists('hubs', $this->data)) {
+ return $this->data['hubs'];
+ }
+
+ $hubs = $this->getExtension('Atom')->getHubs();
+
+ $this->data['hubs'] = $hubs;
+
+ return $this->data['hubs'];
+ }
+
+ /**
+ * Get all categories
+ *
+ * @return Reader\Collection\Category
+ */
+ public function getCategories()
+ {
+ if (array_key_exists('categories', $this->data)) {
+ return $this->data['categories'];
+ }
+
+ $categoryCollection = $this->getExtension('Atom')->getCategories();
+
+ if (count($categoryCollection) == 0) {
+ $categoryCollection = $this->getExtension('DublinCore')->getCategories();
+ }
+
+ $this->data['categories'] = $categoryCollection;
+
+ return $this->data['categories'];
+ }
+
+ /**
+ * Read all entries to the internal entries array
+ *
+ * @return void
+ */
+ protected function indexEntries()
+ {
+ if ($this->getType() == Reader\Reader::TYPE_ATOM_10 ||
+ $this->getType() == Reader\Reader::TYPE_ATOM_03) {
+ $entries = $this->xpath->evaluate('//atom:entry');
+
+ foreach ($entries as $index => $entry) {
+ $this->entries[$index] = $entry;
+ }
+ }
+ }
+
+ /**
+ * Register the default namespaces for the current feed format
+ *
+ */
+ protected function registerNamespaces()
+ {
+ switch ($this->data['type']) {
+ case Reader\Reader::TYPE_ATOM_03:
+ $this->xpath->registerNamespace('atom', Reader\Reader::NAMESPACE_ATOM_03);
+ break;
+ case Reader\Reader::TYPE_ATOM_10:
+ default:
+ $this->xpath->registerNamespace('atom', Reader\Reader::NAMESPACE_ATOM_10);
+ }
+ }
+}
diff --git a/library/Zend/Feed/Reader/Feed/Atom/Source.php b/library/Zend/Feed/Reader/Feed/Atom/Source.php
new file mode 100755
index 0000000000..5eabd974bd
--- /dev/null
+++ b/library/Zend/Feed/Reader/Feed/Atom/Source.php
@@ -0,0 +1,107 @@
+domDocument = $source->ownerDocument;
+ $this->xpath = new DOMXPath($this->domDocument);
+ $this->data['type'] = $type;
+ $this->registerNamespaces();
+ $this->loadExtensions();
+
+ $manager = Reader\Reader::getExtensionManager();
+ $extensions = array('Atom\Feed', 'DublinCore\Feed');
+
+ foreach ($extensions as $name) {
+ $extension = $manager->get($name);
+ $extension->setDomDocument($this->domDocument);
+ $extension->setType($this->data['type']);
+ $extension->setXpath($this->xpath);
+ $this->extensions[$name] = $extension;
+ }
+
+ foreach ($this->extensions as $extension) {
+ $extension->setXpathPrefix(rtrim($xpathPrefix, '/') . '/atom:source');
+ }
+ }
+
+ /**
+ * Since this is not an Entry carrier but a vehicle for Feed metadata, any
+ * applicable Entry methods are stubbed out and do nothing.
+ */
+
+ /**
+ * @return void
+ */
+ public function count()
+ {
+ }
+
+ /**
+ * @return void
+ */
+ public function current()
+ {
+ }
+
+ /**
+ * @return void
+ */
+ public function key()
+ {
+ }
+
+ /**
+ * @return void
+ */
+ public function next()
+ {
+ }
+
+ /**
+ * @return void
+ */
+ public function rewind()
+ {
+ }
+
+ /**
+ * @return void
+ */
+ public function valid()
+ {
+ }
+
+ /**
+ * @return void
+ */
+ protected function indexEntries()
+ {
+ }
+}
diff --git a/library/Zend/Feed/Reader/Feed/FeedInterface.php b/library/Zend/Feed/Reader/Feed/FeedInterface.php
new file mode 100755
index 0000000000..c98a1b333b
--- /dev/null
+++ b/library/Zend/Feed/Reader/Feed/FeedInterface.php
@@ -0,0 +1,110 @@
+get('DublinCore\Feed');
+ $feed->setDomDocument($dom);
+ $feed->setType($this->data['type']);
+ $feed->setXpath($this->xpath);
+ $this->extensions['DublinCore\Feed'] = $feed;
+
+ $feed = $manager->get('Atom\Feed');
+ $feed->setDomDocument($dom);
+ $feed->setType($this->data['type']);
+ $feed->setXpath($this->xpath);
+ $this->extensions['Atom\Feed'] = $feed;
+
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10
+ && $this->getType() !== Reader\Reader::TYPE_RSS_090
+ ) {
+ $xpathPrefix = '/rss/channel';
+ } else {
+ $xpathPrefix = '/rdf:RDF/rss:channel';
+ }
+ foreach ($this->extensions as $extension) {
+ $extension->setXpathPrefix($xpathPrefix);
+ }
+ }
+
+ /**
+ * Get a single author
+ *
+ * @param int $index
+ * @return string|null
+ */
+ public function getAuthor($index = 0)
+ {
+ $authors = $this->getAuthors();
+
+ if (isset($authors[$index])) {
+ return $authors[$index];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get an array with feed authors
+ *
+ * @return array
+ */
+ public function getAuthors()
+ {
+ if (array_key_exists('authors', $this->data)) {
+ return $this->data['authors'];
+ }
+
+ $authors = array();
+ $authorsDc = $this->getExtension('DublinCore')->getAuthors();
+ if (!empty($authorsDc)) {
+ foreach ($authorsDc as $author) {
+ $authors[] = array(
+ 'name' => $author['name']
+ );
+ }
+ }
+
+ /**
+ * Technically RSS doesn't specific author element use at the feed level
+ * but it's supported on a "just in case" basis.
+ */
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10
+ && $this->getType() !== Reader\Reader::TYPE_RSS_090) {
+ $list = $this->xpath->query('//author');
+ } else {
+ $list = $this->xpath->query('//rss:author');
+ }
+ if ($list->length) {
+ foreach ($list as $author) {
+ $string = trim($author->nodeValue);
+ $email = null;
+ $name = null;
+ $data = array();
+ // Pretty rough parsing - but it's a catchall
+ if (preg_match("/^.*@[^ ]*/", $string, $matches)) {
+ $data['email'] = trim($matches[0]);
+ if (preg_match("/\((.*)\)$/", $string, $matches)) {
+ $data['name'] = $matches[1];
+ }
+ $authors[] = $data;
+ }
+ }
+ }
+
+ if (count($authors) == 0) {
+ $authors = $this->getExtension('Atom')->getAuthors();
+ } else {
+ $authors = new Reader\Collection\Author(
+ Reader\Reader::arrayUnique($authors)
+ );
+ }
+
+ if (count($authors) == 0) {
+ $authors = null;
+ }
+
+ $this->data['authors'] = $authors;
+
+ return $this->data['authors'];
+ }
+
+ /**
+ * Get the copyright entry
+ *
+ * @return string|null
+ */
+ public function getCopyright()
+ {
+ if (array_key_exists('copyright', $this->data)) {
+ return $this->data['copyright'];
+ }
+
+ $copyright = null;
+
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
+ $this->getType() !== Reader\Reader::TYPE_RSS_090) {
+ $copyright = $this->xpath->evaluate('string(/rss/channel/copyright)');
+ }
+
+ if (!$copyright && $this->getExtension('DublinCore') !== null) {
+ $copyright = $this->getExtension('DublinCore')->getCopyright();
+ }
+
+ if (empty($copyright)) {
+ $copyright = $this->getExtension('Atom')->getCopyright();
+ }
+
+ if (!$copyright) {
+ $copyright = null;
+ }
+
+ $this->data['copyright'] = $copyright;
+
+ return $this->data['copyright'];
+ }
+
+ /**
+ * Get the feed creation date
+ *
+ * @return string|null
+ */
+ public function getDateCreated()
+ {
+ return $this->getDateModified();
+ }
+
+ /**
+ * Get the feed modification date
+ *
+ * @return DateTime
+ * @throws Exception\RuntimeException
+ */
+ public function getDateModified()
+ {
+ if (array_key_exists('datemodified', $this->data)) {
+ return $this->data['datemodified'];
+ }
+
+ $dateModified = null;
+ $date = null;
+
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
+ $this->getType() !== Reader\Reader::TYPE_RSS_090) {
+ $dateModified = $this->xpath->evaluate('string(/rss/channel/pubDate)');
+ if (!$dateModified) {
+ $dateModified = $this->xpath->evaluate('string(/rss/channel/lastBuildDate)');
+ }
+ if ($dateModified) {
+ $dateModifiedParsed = strtotime($dateModified);
+ if ($dateModifiedParsed) {
+ $date = new DateTime('@' . $dateModifiedParsed);
+ } else {
+ $dateStandards = array(DateTime::RSS, DateTime::RFC822,
+ DateTime::RFC2822, null);
+ foreach ($dateStandards as $standard) {
+ try {
+ $date = DateTime::createFromFormat($standard, $dateModified);
+ break;
+ } catch (\Exception $e) {
+ if ($standard == null) {
+ throw new Exception\RuntimeException(
+ 'Could not load date due to unrecognised'
+ .' format (should follow RFC 822 or 2822):'
+ . $e->getMessage(),
+ 0, $e
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (!$date) {
+ $date = $this->getExtension('DublinCore')->getDate();
+ }
+
+ if (!$date) {
+ $date = $this->getExtension('Atom')->getDateModified();
+ }
+
+ if (!$date) {
+ $date = null;
+ }
+
+ $this->data['datemodified'] = $date;
+
+ return $this->data['datemodified'];
+ }
+
+ /**
+ * Get the feed lastBuild date
+ *
+ * @throws Exception\RuntimeException
+ * @return DateTime
+ */
+ public function getLastBuildDate()
+ {
+ if (array_key_exists('lastBuildDate', $this->data)) {
+ return $this->data['lastBuildDate'];
+ }
+
+ $lastBuildDate = null;
+ $date = null;
+
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
+ $this->getType() !== Reader\Reader::TYPE_RSS_090) {
+ $lastBuildDate = $this->xpath->evaluate('string(/rss/channel/lastBuildDate)');
+ if ($lastBuildDate) {
+ $lastBuildDateParsed = strtotime($lastBuildDate);
+ if ($lastBuildDateParsed) {
+ $date = new DateTime('@' . $lastBuildDateParsed);
+ } else {
+ $dateStandards = array(DateTime::RSS, DateTime::RFC822,
+ DateTime::RFC2822, null);
+ foreach ($dateStandards as $standard) {
+ try {
+ $date = DateTime::createFromFormat($standard, $lastBuildDateParsed);
+ break;
+ } catch (\Exception $e) {
+ if ($standard == null) {
+ throw new Exception\RuntimeException(
+ 'Could not load date due to unrecognised'
+ .' format (should follow RFC 822 or 2822):'
+ . $e->getMessage(),
+ 0, $e
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (!$date) {
+ $date = null;
+ }
+
+ $this->data['lastBuildDate'] = $date;
+
+ return $this->data['lastBuildDate'];
+ }
+
+ /**
+ * Get the feed description
+ *
+ * @return string|null
+ */
+ public function getDescription()
+ {
+ if (array_key_exists('description', $this->data)) {
+ return $this->data['description'];
+ }
+
+ $description = null;
+
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
+ $this->getType() !== Reader\Reader::TYPE_RSS_090) {
+ $description = $this->xpath->evaluate('string(/rss/channel/description)');
+ } else {
+ $description = $this->xpath->evaluate('string(/rdf:RDF/rss:channel/rss:description)');
+ }
+
+ if (!$description && $this->getExtension('DublinCore') !== null) {
+ $description = $this->getExtension('DublinCore')->getDescription();
+ }
+
+ if (empty($description)) {
+ $description = $this->getExtension('Atom')->getDescription();
+ }
+
+ if (!$description) {
+ $description = null;
+ }
+
+ $this->data['description'] = $description;
+
+ return $this->data['description'];
+ }
+
+ /**
+ * Get the feed ID
+ *
+ * @return string|null
+ */
+ public function getId()
+ {
+ if (array_key_exists('id', $this->data)) {
+ return $this->data['id'];
+ }
+
+ $id = null;
+
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
+ $this->getType() !== Reader\Reader::TYPE_RSS_090) {
+ $id = $this->xpath->evaluate('string(/rss/channel/guid)');
+ }
+
+ if (!$id && $this->getExtension('DublinCore') !== null) {
+ $id = $this->getExtension('DublinCore')->getId();
+ }
+
+ if (empty($id)) {
+ $id = $this->getExtension('Atom')->getId();
+ }
+
+ if (!$id) {
+ if ($this->getLink()) {
+ $id = $this->getLink();
+ } elseif ($this->getTitle()) {
+ $id = $this->getTitle();
+ } else {
+ $id = null;
+ }
+ }
+
+ $this->data['id'] = $id;
+
+ return $this->data['id'];
+ }
+
+ /**
+ * Get the feed image data
+ *
+ * @return array|null
+ */
+ public function getImage()
+ {
+ if (array_key_exists('image', $this->data)) {
+ return $this->data['image'];
+ }
+
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
+ $this->getType() !== Reader\Reader::TYPE_RSS_090) {
+ $list = $this->xpath->query('/rss/channel/image');
+ $prefix = '/rss/channel/image[1]';
+ } else {
+ $list = $this->xpath->query('/rdf:RDF/rss:channel/rss:image');
+ $prefix = '/rdf:RDF/rss:channel/rss:image[1]';
+ }
+ if ($list->length > 0) {
+ $image = array();
+ $value = $this->xpath->evaluate('string(' . $prefix . '/url)');
+ if ($value) {
+ $image['uri'] = $value;
+ }
+ $value = $this->xpath->evaluate('string(' . $prefix . '/link)');
+ if ($value) {
+ $image['link'] = $value;
+ }
+ $value = $this->xpath->evaluate('string(' . $prefix . '/title)');
+ if ($value) {
+ $image['title'] = $value;
+ }
+ $value = $this->xpath->evaluate('string(' . $prefix . '/height)');
+ if ($value) {
+ $image['height'] = $value;
+ }
+ $value = $this->xpath->evaluate('string(' . $prefix . '/width)');
+ if ($value) {
+ $image['width'] = $value;
+ }
+ $value = $this->xpath->evaluate('string(' . $prefix . '/description)');
+ if ($value) {
+ $image['description'] = $value;
+ }
+ } else {
+ $image = null;
+ }
+
+ $this->data['image'] = $image;
+
+ return $this->data['image'];
+ }
+
+ /**
+ * Get the feed language
+ *
+ * @return string|null
+ */
+ public function getLanguage()
+ {
+ if (array_key_exists('language', $this->data)) {
+ return $this->data['language'];
+ }
+
+ $language = null;
+
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
+ $this->getType() !== Reader\Reader::TYPE_RSS_090) {
+ $language = $this->xpath->evaluate('string(/rss/channel/language)');
+ }
+
+ if (!$language && $this->getExtension('DublinCore') !== null) {
+ $language = $this->getExtension('DublinCore')->getLanguage();
+ }
+
+ if (empty($language)) {
+ $language = $this->getExtension('Atom')->getLanguage();
+ }
+
+ if (!$language) {
+ $language = $this->xpath->evaluate('string(//@xml:lang[1])');
+ }
+
+ if (!$language) {
+ $language = null;
+ }
+
+ $this->data['language'] = $language;
+
+ return $this->data['language'];
+ }
+
+ /**
+ * Get a link to the feed
+ *
+ * @return string|null
+ */
+ public function getLink()
+ {
+ if (array_key_exists('link', $this->data)) {
+ return $this->data['link'];
+ }
+
+ $link = null;
+
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
+ $this->getType() !== Reader\Reader::TYPE_RSS_090) {
+ $link = $this->xpath->evaluate('string(/rss/channel/link)');
+ } else {
+ $link = $this->xpath->evaluate('string(/rdf:RDF/rss:channel/rss:link)');
+ }
+
+ if (empty($link)) {
+ $link = $this->getExtension('Atom')->getLink();
+ }
+
+ if (!$link) {
+ $link = null;
+ }
+
+ $this->data['link'] = $link;
+
+ return $this->data['link'];
+ }
+
+ /**
+ * Get a link to the feed XML
+ *
+ * @return string|null
+ */
+ public function getFeedLink()
+ {
+ if (array_key_exists('feedlink', $this->data)) {
+ return $this->data['feedlink'];
+ }
+
+ $link = null;
+
+ $link = $this->getExtension('Atom')->getFeedLink();
+
+ if ($link === null || empty($link)) {
+ $link = $this->getOriginalSourceUri();
+ }
+
+ $this->data['feedlink'] = $link;
+
+ return $this->data['feedlink'];
+ }
+
+ /**
+ * Get the feed generator entry
+ *
+ * @return string|null
+ */
+ public function getGenerator()
+ {
+ if (array_key_exists('generator', $this->data)) {
+ return $this->data['generator'];
+ }
+
+ $generator = null;
+
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
+ $this->getType() !== Reader\Reader::TYPE_RSS_090) {
+ $generator = $this->xpath->evaluate('string(/rss/channel/generator)');
+ }
+
+ if (!$generator) {
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
+ $this->getType() !== Reader\Reader::TYPE_RSS_090) {
+ $generator = $this->xpath->evaluate('string(/rss/channel/atom:generator)');
+ } else {
+ $generator = $this->xpath->evaluate('string(/rdf:RDF/rss:channel/atom:generator)');
+ }
+ }
+
+ if (empty($generator)) {
+ $generator = $this->getExtension('Atom')->getGenerator();
+ }
+
+ if (!$generator) {
+ $generator = null;
+ }
+
+ $this->data['generator'] = $generator;
+
+ return $this->data['generator'];
+ }
+
+ /**
+ * Get the feed title
+ *
+ * @return string|null
+ */
+ public function getTitle()
+ {
+ if (array_key_exists('title', $this->data)) {
+ return $this->data['title'];
+ }
+
+ $title = null;
+
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
+ $this->getType() !== Reader\Reader::TYPE_RSS_090) {
+ $title = $this->xpath->evaluate('string(/rss/channel/title)');
+ } else {
+ $title = $this->xpath->evaluate('string(/rdf:RDF/rss:channel/rss:title)');
+ }
+
+ if (!$title && $this->getExtension('DublinCore') !== null) {
+ $title = $this->getExtension('DublinCore')->getTitle();
+ }
+
+ if (!$title) {
+ $title = $this->getExtension('Atom')->getTitle();
+ }
+
+ if (!$title) {
+ $title = null;
+ }
+
+ $this->data['title'] = $title;
+
+ return $this->data['title'];
+ }
+
+ /**
+ * Get an array of any supported Pusubhubbub endpoints
+ *
+ * @return array|null
+ */
+ public function getHubs()
+ {
+ if (array_key_exists('hubs', $this->data)) {
+ return $this->data['hubs'];
+ }
+
+ $hubs = $this->getExtension('Atom')->getHubs();
+
+ if (empty($hubs)) {
+ $hubs = null;
+ } else {
+ $hubs = array_unique($hubs);
+ }
+
+ $this->data['hubs'] = $hubs;
+
+ return $this->data['hubs'];
+ }
+
+ /**
+ * Get all categories
+ *
+ * @return Reader\Collection\Category
+ */
+ public function getCategories()
+ {
+ if (array_key_exists('categories', $this->data)) {
+ return $this->data['categories'];
+ }
+
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
+ $this->getType() !== Reader\Reader::TYPE_RSS_090) {
+ $list = $this->xpath->query('/rss/channel//category');
+ } else {
+ $list = $this->xpath->query('/rdf:RDF/rss:channel//rss:category');
+ }
+
+ if ($list->length) {
+ $categoryCollection = new Collection\Category;
+ foreach ($list as $category) {
+ $categoryCollection[] = array(
+ 'term' => $category->nodeValue,
+ 'scheme' => $category->getAttribute('domain'),
+ 'label' => $category->nodeValue,
+ );
+ }
+ } else {
+ $categoryCollection = $this->getExtension('DublinCore')->getCategories();
+ }
+
+ if (count($categoryCollection) == 0) {
+ $categoryCollection = $this->getExtension('Atom')->getCategories();
+ }
+
+ $this->data['categories'] = $categoryCollection;
+
+ return $this->data['categories'];
+ }
+
+ /**
+ * Read all entries to the internal entries array
+ *
+ */
+ protected function indexEntries()
+ {
+ if ($this->getType() !== Reader\Reader::TYPE_RSS_10 && $this->getType() !== Reader\Reader::TYPE_RSS_090) {
+ $entries = $this->xpath->evaluate('//item');
+ } else {
+ $entries = $this->xpath->evaluate('//rss:item');
+ }
+
+ foreach ($entries as $index => $entry) {
+ $this->entries[$index] = $entry;
+ }
+ }
+
+ /**
+ * Register the default namespaces for the current feed format
+ *
+ */
+ protected function registerNamespaces()
+ {
+ switch ($this->data['type']) {
+ case Reader\Reader::TYPE_RSS_10:
+ $this->xpath->registerNamespace('rdf', Reader\Reader::NAMESPACE_RDF);
+ $this->xpath->registerNamespace('rss', Reader\Reader::NAMESPACE_RSS_10);
+ break;
+
+ case Reader\Reader::TYPE_RSS_090:
+ $this->xpath->registerNamespace('rdf', Reader\Reader::NAMESPACE_RDF);
+ $this->xpath->registerNamespace('rss', Reader\Reader::NAMESPACE_RSS_090);
+ break;
+ }
+ }
+}
diff --git a/library/Zend/Feed/Reader/FeedSet.php b/library/Zend/Feed/Reader/FeedSet.php
new file mode 100755
index 0000000000..e487bc8f4c
--- /dev/null
+++ b/library/Zend/Feed/Reader/FeedSet.php
@@ -0,0 +1,126 @@
+getAttribute('rel')) !== 'alternate'
+ || !$link->getAttribute('type') || !$link->getAttribute('href')) {
+ continue;
+ }
+ if (!isset($this->rss) && $link->getAttribute('type') == 'application/rss+xml') {
+ $this->rss = $this->absolutiseUri(trim($link->getAttribute('href')), $uri);
+ } elseif (!isset($this->atom) && $link->getAttribute('type') == 'application/atom+xml') {
+ $this->atom = $this->absolutiseUri(trim($link->getAttribute('href')), $uri);
+ } elseif (!isset($this->rdf) && $link->getAttribute('type') == 'application/rdf+xml') {
+ $this->rdf = $this->absolutiseUri(trim($link->getAttribute('href')), $uri);
+ }
+ $this[] = new static(array(
+ 'rel' => 'alternate',
+ 'type' => $link->getAttribute('type'),
+ 'href' => $this->absolutiseUri(trim($link->getAttribute('href')), $uri),
+ ));
+ }
+ }
+
+ /**
+ * Attempt to turn a relative URI into an absolute URI
+ */
+ protected function absolutiseUri($link, $uri = null)
+ {
+ $linkUri = Uri::factory($link);
+ if (!$linkUri->isAbsolute() or !$linkUri->isValid()) {
+ if ($uri !== null) {
+ $uri = Uri::factory($uri);
+
+ if ($link[0] !== '/') {
+ $link = $uri->getPath() . '/' . $link;
+ }
+
+ $link = $uri->getScheme() . '://' . $uri->getHost() . '/' . $this->canonicalizePath($link);
+ if (!Uri::factory($link)->isValid()) {
+ $link = null;
+ }
+ }
+ }
+ return $link;
+ }
+
+ /**
+ * Canonicalize relative path
+ */
+ protected function canonicalizePath($path)
+ {
+ $parts = array_filter(explode('/', $path));
+ $absolutes = array();
+ foreach ($parts as $part) {
+ if ('.' == $part) {
+ continue;
+ }
+ if ('..' == $part) {
+ array_pop($absolutes);
+ } else {
+ $absolutes[] = $part;
+ }
+ }
+ return implode('/', $absolutes);
+ }
+
+ /**
+ * Supports lazy loading of feeds using Reader::import() but
+ * delegates any other operations to the parent class.
+ *
+ * @param string $offset
+ * @return mixed
+ */
+ public function offsetGet($offset)
+ {
+ if ($offset == 'feed' && !$this->offsetExists('feed')) {
+ if (!$this->offsetExists('href')) {
+ return null;
+ }
+ $feed = Reader::import($this->offsetGet('href'));
+ $this->offsetSet('feed', $feed);
+ return $feed;
+ }
+ return parent::offsetGet($offset);
+ }
+}
diff --git a/library/Zend/Feed/Reader/Http/ClientInterface.php b/library/Zend/Feed/Reader/Http/ClientInterface.php
new file mode 100755
index 0000000000..43932f7612
--- /dev/null
+++ b/library/Zend/Feed/Reader/Http/ClientInterface.php
@@ -0,0 +1,21 @@
+ array(
+ 'DublinCore\Feed',
+ 'Atom\Feed'
+ ),
+ 'entry' => array(
+ 'Content\Entry',
+ 'DublinCore\Entry',
+ 'Atom\Entry'
+ ),
+ 'core' => array(
+ 'DublinCore\Feed',
+ 'Atom\Feed',
+ 'Content\Entry',
+ 'DublinCore\Entry',
+ 'Atom\Entry'
+ )
+ );
+
+ /**
+ * Get the Feed cache
+ *
+ * @return CacheStorage
+ */
+ public static function getCache()
+ {
+ return static::$cache;
+ }
+
+ /**
+ * Set the feed cache
+ *
+ * @param CacheStorage $cache
+ * @return void
+ */
+ public static function setCache(CacheStorage $cache)
+ {
+ static::$cache = $cache;
+ }
+
+ /**
+ * Set the HTTP client instance
+ *
+ * Sets the HTTP client object to use for retrieving the feeds.
+ *
+ * @param ZendHttp\Client $httpClient
+ * @return void
+ */
+ public static function setHttpClient(ZendHttp\Client $httpClient)
+ {
+ static::$httpClient = $httpClient;
+ }
+
+
+ /**
+ * Gets the HTTP client object. If none is set, a new ZendHttp\Client will be used.
+ *
+ * @return ZendHttp\Client
+ */
+ public static function getHttpClient()
+ {
+ if (!static::$httpClient instanceof ZendHttp\Client) {
+ static::$httpClient = new ZendHttp\Client();
+ }
+
+ return static::$httpClient;
+ }
+
+ /**
+ * Toggle using POST instead of PUT and DELETE HTTP methods
+ *
+ * Some feed implementations do not accept PUT and DELETE HTTP
+ * methods, or they can't be used because of proxies or other
+ * measures. This allows turning on using POST where PUT and
+ * DELETE would normally be used; in addition, an
+ * X-Method-Override header will be sent with a value of PUT or
+ * DELETE as appropriate.
+ *
+ * @param bool $override Whether to override PUT and DELETE.
+ * @return void
+ */
+ public static function setHttpMethodOverride($override = true)
+ {
+ static::$httpMethodOverride = $override;
+ }
+
+ /**
+ * Get the HTTP override state
+ *
+ * @return bool
+ */
+ public static function getHttpMethodOverride()
+ {
+ return static::$httpMethodOverride;
+ }
+
+ /**
+ * Set the flag indicating whether or not to use HTTP conditional GET
+ *
+ * @param bool $bool
+ * @return void
+ */
+ public static function useHttpConditionalGet($bool = true)
+ {
+ static::$httpConditionalGet = $bool;
+ }
+
+ /**
+ * Import a feed by providing a URI
+ *
+ * @param string $uri The URI to the feed
+ * @param string $etag OPTIONAL Last received ETag for this resource
+ * @param string $lastModified OPTIONAL Last-Modified value for this resource
+ * @return Feed\FeedInterface
+ * @throws Exception\RuntimeException
+ */
+ public static function import($uri, $etag = null, $lastModified = null)
+ {
+ $cache = self::getCache();
+ $feed = null;
+ $client = self::getHttpClient();
+ $client->resetParameters();
+ $headers = new ZendHttp\Headers();
+ $client->setHeaders($headers);
+ $client->setUri($uri);
+ $cacheId = 'Zend_Feed_Reader_' . md5($uri);
+
+ if (static::$httpConditionalGet && $cache) {
+ $data = $cache->getItem($cacheId);
+ if ($data) {
+ if ($etag === null) {
+ $etag = $cache->getItem($cacheId . '_etag');
+ }
+ if ($lastModified === null) {
+ $lastModified = $cache->getItem($cacheId . '_lastmodified');
+ }
+ if ($etag) {
+ $headers->addHeaderLine('If-None-Match', $etag);
+ }
+ if ($lastModified) {
+ $headers->addHeaderLine('If-Modified-Since', $lastModified);
+ }
+ }
+ $response = $client->send();
+ if ($response->getStatusCode() !== 200 && $response->getStatusCode() !== 304) {
+ throw new Exception\RuntimeException('Feed failed to load, got response code ' . $response->getStatusCode());
+ }
+ if ($response->getStatusCode() == 304) {
+ $responseXml = $data;
+ } else {
+ $responseXml = $response->getBody();
+ $cache->setItem($cacheId, $responseXml);
+ if ($response->getHeaders()->get('ETag')) {
+ $cache->setItem($cacheId . '_etag', $response->getHeaders()->get('ETag')->getFieldValue());
+ }
+ if ($response->getHeaders()->get('Last-Modified')) {
+ $cache->setItem($cacheId . '_lastmodified', $response->getHeaders()->get('Last-Modified')->getFieldValue());
+ }
+ }
+ return static::importString($responseXml);
+ } elseif ($cache) {
+ $data = $cache->getItem($cacheId);
+ if ($data) {
+ return static::importString($data);
+ }
+ $response = $client->send();
+ if ((int) $response->getStatusCode() !== 200) {
+ throw new Exception\RuntimeException('Feed failed to load, got response code ' . $response->getStatusCode());
+ }
+ $responseXml = $response->getBody();
+ $cache->setItem($cacheId, $responseXml);
+ return static::importString($responseXml);
+ } else {
+ $response = $client->send();
+ if ((int) $response->getStatusCode() !== 200) {
+ throw new Exception\RuntimeException('Feed failed to load, got response code ' . $response->getStatusCode());
+ }
+ $reader = static::importString($response->getBody());
+ $reader->setOriginalSourceUri($uri);
+ return $reader;
+ }
+ }
+
+ /**
+ * Import a feed from a remote URI
+ *
+ * Performs similarly to import(), except it uses the HTTP client passed to
+ * the method, and does not take into account cached data.
+ *
+ * Primary purpose is to make it possible to use the Reader with alternate
+ * HTTP client implementations.
+ *
+ * @param string $uri
+ * @param Http\ClientInterface $client
+ * @return self
+ * @throws Exception\RuntimeException if response is not an Http\ResponseInterface
+ */
+ public static function importRemoteFeed($uri, Http\ClientInterface $client)
+ {
+ $response = $client->get($uri);
+ if (!$response instanceof Http\ResponseInterface) {
+ throw new Exception\RuntimeException(sprintf(
+ 'Did not receive a %s\Http\ResponseInterface from the provided HTTP client; received "%s"',
+ __NAMESPACE__,
+ (is_object($response) ? get_class($response) : gettype($response))
+ ));
+ }
+
+ if ((int) $response->getStatusCode() !== 200) {
+ throw new Exception\RuntimeException('Feed failed to load, got response code ' . $response->getStatusCode());
+ }
+ $reader = static::importString($response->getBody());
+ $reader->setOriginalSourceUri($uri);
+ return $reader;
+ }
+
+ /**
+ * Import a feed from a string
+ *
+ * @param string $string
+ * @return Feed\FeedInterface
+ * @throws Exception\InvalidArgumentException
+ * @throws Exception\RuntimeException
+ */
+ public static function importString($string)
+ {
+ $trimmed = trim($string);
+ if (!is_string($string) || empty($trimmed)) {
+ throw new Exception\InvalidArgumentException('Only non empty strings are allowed as input');
+ }
+
+ $libxmlErrflag = libxml_use_internal_errors(true);
+ $oldValue = libxml_disable_entity_loader(true);
+ $dom = new DOMDocument;
+ $status = $dom->loadXML(trim($string));
+ foreach ($dom->childNodes as $child) {
+ if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid XML: Detected use of illegal DOCTYPE'
+ );
+ }
+ }
+ libxml_disable_entity_loader($oldValue);
+ libxml_use_internal_errors($libxmlErrflag);
+
+ if (!$status) {
+ // Build error message
+ $error = libxml_get_last_error();
+ if ($error && $error->message) {
+ $error->message = trim($error->message);
+ $errormsg = "DOMDocument cannot parse XML: {$error->message}";
+ } else {
+ $errormsg = "DOMDocument cannot parse XML: Please check the XML document's validity";
+ }
+ throw new Exception\RuntimeException($errormsg);
+ }
+
+ $type = static::detectType($dom);
+
+ static::registerCoreExtensions();
+
+ if (substr($type, 0, 3) == 'rss') {
+ $reader = new Feed\Rss($dom, $type);
+ } elseif (substr($type, 8, 5) == 'entry') {
+ $reader = new Entry\Atom($dom->documentElement, 0, self::TYPE_ATOM_10);
+ } elseif (substr($type, 0, 4) == 'atom') {
+ $reader = new Feed\Atom($dom, $type);
+ } else {
+ throw new Exception\RuntimeException('The URI used does not point to a '
+ . 'valid Atom, RSS or RDF feed that Zend\Feed\Reader can parse.');
+ }
+ return $reader;
+ }
+
+ /**
+ * Imports a feed from a file located at $filename.
+ *
+ * @param string $filename
+ * @throws Exception\RuntimeException
+ * @return Feed\FeedInterface
+ */
+ public static function importFile($filename)
+ {
+ ErrorHandler::start();
+ $feed = file_get_contents($filename);
+ $err = ErrorHandler::stop();
+ if ($feed === false) {
+ throw new Exception\RuntimeException("File '{$filename}' could not be loaded", 0, $err);
+ }
+ return static::importString($feed);
+ }
+
+ /**
+ * Find feed links
+ *
+ * @param $uri
+ * @return FeedSet
+ * @throws Exception\RuntimeException
+ */
+ public static function findFeedLinks($uri)
+ {
+ $client = static::getHttpClient();
+ $client->setUri($uri);
+ $response = $client->send();
+ if ($response->getStatusCode() !== 200) {
+ throw new Exception\RuntimeException("Failed to access $uri, got response code " . $response->getStatusCode());
+ }
+ $responseHtml = $response->getBody();
+ $libxmlErrflag = libxml_use_internal_errors(true);
+ $oldValue = libxml_disable_entity_loader(true);
+ $dom = new DOMDocument;
+ $status = $dom->loadHTML(trim($responseHtml));
+ libxml_disable_entity_loader($oldValue);
+ libxml_use_internal_errors($libxmlErrflag);
+ if (!$status) {
+ // Build error message
+ $error = libxml_get_last_error();
+ if ($error && $error->message) {
+ $error->message = trim($error->message);
+ $errormsg = "DOMDocument cannot parse HTML: {$error->message}";
+ } else {
+ $errormsg = "DOMDocument cannot parse HTML: Please check the XML document's validity";
+ }
+ throw new Exception\RuntimeException($errormsg);
+ }
+ $feedSet = new FeedSet;
+ $links = $dom->getElementsByTagName('link');
+ $feedSet->addLinks($links, $uri);
+ return $feedSet;
+ }
+
+ /**
+ * Detect the feed type of the provided feed
+ *
+ * @param Feed\AbstractFeed|DOMDocument|string $feed
+ * @param bool $specOnly
+ * @return string
+ * @throws Exception\InvalidArgumentException
+ * @throws Exception\RuntimeException
+ */
+ public static function detectType($feed, $specOnly = false)
+ {
+ if ($feed instanceof Feed\AbstractFeed) {
+ $dom = $feed->getDomDocument();
+ } elseif ($feed instanceof DOMDocument) {
+ $dom = $feed;
+ } elseif (is_string($feed) && !empty($feed)) {
+ ErrorHandler::start(E_NOTICE|E_WARNING);
+ ini_set('track_errors', 1);
+ $oldValue = libxml_disable_entity_loader(true);
+ $dom = new DOMDocument;
+ $status = $dom->loadXML($feed);
+ foreach ($dom->childNodes as $child) {
+ if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid XML: Detected use of illegal DOCTYPE'
+ );
+ }
+ }
+ libxml_disable_entity_loader($oldValue);
+ ini_restore('track_errors');
+ ErrorHandler::stop();
+ if (!$status) {
+ if (!isset($phpErrormsg)) {
+ if (function_exists('xdebug_is_enabled')) {
+ $phpErrormsg = '(error message not available, when XDebug is running)';
+ } else {
+ $phpErrormsg = '(error message not available)';
+ }
+ }
+ throw new Exception\RuntimeException("DOMDocument cannot parse XML: $phpErrormsg");
+ }
+ } else {
+ throw new Exception\InvalidArgumentException('Invalid object/scalar provided: must'
+ . ' be of type Zend\Feed\Reader\Feed, DomDocument or string');
+ }
+ $xpath = new DOMXPath($dom);
+
+ if ($xpath->query('/rss')->length) {
+ $type = self::TYPE_RSS_ANY;
+ $version = $xpath->evaluate('string(/rss/@version)');
+
+ if (strlen($version) > 0) {
+ switch ($version) {
+ case '2.0':
+ $type = self::TYPE_RSS_20;
+ break;
+
+ case '0.94':
+ $type = self::TYPE_RSS_094;
+ break;
+
+ case '0.93':
+ $type = self::TYPE_RSS_093;
+ break;
+
+ case '0.92':
+ $type = self::TYPE_RSS_092;
+ break;
+
+ case '0.91':
+ $type = self::TYPE_RSS_091;
+ break;
+ }
+ }
+
+ return $type;
+ }
+
+ $xpath->registerNamespace('rdf', self::NAMESPACE_RDF);
+
+ if ($xpath->query('/rdf:RDF')->length) {
+ $xpath->registerNamespace('rss', self::NAMESPACE_RSS_10);
+
+ if ($xpath->query('/rdf:RDF/rss:channel')->length
+ || $xpath->query('/rdf:RDF/rss:image')->length
+ || $xpath->query('/rdf:RDF/rss:item')->length
+ || $xpath->query('/rdf:RDF/rss:textinput')->length
+ ) {
+ return self::TYPE_RSS_10;
+ }
+
+ $xpath->registerNamespace('rss', self::NAMESPACE_RSS_090);
+
+ if ($xpath->query('/rdf:RDF/rss:channel')->length
+ || $xpath->query('/rdf:RDF/rss:image')->length
+ || $xpath->query('/rdf:RDF/rss:item')->length
+ || $xpath->query('/rdf:RDF/rss:textinput')->length
+ ) {
+ return self::TYPE_RSS_090;
+ }
+ }
+
+ $xpath->registerNamespace('atom', self::NAMESPACE_ATOM_10);
+
+ if ($xpath->query('//atom:feed')->length) {
+ return self::TYPE_ATOM_10;
+ }
+
+ if ($xpath->query('//atom:entry')->length) {
+ if ($specOnly == true) {
+ return self::TYPE_ATOM_10;
+ } else {
+ return self::TYPE_ATOM_10_ENTRY;
+ }
+ }
+
+ $xpath->registerNamespace('atom', self::NAMESPACE_ATOM_03);
+
+ if ($xpath->query('//atom:feed')->length) {
+ return self::TYPE_ATOM_03;
+ }
+
+ return self::TYPE_ANY;
+ }
+
+ /**
+ * Set plugin manager for use with Extensions
+ *
+ * @param ExtensionManagerInterface $extensionManager
+ */
+ public static function setExtensionManager(ExtensionManagerInterface $extensionManager)
+ {
+ static::$extensionManager = $extensionManager;
+ }
+
+ /**
+ * Get plugin manager for use with Extensions
+ *
+ * @return ExtensionManagerInterface
+ */
+ public static function getExtensionManager()
+ {
+ if (!isset(static::$extensionManager)) {
+ static::setExtensionManager(new ExtensionManager());
+ }
+ return static::$extensionManager;
+ }
+
+ /**
+ * Register an Extension by name
+ *
+ * @param string $name
+ * @return void
+ * @throws Exception\RuntimeException if unable to resolve Extension class
+ */
+ public static function registerExtension($name)
+ {
+ $feedName = $name . '\Feed';
+ $entryName = $name . '\Entry';
+ $manager = static::getExtensionManager();
+ if (static::isRegistered($name)) {
+ if ($manager->has($feedName) || $manager->has($entryName)) {
+ return;
+ }
+ }
+
+ if (!$manager->has($feedName) && !$manager->has($entryName)) {
+ throw new Exception\RuntimeException('Could not load extension: ' . $name
+ . ' using Plugin Loader. Check prefix paths are configured and extension exists.');
+ }
+ if ($manager->has($feedName)) {
+ static::$extensions['feed'][] = $feedName;
+ }
+ if ($manager->has($entryName)) {
+ static::$extensions['entry'][] = $entryName;
+ }
+ }
+
+ /**
+ * Is a given named Extension registered?
+ *
+ * @param string $extensionName
+ * @return bool
+ */
+ public static function isRegistered($extensionName)
+ {
+ $feedName = $extensionName . '\Feed';
+ $entryName = $extensionName . '\Entry';
+ if (in_array($feedName, static::$extensions['feed'])
+ || in_array($entryName, static::$extensions['entry'])
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get a list of extensions
+ *
+ * @return array
+ */
+ public static function getExtensions()
+ {
+ return static::$extensions;
+ }
+
+ /**
+ * Reset class state to defaults
+ *
+ * @return void
+ */
+ public static function reset()
+ {
+ static::$cache = null;
+ static::$httpClient = null;
+ static::$httpMethodOverride = false;
+ static::$httpConditionalGet = false;
+ static::$extensionManager = null;
+ static::$extensions = array(
+ 'feed' => array(
+ 'DublinCore\Feed',
+ 'Atom\Feed'
+ ),
+ 'entry' => array(
+ 'Content\Entry',
+ 'DublinCore\Entry',
+ 'Atom\Entry'
+ ),
+ 'core' => array(
+ 'DublinCore\Feed',
+ 'Atom\Feed',
+ 'Content\Entry',
+ 'DublinCore\Entry',
+ 'Atom\Entry'
+ )
+ );
+ }
+
+ /**
+ * Register core (default) extensions
+ *
+ * @return void
+ */
+ protected static function registerCoreExtensions()
+ {
+ static::registerExtension('DublinCore');
+ static::registerExtension('Content');
+ static::registerExtension('Atom');
+ static::registerExtension('Slash');
+ static::registerExtension('WellFormedWeb');
+ static::registerExtension('Thread');
+ static::registerExtension('Podcast');
+ }
+
+ /**
+ * Utility method to apply array_unique operation to a multidimensional
+ * array.
+ *
+ * @param array
+ * @return array
+ */
+ public static function arrayUnique(array $array)
+ {
+ foreach ($array as &$value) {
+ $value = serialize($value);
+ }
+ $array = array_unique($array);
+ foreach ($array as &$value) {
+ $value = unserialize($value);
+ }
+ return $array;
+ }
+}
diff --git a/library/Zend/Feed/Uri.php b/library/Zend/Feed/Uri.php
new file mode 100755
index 0000000000..940bce11ab
--- /dev/null
+++ b/library/Zend/Feed/Uri.php
@@ -0,0 +1,184 @@
+valid = false;
+ return;
+ }
+
+ $this->scheme = isset($parsed['scheme']) ? $parsed['scheme'] : null;
+ $this->host = isset($parsed['host']) ? $parsed['host'] : null;
+ $this->port = isset($parsed['port']) ? $parsed['port'] : null;
+ $this->user = isset($parsed['user']) ? $parsed['user'] : null;
+ $this->pass = isset($parsed['pass']) ? $parsed['pass'] : null;
+ $this->path = isset($parsed['path']) ? $parsed['path'] : null;
+ $this->query = isset($parsed['query']) ? $parsed['query'] : null;
+ $this->fragment = isset($parsed['fragment']) ? $parsed['fragment'] : null;
+ }
+
+ /**
+ * Create an instance
+ *
+ * Useful for chained validations
+ *
+ * @param string $uri
+ * @return self
+ */
+ public static function factory($uri)
+ {
+ return new static($uri);
+ }
+
+ /**
+ * Retrieve the host
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ /**
+ * Retrieve the URI path
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * Retrieve the scheme
+ *
+ * @return string
+ */
+ public function getScheme()
+ {
+ return $this->scheme;
+ }
+
+ /**
+ * Is the URI valid?
+ *
+ * @return bool
+ */
+ public function isValid()
+ {
+ if (false === $this->valid) {
+ return false;
+ }
+
+ if ($this->scheme && !in_array($this->scheme, $this->validSchemes)) {
+ return false;
+ }
+
+ if ($this->host) {
+ if ($this->path && substr($this->path, 0, 1) != '/') {
+ return false;
+ }
+ return true;
+ }
+
+ // no host, but user and/or port... what?
+ if ($this->user || $this->port) {
+ return false;
+ }
+
+ if ($this->path) {
+ // Check path-only (no host) URI
+ if (substr($this->path, 0, 2) == '//') {
+ return false;
+ }
+ return true;
+ }
+
+ if (! ($this->query || $this->fragment)) {
+ // No host, path, query or fragment - this is not a valid URI
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Is the URI absolute?
+ *
+ * @return bool
+ */
+ public function isAbsolute()
+ {
+ return ($this->scheme !== null);
+ }
+}
diff --git a/library/Zend/Feed/Writer/AbstractFeed.php b/library/Zend/Feed/Writer/AbstractFeed.php
new file mode 100755
index 0000000000..1dd6fe54b6
--- /dev/null
+++ b/library/Zend/Feed/Writer/AbstractFeed.php
@@ -0,0 +1,846 @@
+_loadExtensions();
+ }
+
+ /**
+ * Set a single author
+ *
+ * The following option keys are supported:
+ * 'name' => (string) The name
+ * 'email' => (string) An optional email
+ * 'uri' => (string) An optional and valid URI
+ *
+ * @param array $author
+ * @throws Exception\InvalidArgumentException If any value of $author not follow the format.
+ * @return AbstractFeed
+ */
+ public function addAuthor(array $author)
+ {
+ // Check array values
+ if (!array_key_exists('name', $author)
+ || empty($author['name'])
+ || !is_string($author['name'])
+ ) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid parameter: author array must include a "name" key with a non-empty string value');
+ }
+
+ if (isset($author['email'])) {
+ if (empty($author['email']) || !is_string($author['email'])) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid parameter: "email" array value must be a non-empty string');
+ }
+ }
+ if (isset($author['uri'])) {
+ if (empty($author['uri']) || !is_string($author['uri']) ||
+ !Uri::factory($author['uri'])->isValid()
+ ) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid parameter: "uri" array value must be a non-empty string and valid URI/IRI');
+ }
+ }
+
+ $this->data['authors'][] = $author;
+
+ return $this;
+ }
+
+ /**
+ * Set an array with feed authors
+ *
+ * @see addAuthor
+ * @param array $authors
+ * @return AbstractFeed
+ */
+ public function addAuthors(array $authors)
+ {
+ foreach ($authors as $author) {
+ $this->addAuthor($author);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the copyright entry
+ *
+ * @param string $copyright
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractFeed
+ */
+ public function setCopyright($copyright)
+ {
+ if (empty($copyright) || !is_string($copyright)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: parameter must be a non-empty string');
+ }
+ $this->data['copyright'] = $copyright;
+
+ return $this;
+ }
+
+ /**
+ * Set the feed creation date
+ *
+ * @param null|int|DateTime
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractFeed
+ */
+ public function setDateCreated($date = null)
+ {
+ if ($date === null) {
+ $date = new DateTime();
+ } elseif (is_int($date)) {
+ $date = new DateTime('@' . $date);
+ } elseif (!$date instanceof DateTime) {
+ throw new Exception\InvalidArgumentException('Invalid DateTime object or UNIX Timestamp'
+ . ' passed as parameter');
+ }
+ $this->data['dateCreated'] = $date;
+
+ return $this;
+ }
+
+ /**
+ * Set the feed modification date
+ *
+ * @param null|int|DateTime
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractFeed
+ */
+ public function setDateModified($date = null)
+ {
+ if ($date === null) {
+ $date = new DateTime();
+ } elseif (is_int($date)) {
+ $date = new DateTime('@' . $date);
+ } elseif (!$date instanceof DateTime) {
+ throw new Exception\InvalidArgumentException('Invalid DateTime object or UNIX Timestamp'
+ . ' passed as parameter');
+ }
+ $this->data['dateModified'] = $date;
+
+ return $this;
+ }
+
+ /**
+ * Set the feed last-build date. Ignored for Atom 1.0.
+ *
+ * @param null|int|DateTime
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractFeed
+ */
+ public function setLastBuildDate($date = null)
+ {
+ if ($date === null) {
+ $date = new DateTime();
+ } elseif (is_int($date)) {
+ $date = new DateTime('@' . $date);
+ } elseif (!$date instanceof DateTime) {
+ throw new Exception\InvalidArgumentException('Invalid DateTime object or UNIX Timestamp'
+ . ' passed as parameter');
+ }
+ $this->data['lastBuildDate'] = $date;
+
+ return $this;
+ }
+
+ /**
+ * Set the feed description
+ *
+ * @param string $description
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractFeed
+ */
+ public function setDescription($description)
+ {
+ if (empty($description) || !is_string($description)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: parameter must be a non-empty string');
+ }
+ $this->data['description'] = $description;
+
+ return $this;
+ }
+
+ /**
+ * Set the feed generator entry
+ *
+ * @param array|string $name
+ * @param null|string $version
+ * @param null|string $uri
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractFeed
+ */
+ public function setGenerator($name, $version = null, $uri = null)
+ {
+ if (is_array($name)) {
+ $data = $name;
+ if (empty($data['name']) || !is_string($data['name'])) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: "name" must be a non-empty string');
+ }
+ $generator = array('name' => $data['name']);
+ if (isset($data['version'])) {
+ if (empty($data['version']) || !is_string($data['version'])) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: "version" must be a non-empty string');
+ }
+ $generator['version'] = $data['version'];
+ }
+ if (isset($data['uri'])) {
+ if (empty($data['uri']) || !is_string($data['uri']) || !Uri::factory($data['uri'])->isValid()) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: "uri" must be a non-empty string and a valid URI/IRI');
+ }
+ $generator['uri'] = $data['uri'];
+ }
+ } else {
+ if (empty($name) || !is_string($name)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: "name" must be a non-empty string');
+ }
+ $generator = array('name' => $name);
+ if (isset($version)) {
+ if (empty($version) || !is_string($version)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: "version" must be a non-empty string');
+ }
+ $generator['version'] = $version;
+ }
+ if (isset($uri)) {
+ if (empty($uri) || !is_string($uri) || !Uri::factory($uri)->isValid()) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: "uri" must be a non-empty string and a valid URI/IRI');
+ }
+ $generator['uri'] = $uri;
+ }
+ }
+ $this->data['generator'] = $generator;
+
+ return $this;
+ }
+
+ /**
+ * Set the feed ID - URI or URN (via PCRE pattern) supported
+ *
+ * @param string $id
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractFeed
+ */
+ public function setId($id)
+ {
+ if ((empty($id) || !is_string($id) || !Uri::factory($id)->isValid())
+ && !preg_match("#^urn:[a-zA-Z0-9][a-zA-Z0-9\-]{1,31}:([a-zA-Z0-9\(\)\+\,\.\:\=\@\;\$\_\!\*\-]|%[0-9a-fA-F]{2})*#", $id)
+ && !$this->_validateTagUri($id)
+ ) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: parameter must be a non-empty string and valid URI/IRI');
+ }
+ $this->data['id'] = $id;
+
+ return $this;
+ }
+
+ /**
+ * Validate a URI using the tag scheme (RFC 4151)
+ *
+ * @param string $id
+ * @return bool
+ */
+ protected function _validateTagUri($id)
+ {
+ if (preg_match('/^tag:(?P.*),(?P\d{4}-?\d{0,2}-?\d{0,2}):(?P.*)(.*:)*$/', $id, $matches)) {
+ $dvalid = false;
+ $date = $matches['date'];
+ $d6 = strtotime($date);
+ if ((strlen($date) == 4) && $date <= date('Y')) {
+ $dvalid = true;
+ } elseif ((strlen($date) == 7) && ($d6 < strtotime("now"))) {
+ $dvalid = true;
+ } elseif ((strlen($date) == 10) && ($d6 < strtotime("now"))) {
+ $dvalid = true;
+ }
+ $validator = new Validator\EmailAddress;
+ if ($validator->isValid($matches['name'])) {
+ $nvalid = true;
+ } else {
+ $nvalid = $validator->isValid('info@' . $matches['name']);
+ }
+ return $dvalid && $nvalid;
+ }
+ return false;
+ }
+
+ /**
+ * Set a feed image (URI at minimum). Parameter is a single array with the
+ * required key 'uri'. When rendering as RSS, the required keys are 'uri',
+ * 'title' and 'link'. RSS also specifies three optional parameters 'width',
+ * 'height' and 'description'. Only 'uri' is required and used for Atom rendering.
+ *
+ * @param array $data
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractFeed
+ */
+ public function setImage(array $data)
+ {
+ if (empty($data['uri']) || !is_string($data['uri'])
+ || !Uri::factory($data['uri'])->isValid()
+ ) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: parameter \'uri\''
+ . ' must be a non-empty string and valid URI/IRI');
+ }
+ $this->data['image'] = $data;
+
+ return $this;
+ }
+
+ /**
+ * Set the feed language
+ *
+ * @param string $language
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractFeed
+ */
+ public function setLanguage($language)
+ {
+ if (empty($language) || !is_string($language)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: parameter must be a non-empty string');
+ }
+ $this->data['language'] = $language;
+
+ return $this;
+ }
+
+ /**
+ * Set a link to the HTML source
+ *
+ * @param string $link
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractFeed
+ */
+ public function setLink($link)
+ {
+ if (empty($link) || !is_string($link) || !Uri::factory($link)->isValid()) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: parameter must be a non-empty string and valid URI/IRI');
+ }
+ $this->data['link'] = $link;
+
+ return $this;
+ }
+
+ /**
+ * Set a link to an XML feed for any feed type/version
+ *
+ * @param string $link
+ * @param string $type
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractFeed
+ */
+ public function setFeedLink($link, $type)
+ {
+ if (empty($link) || !is_string($link) || !Uri::factory($link)->isValid()) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: "link"" must be a non-empty string and valid URI/IRI');
+ }
+ if (!in_array(strtolower($type), array('rss', 'rdf', 'atom'))) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: "type"; You must declare the type of feed the link points to, i.e. RSS, RDF or Atom');
+ }
+ $this->data['feedLinks'][strtolower($type)] = $link;
+
+ return $this;
+ }
+
+ /**
+ * Set the feed title
+ *
+ * @param string $title
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractFeed
+ */
+ public function setTitle($title)
+ {
+ if (empty($title) || !is_string($title)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: parameter must be a non-empty string');
+ }
+ $this->data['title'] = $title;
+
+ return $this;
+ }
+
+ /**
+ * Set the feed character encoding
+ *
+ * @param string $encoding
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractFeed
+ */
+ public function setEncoding($encoding)
+ {
+ if (empty($encoding) || !is_string($encoding)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: parameter must be a non-empty string');
+ }
+ $this->data['encoding'] = $encoding;
+
+ return $this;
+ }
+
+ /**
+ * Set the feed's base URL
+ *
+ * @param string $url
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractFeed
+ */
+ public function setBaseUrl($url)
+ {
+ if (empty($url) || !is_string($url) || !Uri::factory($url)->isValid()) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: "url" array value'
+ . ' must be a non-empty string and valid URI/IRI');
+ }
+ $this->data['baseUrl'] = $url;
+
+ return $this;
+ }
+
+ /**
+ * Add a Pubsubhubbub hub endpoint URL
+ *
+ * @param string $url
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractFeed
+ */
+ public function addHub($url)
+ {
+ if (empty($url) || !is_string($url) || !Uri::factory($url)->isValid()) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: "url" array value'
+ . ' must be a non-empty string and valid URI/IRI');
+ }
+ if (!isset($this->data['hubs'])) {
+ $this->data['hubs'] = array();
+ }
+ $this->data['hubs'][] = $url;
+
+ return $this;
+ }
+
+ /**
+ * Add Pubsubhubbub hub endpoint URLs
+ *
+ * @param array $urls
+ * @return AbstractFeed
+ */
+ public function addHubs(array $urls)
+ {
+ foreach ($urls as $url) {
+ $this->addHub($url);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a feed category
+ *
+ * @param array $category
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractFeed
+ */
+ public function addCategory(array $category)
+ {
+ if (!isset($category['term'])) {
+ throw new Exception\InvalidArgumentException('Each category must be an array and '
+ . 'contain at least a "term" element containing the machine '
+ . ' readable category name');
+ }
+ if (isset($category['scheme'])) {
+ if (empty($category['scheme'])
+ || !is_string($category['scheme'])
+ || !Uri::factory($category['scheme'])->isValid()
+ ) {
+ throw new Exception\InvalidArgumentException('The Atom scheme or RSS domain of'
+ . ' a category must be a valid URI');
+ }
+ }
+ if (!isset($this->data['categories'])) {
+ $this->data['categories'] = array();
+ }
+ $this->data['categories'][] = $category;
+
+ return $this;
+ }
+
+ /**
+ * Set an array of feed categories
+ *
+ * @param array $categories
+ * @return AbstractFeed
+ */
+ public function addCategories(array $categories)
+ {
+ foreach ($categories as $category) {
+ $this->addCategory($category);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get a single author
+ *
+ * @param int $index
+ * @return string|null
+ */
+ public function getAuthor($index = 0)
+ {
+ if (isset($this->data['authors'][$index])) {
+ return $this->data['authors'][$index];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get an array with feed authors
+ *
+ * @return array
+ */
+ public function getAuthors()
+ {
+ if (!array_key_exists('authors', $this->data)) {
+ return null;
+ }
+ return $this->data['authors'];
+ }
+
+ /**
+ * Get the copyright entry
+ *
+ * @return string|null
+ */
+ public function getCopyright()
+ {
+ if (!array_key_exists('copyright', $this->data)) {
+ return null;
+ }
+ return $this->data['copyright'];
+ }
+
+ /**
+ * Get the feed creation date
+ *
+ * @return string|null
+ */
+ public function getDateCreated()
+ {
+ if (!array_key_exists('dateCreated', $this->data)) {
+ return null;
+ }
+ return $this->data['dateCreated'];
+ }
+
+ /**
+ * Get the feed modification date
+ *
+ * @return string|null
+ */
+ public function getDateModified()
+ {
+ if (!array_key_exists('dateModified', $this->data)) {
+ return null;
+ }
+ return $this->data['dateModified'];
+ }
+
+ /**
+ * Get the feed last-build date
+ *
+ * @return string|null
+ */
+ public function getLastBuildDate()
+ {
+ if (!array_key_exists('lastBuildDate', $this->data)) {
+ return null;
+ }
+ return $this->data['lastBuildDate'];
+ }
+
+ /**
+ * Get the feed description
+ *
+ * @return string|null
+ */
+ public function getDescription()
+ {
+ if (!array_key_exists('description', $this->data)) {
+ return null;
+ }
+ return $this->data['description'];
+ }
+
+ /**
+ * Get the feed generator entry
+ *
+ * @return string|null
+ */
+ public function getGenerator()
+ {
+ if (!array_key_exists('generator', $this->data)) {
+ return null;
+ }
+ return $this->data['generator'];
+ }
+
+ /**
+ * Get the feed ID
+ *
+ * @return string|null
+ */
+ public function getId()
+ {
+ if (!array_key_exists('id', $this->data)) {
+ return null;
+ }
+ return $this->data['id'];
+ }
+
+ /**
+ * Get the feed image URI
+ *
+ * @return array
+ */
+ public function getImage()
+ {
+ if (!array_key_exists('image', $this->data)) {
+ return null;
+ }
+ return $this->data['image'];
+ }
+
+ /**
+ * Get the feed language
+ *
+ * @return string|null
+ */
+ public function getLanguage()
+ {
+ if (!array_key_exists('language', $this->data)) {
+ return null;
+ }
+ return $this->data['language'];
+ }
+
+ /**
+ * Get a link to the HTML source
+ *
+ * @return string|null
+ */
+ public function getLink()
+ {
+ if (!array_key_exists('link', $this->data)) {
+ return null;
+ }
+ return $this->data['link'];
+ }
+
+ /**
+ * Get a link to the XML feed
+ *
+ * @return string|null
+ */
+ public function getFeedLinks()
+ {
+ if (!array_key_exists('feedLinks', $this->data)) {
+ return null;
+ }
+ return $this->data['feedLinks'];
+ }
+
+ /**
+ * Get the feed title
+ *
+ * @return string|null
+ */
+ public function getTitle()
+ {
+ if (!array_key_exists('title', $this->data)) {
+ return null;
+ }
+ return $this->data['title'];
+ }
+
+ /**
+ * Get the feed character encoding
+ *
+ * @return string|null
+ */
+ public function getEncoding()
+ {
+ if (!array_key_exists('encoding', $this->data)) {
+ return 'UTF-8';
+ }
+ return $this->data['encoding'];
+ }
+
+ /**
+ * Get the feed's base url
+ *
+ * @return string|null
+ */
+ public function getBaseUrl()
+ {
+ if (!array_key_exists('baseUrl', $this->data)) {
+ return null;
+ }
+ return $this->data['baseUrl'];
+ }
+
+ /**
+ * Get the URLs used as Pubsubhubbub hubs endpoints
+ *
+ * @return string|null
+ */
+ public function getHubs()
+ {
+ if (!array_key_exists('hubs', $this->data)) {
+ return null;
+ }
+ return $this->data['hubs'];
+ }
+
+ /**
+ * Get the feed categories
+ *
+ * @return string|null
+ */
+ public function getCategories()
+ {
+ if (!array_key_exists('categories', $this->data)) {
+ return null;
+ }
+ return $this->data['categories'];
+ }
+
+ /**
+ * Resets the instance and deletes all data
+ *
+ * @return void
+ */
+ public function reset()
+ {
+ $this->data = array();
+ }
+
+ /**
+ * Set the current feed type being exported to "rss" or "atom". This allows
+ * other objects to gracefully choose whether to execute or not, depending
+ * on their appropriateness for the current type, e.g. renderers.
+ *
+ * @param string $type
+ * @return AbstractFeed
+ */
+ public function setType($type)
+ {
+ $this->type = $type;
+ return $this;
+ }
+
+ /**
+ * Retrieve the current or last feed type exported.
+ *
+ * @return string Value will be "rss" or "atom"
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * Unset a specific data point
+ *
+ * @param string $name
+ * @return AbstractFeed
+ */
+ public function remove($name)
+ {
+ if (isset($this->data[$name])) {
+ unset($this->data[$name]);
+ }
+ return $this;
+ }
+
+ /**
+ * Method overloading: call given method on first extension implementing it
+ *
+ * @param string $method
+ * @param array $args
+ * @return mixed
+ * @throws Exception\BadMethodCallException if no extensions implements the method
+ */
+ public function __call($method, $args)
+ {
+ foreach ($this->extensions as $extension) {
+ try {
+ return call_user_func_array(array($extension, $method), $args);
+ } catch (Exception\BadMethodCallException $e) {
+ }
+ }
+ throw new Exception\BadMethodCallException(
+ 'Method: ' . $method . ' does not exist and could not be located on a registered Extension'
+ );
+ }
+
+ /**
+ * Load extensions from Zend\Feed\Writer\Writer
+ *
+ * @throws Exception\RuntimeException
+ * @return void
+ */
+ protected function _loadExtensions()
+ {
+ $all = Writer::getExtensions();
+ $manager = Writer::getExtensionManager();
+ $exts = $all['feed'];
+ foreach ($exts as $ext) {
+ if (!$manager->has($ext)) {
+ throw new Exception\RuntimeException(sprintf('Unable to load extension "%s"; could not resolve to class', $ext));
+ }
+ $this->extensions[$ext] = $manager->get($ext);
+ $this->extensions[$ext]->setEncoding($this->getEncoding());
+ }
+ }
+}
diff --git a/library/Zend/Feed/Writer/Deleted.php b/library/Zend/Feed/Writer/Deleted.php
new file mode 100755
index 0000000000..b91ee09452
--- /dev/null
+++ b/library/Zend/Feed/Writer/Deleted.php
@@ -0,0 +1,236 @@
+data['encoding'] = $encoding;
+
+ return $this;
+ }
+
+ /**
+ * Get the feed character encoding
+ *
+ * @return string|null
+ */
+ public function getEncoding()
+ {
+ if (!array_key_exists('encoding', $this->data)) {
+ return 'UTF-8';
+ }
+ return $this->data['encoding'];
+ }
+
+ /**
+ * Unset a specific data point
+ *
+ * @param string $name
+ * @return Deleted
+ */
+ public function remove($name)
+ {
+ if (isset($this->data[$name])) {
+ unset($this->data[$name]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the current feed type being exported to "rss" or "atom". This allows
+ * other objects to gracefully choose whether to execute or not, depending
+ * on their appropriateness for the current type, e.g. renderers.
+ *
+ * @param string $type
+ * @return Deleted
+ */
+ public function setType($type)
+ {
+ $this->type = $type;
+ return $this;
+ }
+
+ /**
+ * Retrieve the current or last feed type exported.
+ *
+ * @return string Value will be "rss" or "atom"
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * Set reference
+ *
+ * @param $reference
+ * @throws Exception\InvalidArgumentException
+ * @return Deleted
+ */
+ public function setReference($reference)
+ {
+ if (empty($reference) || !is_string($reference)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: reference must be a non-empty string');
+ }
+ $this->data['reference'] = $reference;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getReference()
+ {
+ if (!array_key_exists('reference', $this->data)) {
+ return null;
+ }
+ return $this->data['reference'];
+ }
+
+ /**
+ * Set when
+ *
+ * @param null|string|DateTime $date
+ * @throws Exception\InvalidArgumentException
+ * @return Deleted
+ */
+ public function setWhen($date = null)
+ {
+ if ($date === null) {
+ $date = new DateTime();
+ } elseif (is_int($date)) {
+ $date = new DateTime('@' . $date);
+ } elseif (!$date instanceof DateTime) {
+ throw new Exception\InvalidArgumentException('Invalid DateTime object or UNIX Timestamp'
+ . ' passed as parameter');
+ }
+ $this->data['when'] = $date;
+
+ return $this;
+ }
+
+ /**
+ * @return DateTime
+ */
+ public function getWhen()
+ {
+ if (!array_key_exists('when', $this->data)) {
+ return null;
+ }
+ return $this->data['when'];
+ }
+
+ /**
+ * Set by
+ *
+ * @param array $by
+ * @throws Exception\InvalidArgumentException
+ * @return Deleted
+ */
+ public function setBy(array $by)
+ {
+ $author = array();
+ if (!array_key_exists('name', $by)
+ || empty($by['name'])
+ || !is_string($by['name'])
+ ) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: author array must include a'
+ . ' "name" key with a non-empty string value');
+ }
+ $author['name'] = $by['name'];
+ if (isset($by['email'])) {
+ if (empty($by['email']) || !is_string($by['email'])) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: "email" array'
+ . ' value must be a non-empty string');
+ }
+ $author['email'] = $by['email'];
+ }
+ if (isset($by['uri'])) {
+ if (empty($by['uri'])
+ || !is_string($by['uri'])
+ || !Uri::factory($by['uri'])->isValid()
+ ) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: "uri" array value must'
+ . ' be a non-empty string and valid URI/IRI');
+ }
+ $author['uri'] = $by['uri'];
+ }
+ $this->data['by'] = $author;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getBy()
+ {
+ if (!array_key_exists('by', $this->data)) {
+ return null;
+ }
+ return $this->data['by'];
+ }
+
+ /**
+ * @param string $comment
+ * @return Deleted
+ */
+ public function setComment($comment)
+ {
+ $this->data['comment'] = $comment;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getComment()
+ {
+ if (!array_key_exists('comment', $this->data)) {
+ return null;
+ }
+ return $this->data['comment'];
+ }
+}
diff --git a/library/Zend/Feed/Writer/Entry.php b/library/Zend/Feed/Writer/Entry.php
new file mode 100755
index 0000000000..c5b1085ace
--- /dev/null
+++ b/library/Zend/Feed/Writer/Entry.php
@@ -0,0 +1,765 @@
+_loadExtensions();
+ }
+
+ /**
+ * Set a single author
+ *
+ * The following option keys are supported:
+ * 'name' => (string) The name
+ * 'email' => (string) An optional email
+ * 'uri' => (string) An optional and valid URI
+ *
+ * @param array $author
+ * @throws Exception\InvalidArgumentException If any value of $author not follow the format.
+ * @return Entry
+ */
+ public function addAuthor(array $author)
+ {
+ // Check array values
+ if (!array_key_exists('name', $author)
+ || empty($author['name'])
+ || !is_string($author['name'])
+ ) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid parameter: author array must include a "name" key with a non-empty string value');
+ }
+
+ if (isset($author['email'])) {
+ if (empty($author['email']) || !is_string($author['email'])) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid parameter: "email" array value must be a non-empty string');
+ }
+ }
+ if (isset($author['uri'])) {
+ if (empty($author['uri']) || !is_string($author['uri']) ||
+ !Uri::factory($author['uri'])->isValid()
+ ) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid parameter: "uri" array value must be a non-empty string and valid URI/IRI');
+ }
+ }
+
+ $this->data['authors'][] = $author;
+
+ return $this;
+ }
+
+ /**
+ * Set an array with feed authors
+ *
+ * @see addAuthor
+ * @param array $authors
+ * @return Entry
+ */
+ public function addAuthors(array $authors)
+ {
+ foreach ($authors as $author) {
+ $this->addAuthor($author);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the feed character encoding
+ *
+ * @param string $encoding
+ * @throws Exception\InvalidArgumentException
+ * @return Entry
+ */
+ public function setEncoding($encoding)
+ {
+ if (empty($encoding) || !is_string($encoding)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: parameter must be a non-empty string');
+ }
+ $this->data['encoding'] = $encoding;
+
+ return $this;
+ }
+
+ /**
+ * Get the feed character encoding
+ *
+ * @return string|null
+ */
+ public function getEncoding()
+ {
+ if (!array_key_exists('encoding', $this->data)) {
+ return 'UTF-8';
+ }
+ return $this->data['encoding'];
+ }
+
+ /**
+ * Set the copyright entry
+ *
+ * @param string $copyright
+ * @throws Exception\InvalidArgumentException
+ * @return Entry
+ */
+ public function setCopyright($copyright)
+ {
+ if (empty($copyright) || !is_string($copyright)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: parameter must be a non-empty string');
+ }
+ $this->data['copyright'] = $copyright;
+
+ return $this;
+ }
+
+ /**
+ * Set the entry's content
+ *
+ * @param string $content
+ * @throws Exception\InvalidArgumentException
+ * @return Entry
+ */
+ public function setContent($content)
+ {
+ if (empty($content) || !is_string($content)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: parameter must be a non-empty string');
+ }
+ $this->data['content'] = $content;
+
+ return $this;
+ }
+
+ /**
+ * Set the feed creation date
+ *
+ * @param null|int|DateTime $date
+ * @throws Exception\InvalidArgumentException
+ * @return Entry
+ */
+ public function setDateCreated($date = null)
+ {
+ if ($date === null) {
+ $date = new DateTime();
+ } elseif (is_int($date)) {
+ $date = new DateTime('@' . $date);
+ } elseif (!$date instanceof DateTime) {
+ throw new Exception\InvalidArgumentException('Invalid DateTime object or UNIX Timestamp passed as parameter');
+ }
+ $this->data['dateCreated'] = $date;
+
+ return $this;
+ }
+
+ /**
+ * Set the feed modification date
+ *
+ * @param null|int|DateTime $date
+ * @throws Exception\InvalidArgumentException
+ * @return Entry
+ */
+ public function setDateModified($date = null)
+ {
+ if ($date === null) {
+ $date = new DateTime();
+ } elseif (is_int($date)) {
+ $date = new DateTime('@' . $date);
+ } elseif (!$date instanceof DateTime) {
+ throw new Exception\InvalidArgumentException('Invalid DateTime object or UNIX Timestamp passed as parameter');
+ }
+ $this->data['dateModified'] = $date;
+
+ return $this;
+ }
+
+ /**
+ * Set the feed description
+ *
+ * @param string $description
+ * @throws Exception\InvalidArgumentException
+ * @return Entry
+ */
+ public function setDescription($description)
+ {
+ if (empty($description) || !is_string($description)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: parameter must be a non-empty string');
+ }
+ $this->data['description'] = $description;
+
+ return $this;
+ }
+
+ /**
+ * Set the feed ID
+ *
+ * @param string $id
+ * @throws Exception\InvalidArgumentException
+ * @return Entry
+ */
+ public function setId($id)
+ {
+ if (empty($id) || !is_string($id)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: parameter must be a non-empty string');
+ }
+ $this->data['id'] = $id;
+
+ return $this;
+ }
+
+ /**
+ * Set a link to the HTML source of this entry
+ *
+ * @param string $link
+ * @throws Exception\InvalidArgumentException
+ * @return Entry
+ */
+ public function setLink($link)
+ {
+ if (empty($link) || !is_string($link) || !Uri::factory($link)->isValid()) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: parameter must be a non-empty string and valid URI/IRI');
+ }
+ $this->data['link'] = $link;
+
+ return $this;
+ }
+
+ /**
+ * Set the number of comments associated with this entry
+ *
+ * @param int $count
+ * @throws Exception\InvalidArgumentException
+ * @return Entry
+ */
+ public function setCommentCount($count)
+ {
+ if (!is_numeric($count) || (int) $count != $count || (int) $count < 0) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: "count" must be a positive integer number or zero');
+ }
+ $this->data['commentCount'] = (int) $count;
+
+ return $this;
+ }
+
+ /**
+ * Set a link to a HTML page containing comments associated with this entry
+ *
+ * @param string $link
+ * @throws Exception\InvalidArgumentException
+ * @return Entry
+ */
+ public function setCommentLink($link)
+ {
+ if (empty($link) || !is_string($link) || !Uri::factory($link)->isValid()) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: "link" must be a non-empty string and valid URI/IRI');
+ }
+ $this->data['commentLink'] = $link;
+
+ return $this;
+ }
+
+ /**
+ * Set a link to an XML feed for any comments associated with this entry
+ *
+ * @param array $link
+ * @throws Exception\InvalidArgumentException
+ * @return Entry
+ */
+ public function setCommentFeedLink(array $link)
+ {
+ if (!isset($link['uri']) || !is_string($link['uri']) || !Uri::factory($link['uri'])->isValid()) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: "link" must be a non-empty string and valid URI/IRI');
+ }
+ if (!isset($link['type']) || !in_array($link['type'], array('atom', 'rss', 'rdf'))) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: "type" must be one'
+ . ' of "atom", "rss" or "rdf"');
+ }
+ if (!isset($this->data['commentFeedLinks'])) {
+ $this->data['commentFeedLinks'] = array();
+ }
+ $this->data['commentFeedLinks'][] = $link;
+
+ return $this;
+ }
+
+ /**
+ * Set a links to an XML feed for any comments associated with this entry.
+ * Each link is an array with keys "uri" and "type", where type is one of:
+ * "atom", "rss" or "rdf".
+ *
+ * @param array $links
+ * @return Entry
+ */
+ public function setCommentFeedLinks(array $links)
+ {
+ foreach ($links as $link) {
+ $this->setCommentFeedLink($link);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the feed title
+ *
+ * @param string $title
+ * @throws Exception\InvalidArgumentException
+ * @return Entry
+ */
+ public function setTitle($title)
+ {
+ if (empty($title) || !is_string($title)) {
+ throw new Exception\InvalidArgumentException('Invalid parameter: parameter must be a non-empty string');
+ }
+ $this->data['title'] = $title;
+
+ return $this;
+ }
+
+ /**
+ * Get an array with feed authors
+ *
+ * @return array
+ */
+ public function getAuthors()
+ {
+ if (!array_key_exists('authors', $this->data)) {
+ return null;
+ }
+ return $this->data['authors'];
+ }
+
+ /**
+ * Get the entry content
+ *
+ * @return string
+ */
+ public function getContent()
+ {
+ if (!array_key_exists('content', $this->data)) {
+ return null;
+ }
+ return $this->data['content'];
+ }
+
+ /**
+ * Get the entry copyright information
+ *
+ * @return string
+ */
+ public function getCopyright()
+ {
+ if (!array_key_exists('copyright', $this->data)) {
+ return null;
+ }
+ return $this->data['copyright'];
+ }
+
+ /**
+ * Get the entry creation date
+ *
+ * @return string
+ */
+ public function getDateCreated()
+ {
+ if (!array_key_exists('dateCreated', $this->data)) {
+ return null;
+ }
+ return $this->data['dateCreated'];
+ }
+
+ /**
+ * Get the entry modification date
+ *
+ * @return string
+ */
+ public function getDateModified()
+ {
+ if (!array_key_exists('dateModified', $this->data)) {
+ return null;
+ }
+ return $this->data['dateModified'];
+ }
+
+ /**
+ * Get the entry description
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ if (!array_key_exists('description', $this->data)) {
+ return null;
+ }
+ return $this->data['description'];
+ }
+
+ /**
+ * Get the entry ID
+ *
+ * @return string
+ */
+ public function getId()
+ {
+ if (!array_key_exists('id', $this->data)) {
+ return null;
+ }
+ return $this->data['id'];
+ }
+
+ /**
+ * Get a link to the HTML source
+ *
+ * @return string|null
+ */
+ public function getLink()
+ {
+ if (!array_key_exists('link', $this->data)) {
+ return null;
+ }
+ return $this->data['link'];
+ }
+
+
+ /**
+ * Get all links
+ *
+ * @return array
+ */
+ public function getLinks()
+ {
+ if (!array_key_exists('links', $this->data)) {
+ return null;
+ }
+ return $this->data['links'];
+ }
+
+ /**
+ * Get the entry title
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ if (!array_key_exists('title', $this->data)) {
+ return null;
+ }
+ return $this->data['title'];
+ }
+
+ /**
+ * Get the number of comments/replies for current entry
+ *
+ * @return int
+ */
+ public function getCommentCount()
+ {
+ if (!array_key_exists('commentCount', $this->data)) {
+ return null;
+ }
+ return $this->data['commentCount'];
+ }
+
+ /**
+ * Returns a URI pointing to the HTML page where comments can be made on this entry
+ *
+ * @return string
+ */
+ public function getCommentLink()
+ {
+ if (!array_key_exists('commentLink', $this->data)) {
+ return null;
+ }
+ return $this->data['commentLink'];
+ }
+
+ /**
+ * Returns an array of URIs pointing to a feed of all comments for this entry
+ * where the array keys indicate the feed type (atom, rss or rdf).
+ *
+ * @return string
+ */
+ public function getCommentFeedLinks()
+ {
+ if (!array_key_exists('commentFeedLinks', $this->data)) {
+ return null;
+ }
+ return $this->data['commentFeedLinks'];
+ }
+
+ /**
+ * Add an entry category
+ *
+ * @param array $category
+ * @throws Exception\InvalidArgumentException
+ * @return Entry
+ */
+ public function addCategory(array $category)
+ {
+ if (!isset($category['term'])) {
+ throw new Exception\InvalidArgumentException('Each category must be an array and '
+ . 'contain at least a "term" element containing the machine '
+ . ' readable category name');
+ }
+ if (isset($category['scheme'])) {
+ if (empty($category['scheme'])
+ || !is_string($category['scheme'])
+ || !Uri::factory($category['scheme'])->isValid()
+ ) {
+ throw new Exception\InvalidArgumentException('The Atom scheme or RSS domain of'
+ . ' a category must be a valid URI');
+ }
+ }
+ if (!isset($this->data['categories'])) {
+ $this->data['categories'] = array();
+ }
+ $this->data['categories'][] = $category;
+
+ return $this;
+ }
+
+ /**
+ * Set an array of entry categories
+ *
+ * @param array $categories
+ * @return Entry
+ */
+ public function addCategories(array $categories)
+ {
+ foreach ($categories as $category) {
+ $this->addCategory($category);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the entry categories
+ *
+ * @return string|null
+ */
+ public function getCategories()
+ {
+ if (!array_key_exists('categories', $this->data)) {
+ return null;
+ }
+ return $this->data['categories'];
+ }
+
+ /**
+ * Adds an enclosure to the entry. The array parameter may contain the
+ * keys 'uri', 'type' and 'length'. Only 'uri' is required for Atom, though the
+ * others must also be provided or RSS rendering (where they are required)
+ * will throw an Exception.
+ *
+ * @param array $enclosure
+ * @throws Exception\InvalidArgumentException
+ * @return Entry
+ */
+ public function setEnclosure(array $enclosure)
+ {
+ if (!isset($enclosure['uri'])) {
+ throw new Exception\InvalidArgumentException('Enclosure "uri" is not set');
+ }
+ if (!Uri::factory($enclosure['uri'])->isValid()) {
+ throw new Exception\InvalidArgumentException('Enclosure "uri" is not a valid URI/IRI');
+ }
+ $this->data['enclosure'] = $enclosure;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve an array of all enclosures to be added to entry.
+ *
+ * @return array
+ */
+ public function getEnclosure()
+ {
+ if (!array_key_exists('enclosure', $this->data)) {
+ return null;
+ }
+ return $this->data['enclosure'];
+ }
+
+ /**
+ * Unset a specific data point
+ *
+ * @param string $name
+ * @return Entry
+ */
+ public function remove($name)
+ {
+ if (isset($this->data[$name])) {
+ unset($this->data[$name]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get registered extensions
+ *
+ * @return array
+ */
+ public function getExtensions()
+ {
+ return $this->extensions;
+ }
+
+ /**
+ * Return an Extension object with the matching name (postfixed with _Entry)
+ *
+ * @param string $name
+ * @return object
+ */
+ public function getExtension($name)
+ {
+ if (array_key_exists($name . '\\Entry', $this->extensions)) {
+ return $this->extensions[$name . '\\Entry'];
+ }
+ return null;
+ }
+
+ /**
+ * Set the current feed type being exported to "rss" or "atom". This allows
+ * other objects to gracefully choose whether to execute or not, depending
+ * on their appropriateness for the current type, e.g. renderers.
+ *
+ * @param string $type
+ * @return Entry
+ */
+ public function setType($type)
+ {
+ $this->type = $type;
+ return $this;
+ }
+
+ /**
+ * Retrieve the current or last feed type exported.
+ *
+ * @return string Value will be "rss" or "atom"
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * Method overloading: call given method on first extension implementing it
+ *
+ * @param string $method
+ * @param array $args
+ * @return mixed
+ * @throws Exception\BadMethodCallException if no extensions implements the method
+ */
+ public function __call($method, $args)
+ {
+ foreach ($this->extensions as $extension) {
+ try {
+ return call_user_func_array(array($extension, $method), $args);
+ } catch (\BadMethodCallException $e) {
+ }
+ }
+ throw new Exception\BadMethodCallException('Method: ' . $method
+ . ' does not exist and could not be located on a registered Extension');
+ }
+
+ /**
+ * Creates a new Zend\Feed\Writer\Source data container for use. This is NOT
+ * added to the current feed automatically, but is necessary to create a
+ * container with some initial values preset based on the current feed data.
+ *
+ * @return Source
+ */
+ public function createSource()
+ {
+ $source = new Source;
+ if ($this->getEncoding()) {
+ $source->setEncoding($this->getEncoding());
+ }
+ $source->setType($this->getType());
+ return $source;
+ }
+
+ /**
+ * Appends a Zend\Feed\Writer\Entry object representing a new entry/item
+ * the feed data container's internal group of entries.
+ *
+ * @param Source $source
+ * @return Entry
+ */
+ public function setSource(Source $source)
+ {
+ $this->data['source'] = $source;
+ return $this;
+ }
+
+ /**
+ * @return Source
+ */
+ public function getSource()
+ {
+ if (isset($this->data['source'])) {
+ return $this->data['source'];
+ }
+ return null;
+ }
+
+ /**
+ * Load extensions from Zend\Feed\Writer\Writer
+ *
+ * @return void
+ */
+ protected function _loadExtensions()
+ {
+ $all = Writer::getExtensions();
+ $manager = Writer::getExtensionManager();
+ $exts = $all['entry'];
+ foreach ($exts as $ext) {
+ $this->extensions[$ext] = $manager->get($ext);
+ $this->extensions[$ext]->setEncoding($this->getEncoding());
+ }
+ }
+}
diff --git a/library/Zend/Feed/Writer/Exception/BadMethodCallException.php b/library/Zend/Feed/Writer/Exception/BadMethodCallException.php
new file mode 100755
index 0000000000..79d1c82c75
--- /dev/null
+++ b/library/Zend/Feed/Writer/Exception/BadMethodCallException.php
@@ -0,0 +1,23 @@
+container = $container;
+ return $this;
+ }
+
+ /**
+ * Set feed encoding
+ *
+ * @param string $enc
+ * @return AbstractRenderer
+ */
+ public function setEncoding($enc)
+ {
+ $this->encoding = $enc;
+ return $this;
+ }
+
+ /**
+ * Get feed encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->encoding;
+ }
+
+ /**
+ * Set DOMDocument and DOMElement on which to operate
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $base
+ * @return AbstractRenderer
+ */
+ public function setDomDocument(DOMDocument $dom, DOMElement $base)
+ {
+ $this->dom = $dom;
+ $this->base = $base;
+ return $this;
+ }
+
+ /**
+ * Get data container being rendered
+ *
+ * @return mixed
+ */
+ public function getDataContainer()
+ {
+ return $this->container;
+ }
+
+ /**
+ * Set feed type
+ *
+ * @param string $type
+ * @return AbstractRenderer
+ */
+ public function setType($type)
+ {
+ $this->type = $type;
+ return $this;
+ }
+
+ /**
+ * Get feedtype
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * Set root element of document
+ *
+ * @param DOMElement $root
+ * @return AbstractRenderer
+ */
+ public function setRootElement(DOMElement $root)
+ {
+ $this->rootElement = $root;
+ return $this;
+ }
+
+ /**
+ * Get root element
+ *
+ * @return DOMElement
+ */
+ public function getRootElement()
+ {
+ return $this->rootElement;
+ }
+
+ /**
+ * Append namespaces to feed
+ *
+ * @return void
+ */
+ abstract protected function _appendNamespaces();
+}
diff --git a/library/Zend/Feed/Writer/Extension/Atom/Renderer/Feed.php b/library/Zend/Feed/Writer/Extension/Atom/Renderer/Feed.php
new file mode 100755
index 0000000000..6b24ec01c8
--- /dev/null
+++ b/library/Zend/Feed/Writer/Extension/Atom/Renderer/Feed.php
@@ -0,0 +1,108 @@
+getType()) == 'atom') {
+ return;
+ }
+ $this->_setFeedLinks($this->dom, $this->base);
+ $this->_setHubs($this->dom, $this->base);
+ if ($this->called) {
+ $this->_appendNamespaces();
+ }
+ }
+
+ /**
+ * Append namespaces to root element of feed
+ *
+ * @return void
+ */
+ protected function _appendNamespaces()
+ {
+ $this->getRootElement()->setAttribute('xmlns:atom',
+ 'http://www.w3.org/2005/Atom');
+ }
+
+ /**
+ * Set feed link elements
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setFeedLinks(DOMDocument $dom, DOMElement $root)
+ {
+ $flinks = $this->getDataContainer()->getFeedLinks();
+ if (!$flinks || empty($flinks)) {
+ return;
+ }
+ foreach ($flinks as $type => $href) {
+ if (strtolower($type) == $this->getType()) { // issue 2605
+ $mime = 'application/' . strtolower($type) . '+xml';
+ $flink = $dom->createElement('atom:link');
+ $root->appendChild($flink);
+ $flink->setAttribute('rel', 'self');
+ $flink->setAttribute('type', $mime);
+ $flink->setAttribute('href', $href);
+ }
+ }
+ $this->called = true;
+ }
+
+ /**
+ * Set PuSH hubs
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setHubs(DOMDocument $dom, DOMElement $root)
+ {
+ $hubs = $this->getDataContainer()->getHubs();
+ if (!$hubs || empty($hubs)) {
+ return;
+ }
+ foreach ($hubs as $hubUrl) {
+ $hub = $dom->createElement('atom:link');
+ $hub->setAttribute('rel', 'hub');
+ $hub->setAttribute('href', $hubUrl);
+ $root->appendChild($hub);
+ }
+ $this->called = true;
+ }
+}
diff --git a/library/Zend/Feed/Writer/Extension/Content/Renderer/Entry.php b/library/Zend/Feed/Writer/Extension/Content/Renderer/Entry.php
new file mode 100755
index 0000000000..b90315169d
--- /dev/null
+++ b/library/Zend/Feed/Writer/Extension/Content/Renderer/Entry.php
@@ -0,0 +1,75 @@
+getType()) == 'atom') {
+ return;
+ }
+ $this->_setContent($this->dom, $this->base);
+ if ($this->called) {
+ $this->_appendNamespaces();
+ }
+ }
+
+ /**
+ * Append namespaces to root element
+ *
+ * @return void
+ */
+ protected function _appendNamespaces()
+ {
+ $this->getRootElement()->setAttribute('xmlns:content',
+ 'http://purl.org/rss/1.0/modules/content/');
+ }
+
+ /**
+ * Set entry content
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setContent(DOMDocument $dom, DOMElement $root)
+ {
+ $content = $this->getDataContainer()->getContent();
+ if (!$content) {
+ return;
+ }
+ $element = $dom->createElement('content:encoded');
+ $root->appendChild($element);
+ $cdata = $dom->createCDATASection($content);
+ $element->appendChild($cdata);
+ $this->called = true;
+ }
+}
diff --git a/library/Zend/Feed/Writer/Extension/DublinCore/Renderer/Entry.php b/library/Zend/Feed/Writer/Extension/DublinCore/Renderer/Entry.php
new file mode 100755
index 0000000000..8f9465c70d
--- /dev/null
+++ b/library/Zend/Feed/Writer/Extension/DublinCore/Renderer/Entry.php
@@ -0,0 +1,79 @@
+getType()) == 'atom') {
+ return;
+ }
+ $this->_setAuthors($this->dom, $this->base);
+ if ($this->called) {
+ $this->_appendNamespaces();
+ }
+ }
+
+ /**
+ * Append namespaces to entry
+ *
+ * @return void
+ */
+ protected function _appendNamespaces()
+ {
+ $this->getRootElement()->setAttribute('xmlns:dc',
+ 'http://purl.org/dc/elements/1.1/');
+ }
+
+ /**
+ * Set entry author elements
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setAuthors(DOMDocument $dom, DOMElement $root)
+ {
+ $authors = $this->getDataContainer()->getAuthors();
+ if (!$authors || empty($authors)) {
+ return;
+ }
+ foreach ($authors as $data) {
+ $author = $this->dom->createElement('dc:creator');
+ if (array_key_exists('name', $data)) {
+ $text = $dom->createTextNode($data['name']);
+ $author->appendChild($text);
+ $root->appendChild($author);
+ }
+ }
+ $this->called = true;
+ }
+}
diff --git a/library/Zend/Feed/Writer/Extension/DublinCore/Renderer/Feed.php b/library/Zend/Feed/Writer/Extension/DublinCore/Renderer/Feed.php
new file mode 100755
index 0000000000..6e68f98e83
--- /dev/null
+++ b/library/Zend/Feed/Writer/Extension/DublinCore/Renderer/Feed.php
@@ -0,0 +1,79 @@
+getType()) == 'atom') {
+ return;
+ }
+ $this->_setAuthors($this->dom, $this->base);
+ if ($this->called) {
+ $this->_appendNamespaces();
+ }
+ }
+
+ /**
+ * Append namespaces to feed element
+ *
+ * @return void
+ */
+ protected function _appendNamespaces()
+ {
+ $this->getRootElement()->setAttribute('xmlns:dc',
+ 'http://purl.org/dc/elements/1.1/');
+ }
+
+ /**
+ * Set feed authors
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setAuthors(DOMDocument $dom, DOMElement $root)
+ {
+ $authors = $this->getDataContainer()->getAuthors();
+ if (!$authors || empty($authors)) {
+ return;
+ }
+ foreach ($authors as $data) {
+ $author = $this->dom->createElement('dc:creator');
+ if (array_key_exists('name', $data)) {
+ $text = $dom->createTextNode($data['name']);
+ $author->appendChild($text);
+ $root->appendChild($author);
+ }
+ }
+ $this->called = true;
+ }
+}
diff --git a/library/Zend/Feed/Writer/Extension/ITunes/Entry.php b/library/Zend/Feed/Writer/Extension/ITunes/Entry.php
new file mode 100755
index 0000000000..1b7b64aa5b
--- /dev/null
+++ b/library/Zend/Feed/Writer/Extension/ITunes/Entry.php
@@ -0,0 +1,246 @@
+stringWrapper = StringUtils::getWrapper($this->encoding);
+ }
+
+ /**
+ * Set feed encoding
+ *
+ * @param string $enc
+ * @return Entry
+ */
+ public function setEncoding($enc)
+ {
+ $this->stringWrapper = StringUtils::getWrapper($enc);
+ $this->encoding = $enc;
+ return $this;
+ }
+
+ /**
+ * Get feed encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->encoding;
+ }
+
+ /**
+ * Set a block value of "yes" or "no". You may also set an empty string.
+ *
+ * @param string
+ * @return Entry
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ public function setItunesBlock($value)
+ {
+ if (!ctype_alpha($value) && strlen($value) > 0) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: "block" may only'
+ . ' contain alphabetic characters');
+ }
+
+ if ($this->stringWrapper->strlen($value) > 255) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: "block" may only'
+ . ' contain a maximum of 255 characters');
+ }
+ $this->data['block'] = $value;
+ }
+
+ /**
+ * Add authors to itunes entry
+ *
+ * @param array $values
+ * @return Entry
+ */
+ public function addItunesAuthors(array $values)
+ {
+ foreach ($values as $value) {
+ $this->addItunesAuthor($value);
+ }
+ return $this;
+ }
+
+ /**
+ * Add author to itunes entry
+ *
+ * @param string $value
+ * @return Entry
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ public function addItunesAuthor($value)
+ {
+ if ($this->stringWrapper->strlen($value) > 255) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: any "author" may only'
+ . ' contain a maximum of 255 characters each');
+ }
+ if (!isset($this->data['authors'])) {
+ $this->data['authors'] = array();
+ }
+ $this->data['authors'][] = $value;
+ return $this;
+ }
+
+ /**
+ * Set duration
+ *
+ * @param int $value
+ * @return Entry
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ public function setItunesDuration($value)
+ {
+ $value = (string) $value;
+ if (!ctype_digit($value)
+ && !preg_match("/^\d+:[0-5]{1}[0-9]{1}$/", $value)
+ && !preg_match("/^\d+:[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}$/", $value)
+ ) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: "duration" may only'
+ . ' be of a specified [[HH:]MM:]SS format');
+ }
+ $this->data['duration'] = $value;
+ return $this;
+ }
+
+ /**
+ * Set "explicit" flag
+ *
+ * @param bool $value
+ * @return Entry
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ public function setItunesExplicit($value)
+ {
+ if (!in_array($value, array('yes', 'no', 'clean'))) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: "explicit" may only'
+ . ' be one of "yes", "no" or "clean"');
+ }
+ $this->data['explicit'] = $value;
+ return $this;
+ }
+
+ /**
+ * Set keywords
+ *
+ * @param array $value
+ * @return Entry
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ public function setItunesKeywords(array $value)
+ {
+ if (count($value) > 12) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: "keywords" may only'
+ . ' contain a maximum of 12 terms');
+ }
+
+ $concat = implode(',', $value);
+ if ($this->stringWrapper->strlen($concat) > 255) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: "keywords" may only'
+ . ' have a concatenated length of 255 chars where terms are delimited'
+ . ' by a comma');
+ }
+ $this->data['keywords'] = $value;
+ return $this;
+ }
+
+ /**
+ * Set subtitle
+ *
+ * @param string $value
+ * @return Entry
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ public function setItunesSubtitle($value)
+ {
+ if ($this->stringWrapper->strlen($value) > 255) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: "subtitle" may only'
+ . ' contain a maximum of 255 characters');
+ }
+ $this->data['subtitle'] = $value;
+ return $this;
+ }
+
+ /**
+ * Set summary
+ *
+ * @param string $value
+ * @return Entry
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ public function setItunesSummary($value)
+ {
+ if ($this->stringWrapper->strlen($value) > 4000) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: "summary" may only'
+ . ' contain a maximum of 4000 characters');
+ }
+ $this->data['summary'] = $value;
+ return $this;
+ }
+
+ /**
+ * Overloading to itunes specific setters
+ *
+ * @param string $method
+ * @param array $params
+ * @throws Writer\Exception\BadMethodCallException
+ * @return mixed
+ */
+ public function __call($method, array $params)
+ {
+ $point = lcfirst(substr($method, 9));
+ if (!method_exists($this, 'setItunes' . ucfirst($point))
+ && !method_exists($this, 'addItunes' . ucfirst($point))
+ ) {
+ throw new Writer\Exception\BadMethodCallException(
+ 'invalid method: ' . $method
+ );
+ }
+ if (!array_key_exists($point, $this->data)
+ || empty($this->data[$point])
+ ) {
+ return null;
+ }
+ return $this->data[$point];
+ }
+}
diff --git a/library/Zend/Feed/Writer/Extension/ITunes/Feed.php b/library/Zend/Feed/Writer/Extension/ITunes/Feed.php
new file mode 100755
index 0000000000..22c54db6e6
--- /dev/null
+++ b/library/Zend/Feed/Writer/Extension/ITunes/Feed.php
@@ -0,0 +1,362 @@
+stringWrapper = StringUtils::getWrapper($this->encoding);
+ }
+
+ /**
+ * Set feed encoding
+ *
+ * @param string $enc
+ * @return Feed
+ */
+ public function setEncoding($enc)
+ {
+ $this->stringWrapper = StringUtils::getWrapper($enc);
+ $this->encoding = $enc;
+ return $this;
+ }
+
+ /**
+ * Get feed encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->encoding;
+ }
+
+ /**
+ * Set a block value of "yes" or "no". You may also set an empty string.
+ *
+ * @param string
+ * @return Feed
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ public function setItunesBlock($value)
+ {
+ if (!ctype_alpha($value) && strlen($value) > 0) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: "block" may only'
+ . ' contain alphabetic characters');
+ }
+ if ($this->stringWrapper->strlen($value) > 255) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: "block" may only'
+ . ' contain a maximum of 255 characters');
+ }
+ $this->data['block'] = $value;
+ return $this;
+ }
+
+ /**
+ * Add feed authors
+ *
+ * @param array $values
+ * @return Feed
+ */
+ public function addItunesAuthors(array $values)
+ {
+ foreach ($values as $value) {
+ $this->addItunesAuthor($value);
+ }
+ return $this;
+ }
+
+ /**
+ * Add feed author
+ *
+ * @param string $value
+ * @return Feed
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ public function addItunesAuthor($value)
+ {
+ if ($this->stringWrapper->strlen($value) > 255) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: any "author" may only'
+ . ' contain a maximum of 255 characters each');
+ }
+ if (!isset($this->data['authors'])) {
+ $this->data['authors'] = array();
+ }
+ $this->data['authors'][] = $value;
+ return $this;
+ }
+
+ /**
+ * Set feed categories
+ *
+ * @param array $values
+ * @return Feed
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ public function setItunesCategories(array $values)
+ {
+ if (!isset($this->data['categories'])) {
+ $this->data['categories'] = array();
+ }
+ foreach ($values as $key => $value) {
+ if (!is_array($value)) {
+ if ($this->stringWrapper->strlen($value) > 255) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: any "category" may only'
+ . ' contain a maximum of 255 characters each');
+ }
+ $this->data['categories'][] = $value;
+ } else {
+ if ($this->stringWrapper->strlen($key) > 255) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: any "category" may only'
+ . ' contain a maximum of 255 characters each');
+ }
+ $this->data['categories'][$key] = array();
+ foreach ($value as $val) {
+ if ($this->stringWrapper->strlen($val) > 255) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: any "category" may only'
+ . ' contain a maximum of 255 characters each');
+ }
+ $this->data['categories'][$key][] = $val;
+ }
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Set feed image (icon)
+ *
+ * @param string $value
+ * @return Feed
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ public function setItunesImage($value)
+ {
+ if (!Uri::factory($value)->isValid()) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: "image" may only'
+ . ' be a valid URI/IRI');
+ }
+ if (!in_array(substr($value, -3), array('jpg', 'png'))) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: "image" may only'
+ . ' use file extension "jpg" or "png" which must be the last three'
+ . ' characters of the URI (i.e. no query string or fragment)');
+ }
+ $this->data['image'] = $value;
+ return $this;
+ }
+
+ /**
+ * Set feed cumulative duration
+ *
+ * @param string $value
+ * @return Feed
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ public function setItunesDuration($value)
+ {
+ $value = (string) $value;
+ if (!ctype_digit($value)
+ && !preg_match("/^\d+:[0-5]{1}[0-9]{1}$/", $value)
+ && !preg_match("/^\d+:[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}$/", $value)
+ ) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: "duration" may only'
+ . ' be of a specified [[HH:]MM:]SS format');
+ }
+ $this->data['duration'] = $value;
+ return $this;
+ }
+
+ /**
+ * Set "explicit" flag
+ *
+ * @param bool $value
+ * @return Feed
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ public function setItunesExplicit($value)
+ {
+ if (!in_array($value, array('yes', 'no', 'clean'))) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: "explicit" may only'
+ . ' be one of "yes", "no" or "clean"');
+ }
+ $this->data['explicit'] = $value;
+ return $this;
+ }
+
+ /**
+ * Set feed keywords
+ *
+ * @param array $value
+ * @return Feed
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ public function setItunesKeywords(array $value)
+ {
+ if (count($value) > 12) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: "keywords" may only'
+ . ' contain a maximum of 12 terms');
+ }
+ $concat = implode(',', $value);
+ if ($this->stringWrapper->strlen($concat) > 255) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: "keywords" may only'
+ . ' have a concatenated length of 255 chars where terms are delimited'
+ . ' by a comma');
+ }
+ $this->data['keywords'] = $value;
+ return $this;
+ }
+
+ /**
+ * Set new feed URL
+ *
+ * @param string $value
+ * @return Feed
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ public function setItunesNewFeedUrl($value)
+ {
+ if (!Uri::factory($value)->isValid()) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: "newFeedUrl" may only'
+ . ' be a valid URI/IRI');
+ }
+ $this->data['newFeedUrl'] = $value;
+ return $this;
+ }
+
+ /**
+ * Add feed owners
+ *
+ * @param array $values
+ * @return Feed
+ */
+ public function addItunesOwners(array $values)
+ {
+ foreach ($values as $value) {
+ $this->addItunesOwner($value);
+ }
+ return $this;
+ }
+
+ /**
+ * Add feed owner
+ *
+ * @param array $value
+ * @return Feed
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ public function addItunesOwner(array $value)
+ {
+ if (!isset($value['name']) || !isset($value['email'])) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: any "owner" must'
+ . ' be an array containing keys "name" and "email"');
+ }
+ if ($this->stringWrapper->strlen($value['name']) > 255
+ || $this->stringWrapper->strlen($value['email']) > 255
+ ) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: any "owner" may only'
+ . ' contain a maximum of 255 characters each for "name" and "email"');
+ }
+ if (!isset($this->data['owners'])) {
+ $this->data['owners'] = array();
+ }
+ $this->data['owners'][] = $value;
+ return $this;
+ }
+
+ /**
+ * Set feed subtitle
+ *
+ * @param string $value
+ * @return Feed
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ public function setItunesSubtitle($value)
+ {
+ if ($this->stringWrapper->strlen($value) > 255) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: "subtitle" may only'
+ . ' contain a maximum of 255 characters');
+ }
+ $this->data['subtitle'] = $value;
+ return $this;
+ }
+
+ /**
+ * Set feed summary
+ *
+ * @param string $value
+ * @return Feed
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ public function setItunesSummary($value)
+ {
+ if ($this->stringWrapper->strlen($value) > 4000) {
+ throw new Writer\Exception\InvalidArgumentException('invalid parameter: "summary" may only'
+ . ' contain a maximum of 4000 characters');
+ }
+ $this->data['summary'] = $value;
+ return $this;
+ }
+
+ /**
+ * Overloading: proxy to internal setters
+ *
+ * @param string $method
+ * @param array $params
+ * @return mixed
+ * @throws Writer\Exception\BadMethodCallException
+ */
+ public function __call($method, array $params)
+ {
+ $point = lcfirst(substr($method, 9));
+ if (!method_exists($this, 'setItunes' . ucfirst($point))
+ && !method_exists($this, 'addItunes' . ucfirst($point))
+ ) {
+ throw new Writer\Exception\BadMethodCallException(
+ 'invalid method: ' . $method
+ );
+ }
+ if (!array_key_exists($point, $this->data) || empty($this->data[$point])) {
+ return null;
+ }
+ return $this->data[$point];
+ }
+}
diff --git a/library/Zend/Feed/Writer/Extension/ITunes/Renderer/Entry.php b/library/Zend/Feed/Writer/Extension/ITunes/Renderer/Entry.php
new file mode 100755
index 0000000000..bc57d1daa2
--- /dev/null
+++ b/library/Zend/Feed/Writer/Extension/ITunes/Renderer/Entry.php
@@ -0,0 +1,200 @@
+_setAuthors($this->dom, $this->base);
+ $this->_setBlock($this->dom, $this->base);
+ $this->_setDuration($this->dom, $this->base);
+ $this->_setExplicit($this->dom, $this->base);
+ $this->_setKeywords($this->dom, $this->base);
+ $this->_setSubtitle($this->dom, $this->base);
+ $this->_setSummary($this->dom, $this->base);
+ if ($this->called) {
+ $this->_appendNamespaces();
+ }
+ }
+
+ /**
+ * Append namespaces to entry root
+ *
+ * @return void
+ */
+ protected function _appendNamespaces()
+ {
+ $this->getRootElement()->setAttribute('xmlns:itunes',
+ 'http://www.itunes.com/dtds/podcast-1.0.dtd');
+ }
+
+ /**
+ * Set entry authors
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setAuthors(DOMDocument $dom, DOMElement $root)
+ {
+ $authors = $this->getDataContainer()->getItunesAuthors();
+ if (!$authors || empty($authors)) {
+ return;
+ }
+ foreach ($authors as $author) {
+ $el = $dom->createElement('itunes:author');
+ $text = $dom->createTextNode($author);
+ $el->appendChild($text);
+ $root->appendChild($el);
+ $this->called = true;
+ }
+ }
+
+ /**
+ * Set itunes block
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setBlock(DOMDocument $dom, DOMElement $root)
+ {
+ $block = $this->getDataContainer()->getItunesBlock();
+ if ($block === null) {
+ return;
+ }
+ $el = $dom->createElement('itunes:block');
+ $text = $dom->createTextNode($block);
+ $el->appendChild($text);
+ $root->appendChild($el);
+ $this->called = true;
+ }
+
+ /**
+ * Set entry duration
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setDuration(DOMDocument $dom, DOMElement $root)
+ {
+ $duration = $this->getDataContainer()->getItunesDuration();
+ if (!$duration) {
+ return;
+ }
+ $el = $dom->createElement('itunes:duration');
+ $text = $dom->createTextNode($duration);
+ $el->appendChild($text);
+ $root->appendChild($el);
+ $this->called = true;
+ }
+
+ /**
+ * Set explicit flag
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setExplicit(DOMDocument $dom, DOMElement $root)
+ {
+ $explicit = $this->getDataContainer()->getItunesExplicit();
+ if ($explicit === null) {
+ return;
+ }
+ $el = $dom->createElement('itunes:explicit');
+ $text = $dom->createTextNode($explicit);
+ $el->appendChild($text);
+ $root->appendChild($el);
+ $this->called = true;
+ }
+
+ /**
+ * Set entry keywords
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setKeywords(DOMDocument $dom, DOMElement $root)
+ {
+ $keywords = $this->getDataContainer()->getItunesKeywords();
+ if (!$keywords || empty($keywords)) {
+ return;
+ }
+ $el = $dom->createElement('itunes:keywords');
+ $text = $dom->createTextNode(implode(',', $keywords));
+ $el->appendChild($text);
+ $root->appendChild($el);
+ $this->called = true;
+ }
+
+ /**
+ * Set entry subtitle
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setSubtitle(DOMDocument $dom, DOMElement $root)
+ {
+ $subtitle = $this->getDataContainer()->getItunesSubtitle();
+ if (!$subtitle) {
+ return;
+ }
+ $el = $dom->createElement('itunes:subtitle');
+ $text = $dom->createTextNode($subtitle);
+ $el->appendChild($text);
+ $root->appendChild($el);
+ $this->called = true;
+ }
+
+ /**
+ * Set entry summary
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setSummary(DOMDocument $dom, DOMElement $root)
+ {
+ $summary = $this->getDataContainer()->getItunesSummary();
+ if (!$summary) {
+ return;
+ }
+ $el = $dom->createElement('itunes:summary');
+ $text = $dom->createTextNode($summary);
+ $el->appendChild($text);
+ $root->appendChild($el);
+ $this->called = true;
+ }
+}
diff --git a/library/Zend/Feed/Writer/Extension/ITunes/Renderer/Feed.php b/library/Zend/Feed/Writer/Extension/ITunes/Renderer/Feed.php
new file mode 100755
index 0000000000..e6113a22f6
--- /dev/null
+++ b/library/Zend/Feed/Writer/Extension/ITunes/Renderer/Feed.php
@@ -0,0 +1,303 @@
+_setAuthors($this->dom, $this->base);
+ $this->_setBlock($this->dom, $this->base);
+ $this->_setCategories($this->dom, $this->base);
+ $this->_setImage($this->dom, $this->base);
+ $this->_setDuration($this->dom, $this->base);
+ $this->_setExplicit($this->dom, $this->base);
+ $this->_setKeywords($this->dom, $this->base);
+ $this->_setNewFeedUrl($this->dom, $this->base);
+ $this->_setOwners($this->dom, $this->base);
+ $this->_setSubtitle($this->dom, $this->base);
+ $this->_setSummary($this->dom, $this->base);
+ if ($this->called) {
+ $this->_appendNamespaces();
+ }
+ }
+
+ /**
+ * Append feed namespaces
+ *
+ * @return void
+ */
+ protected function _appendNamespaces()
+ {
+ $this->getRootElement()->setAttribute('xmlns:itunes',
+ 'http://www.itunes.com/dtds/podcast-1.0.dtd');
+ }
+
+ /**
+ * Set feed authors
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setAuthors(DOMDocument $dom, DOMElement $root)
+ {
+ $authors = $this->getDataContainer()->getItunesAuthors();
+ if (!$authors || empty($authors)) {
+ return;
+ }
+ foreach ($authors as $author) {
+ $el = $dom->createElement('itunes:author');
+ $text = $dom->createTextNode($author);
+ $el->appendChild($text);
+ $root->appendChild($el);
+ }
+ $this->called = true;
+ }
+
+ /**
+ * Set feed itunes block
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setBlock(DOMDocument $dom, DOMElement $root)
+ {
+ $block = $this->getDataContainer()->getItunesBlock();
+ if ($block === null) {
+ return;
+ }
+ $el = $dom->createElement('itunes:block');
+ $text = $dom->createTextNode($block);
+ $el->appendChild($text);
+ $root->appendChild($el);
+ $this->called = true;
+ }
+
+ /**
+ * Set feed categories
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setCategories(DOMDocument $dom, DOMElement $root)
+ {
+ $cats = $this->getDataContainer()->getItunesCategories();
+ if (!$cats || empty($cats)) {
+ return;
+ }
+ foreach ($cats as $key => $cat) {
+ if (!is_array($cat)) {
+ $el = $dom->createElement('itunes:category');
+ $el->setAttribute('text', $cat);
+ $root->appendChild($el);
+ } else {
+ $el = $dom->createElement('itunes:category');
+ $el->setAttribute('text', $key);
+ $root->appendChild($el);
+ foreach ($cat as $subcat) {
+ $el2 = $dom->createElement('itunes:category');
+ $el2->setAttribute('text', $subcat);
+ $el->appendChild($el2);
+ }
+ }
+ }
+ $this->called = true;
+ }
+
+ /**
+ * Set feed image (icon)
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setImage(DOMDocument $dom, DOMElement $root)
+ {
+ $image = $this->getDataContainer()->getItunesImage();
+ if (!$image) {
+ return;
+ }
+ $el = $dom->createElement('itunes:image');
+ $el->setAttribute('href', $image);
+ $root->appendChild($el);
+ $this->called = true;
+ }
+
+ /**
+ * Set feed cumulative duration
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setDuration(DOMDocument $dom, DOMElement $root)
+ {
+ $duration = $this->getDataContainer()->getItunesDuration();
+ if (!$duration) {
+ return;
+ }
+ $el = $dom->createElement('itunes:duration');
+ $text = $dom->createTextNode($duration);
+ $el->appendChild($text);
+ $root->appendChild($el);
+ $this->called = true;
+ }
+
+ /**
+ * Set explicit flag
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setExplicit(DOMDocument $dom, DOMElement $root)
+ {
+ $explicit = $this->getDataContainer()->getItunesExplicit();
+ if ($explicit === null) {
+ return;
+ }
+ $el = $dom->createElement('itunes:explicit');
+ $text = $dom->createTextNode($explicit);
+ $el->appendChild($text);
+ $root->appendChild($el);
+ $this->called = true;
+ }
+
+ /**
+ * Set feed keywords
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setKeywords(DOMDocument $dom, DOMElement $root)
+ {
+ $keywords = $this->getDataContainer()->getItunesKeywords();
+ if (!$keywords || empty($keywords)) {
+ return;
+ }
+ $el = $dom->createElement('itunes:keywords');
+ $text = $dom->createTextNode(implode(',', $keywords));
+ $el->appendChild($text);
+ $root->appendChild($el);
+ $this->called = true;
+ }
+
+ /**
+ * Set feed's new URL
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setNewFeedUrl(DOMDocument $dom, DOMElement $root)
+ {
+ $url = $this->getDataContainer()->getItunesNewFeedUrl();
+ if (!$url) {
+ return;
+ }
+ $el = $dom->createElement('itunes:new-feed-url');
+ $text = $dom->createTextNode($url);
+ $el->appendChild($text);
+ $root->appendChild($el);
+ $this->called = true;
+ }
+
+ /**
+ * Set feed owners
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setOwners(DOMDocument $dom, DOMElement $root)
+ {
+ $owners = $this->getDataContainer()->getItunesOwners();
+ if (!$owners || empty($owners)) {
+ return;
+ }
+ foreach ($owners as $owner) {
+ $el = $dom->createElement('itunes:owner');
+ $name = $dom->createElement('itunes:name');
+ $text = $dom->createTextNode($owner['name']);
+ $name->appendChild($text);
+ $email = $dom->createElement('itunes:email');
+ $text = $dom->createTextNode($owner['email']);
+ $email->appendChild($text);
+ $root->appendChild($el);
+ $el->appendChild($name);
+ $el->appendChild($email);
+ }
+ $this->called = true;
+ }
+
+ /**
+ * Set feed subtitle
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setSubtitle(DOMDocument $dom, DOMElement $root)
+ {
+ $subtitle = $this->getDataContainer()->getItunesSubtitle();
+ if (!$subtitle) {
+ return;
+ }
+ $el = $dom->createElement('itunes:subtitle');
+ $text = $dom->createTextNode($subtitle);
+ $el->appendChild($text);
+ $root->appendChild($el);
+ $this->called = true;
+ }
+
+ /**
+ * Set feed summary
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setSummary(DOMDocument $dom, DOMElement $root)
+ {
+ $summary = $this->getDataContainer()->getItunesSummary();
+ if (!$summary) {
+ return;
+ }
+ $el = $dom->createElement('itunes:summary');
+ $text = $dom->createTextNode($summary);
+ $el->appendChild($text);
+ $root->appendChild($el);
+ $this->called = true;
+ }
+}
diff --git a/library/Zend/Feed/Writer/Extension/RendererInterface.php b/library/Zend/Feed/Writer/Extension/RendererInterface.php
new file mode 100755
index 0000000000..e72346c205
--- /dev/null
+++ b/library/Zend/Feed/Writer/Extension/RendererInterface.php
@@ -0,0 +1,49 @@
+getType()) == 'atom') {
+ return; // RSS 2.0 only
+ }
+ $this->_setCommentCount($this->dom, $this->base);
+ if ($this->called) {
+ $this->_appendNamespaces();
+ }
+ }
+
+ /**
+ * Append entry namespaces
+ *
+ * @return void
+ */
+ protected function _appendNamespaces()
+ {
+ $this->getRootElement()->setAttribute('xmlns:slash',
+ 'http://purl.org/rss/1.0/modules/slash/');
+ }
+
+ /**
+ * Set entry comment count
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setCommentCount(DOMDocument $dom, DOMElement $root)
+ {
+ $count = $this->getDataContainer()->getCommentCount();
+ if (!$count) {
+ $count = 0;
+ }
+ $tcount = $this->dom->createElement('slash:comments');
+ $tcount->nodeValue = $count;
+ $root->appendChild($tcount);
+ $this->called = true;
+ }
+}
diff --git a/library/Zend/Feed/Writer/Extension/Threading/Renderer/Entry.php b/library/Zend/Feed/Writer/Extension/Threading/Renderer/Entry.php
new file mode 100755
index 0000000000..a0ea739d84
--- /dev/null
+++ b/library/Zend/Feed/Writer/Extension/Threading/Renderer/Entry.php
@@ -0,0 +1,128 @@
+getType()) == 'rss') {
+ return; // Atom 1.0 only
+ }
+ $this->_setCommentLink($this->dom, $this->base);
+ $this->_setCommentFeedLinks($this->dom, $this->base);
+ $this->_setCommentCount($this->dom, $this->base);
+ if ($this->called) {
+ $this->_appendNamespaces();
+ }
+ }
+
+ /**
+ * Append entry namespaces
+ *
+ * @return void
+ */
+ protected function _appendNamespaces()
+ {
+ $this->getRootElement()->setAttribute('xmlns:thr',
+ 'http://purl.org/syndication/thread/1.0');
+ }
+
+ /**
+ * Set comment link
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setCommentLink(DOMDocument $dom, DOMElement $root)
+ {
+ $link = $this->getDataContainer()->getCommentLink();
+ if (!$link) {
+ return;
+ }
+ $clink = $this->dom->createElement('link');
+ $clink->setAttribute('rel', 'replies');
+ $clink->setAttribute('type', 'text/html');
+ $clink->setAttribute('href', $link);
+ $count = $this->getDataContainer()->getCommentCount();
+ if ($count !== null) {
+ $clink->setAttribute('thr:count', $count);
+ }
+ $root->appendChild($clink);
+ $this->called = true;
+ }
+
+ /**
+ * Set comment feed links
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setCommentFeedLinks(DOMDocument $dom, DOMElement $root)
+ {
+ $links = $this->getDataContainer()->getCommentFeedLinks();
+ if (!$links || empty($links)) {
+ return;
+ }
+ foreach ($links as $link) {
+ $flink = $this->dom->createElement('link');
+ $flink->setAttribute('rel', 'replies');
+ $flink->setAttribute('type', 'application/' . $link['type'] . '+xml');
+ $flink->setAttribute('href', $link['uri']);
+ $count = $this->getDataContainer()->getCommentCount();
+ if ($count !== null) {
+ $flink->setAttribute('thr:count', $count);
+ }
+ $root->appendChild($flink);
+ $this->called = true;
+ }
+ }
+
+ /**
+ * Set entry comment count
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setCommentCount(DOMDocument $dom, DOMElement $root)
+ {
+ $count = $this->getDataContainer()->getCommentCount();
+ if ($count === null) {
+ return;
+ }
+ $tcount = $this->dom->createElement('thr:total');
+ $tcount->nodeValue = $count;
+ $root->appendChild($tcount);
+ $this->called = true;
+ }
+}
diff --git a/library/Zend/Feed/Writer/Extension/WellFormedWeb/Renderer/Entry.php b/library/Zend/Feed/Writer/Extension/WellFormedWeb/Renderer/Entry.php
new file mode 100755
index 0000000000..d661360a14
--- /dev/null
+++ b/library/Zend/Feed/Writer/Extension/WellFormedWeb/Renderer/Entry.php
@@ -0,0 +1,79 @@
+getType()) == 'atom') {
+ return; // RSS 2.0 only
+ }
+ $this->_setCommentFeedLinks($this->dom, $this->base);
+ if ($this->called) {
+ $this->_appendNamespaces();
+ }
+ }
+
+ /**
+ * Append entry namespaces
+ *
+ * @return void
+ */
+ protected function _appendNamespaces()
+ {
+ $this->getRootElement()->setAttribute('xmlns:wfw',
+ 'http://wellformedweb.org/CommentAPI/');
+ }
+
+ /**
+ * Set entry comment feed links
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setCommentFeedLinks(DOMDocument $dom, DOMElement $root)
+ {
+ $links = $this->getDataContainer()->getCommentFeedLinks();
+ if (!$links || empty($links)) {
+ return;
+ }
+ foreach ($links as $link) {
+ if ($link['type'] == 'rss') {
+ $flink = $this->dom->createElement('wfw:commentRss');
+ $text = $dom->createTextNode($link['uri']);
+ $flink->appendChild($text);
+ $root->appendChild($flink);
+ }
+ }
+ $this->called = true;
+ }
+}
diff --git a/library/Zend/Feed/Writer/ExtensionManager.php b/library/Zend/Feed/Writer/ExtensionManager.php
new file mode 100755
index 0000000000..77d49a0dc3
--- /dev/null
+++ b/library/Zend/Feed/Writer/ExtensionManager.php
@@ -0,0 +1,80 @@
+pluginManager = $pluginManager;
+ }
+
+ /**
+ * Method overloading
+ *
+ * Proxy to composed ExtensionPluginManager instance.
+ *
+ * @param string $method
+ * @param array $args
+ * @return mixed
+ * @throws Exception\BadMethodCallException
+ */
+ public function __call($method, $args)
+ {
+ if (!method_exists($this->pluginManager, $method)) {
+ throw new Exception\BadMethodCallException(sprintf(
+ 'Method by name of %s does not exist in %s',
+ $method,
+ __CLASS__
+ ));
+ }
+ return call_user_func_array(array($this->pluginManager, $method), $args);
+ }
+
+ /**
+ * Get the named extension
+ *
+ * @param string $name
+ * @return Extension\AbstractRenderer
+ */
+ public function get($name)
+ {
+ return $this->pluginManager->get($name);
+ }
+
+ /**
+ * Do we have the named extension?
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function has($name)
+ {
+ return $this->pluginManager->has($name);
+ }
+}
diff --git a/library/Zend/Feed/Writer/ExtensionManagerInterface.php b/library/Zend/Feed/Writer/ExtensionManagerInterface.php
new file mode 100755
index 0000000000..0f7e023fec
--- /dev/null
+++ b/library/Zend/Feed/Writer/ExtensionManagerInterface.php
@@ -0,0 +1,29 @@
+ 'Zend\Feed\Writer\Extension\Atom\Renderer\Feed',
+ 'contentrendererentry' => 'Zend\Feed\Writer\Extension\Content\Renderer\Entry',
+ 'dublincorerendererentry' => 'Zend\Feed\Writer\Extension\DublinCore\Renderer\Entry',
+ 'dublincorerendererfeed' => 'Zend\Feed\Writer\Extension\DublinCore\Renderer\Feed',
+ 'itunesentry' => 'Zend\Feed\Writer\Extension\ITunes\Entry',
+ 'itunesfeed' => 'Zend\Feed\Writer\Extension\ITunes\Feed',
+ 'itunesrendererentry' => 'Zend\Feed\Writer\Extension\ITunes\Renderer\Entry',
+ 'itunesrendererfeed' => 'Zend\Feed\Writer\Extension\ITunes\Renderer\Feed',
+ 'slashrendererentry' => 'Zend\Feed\Writer\Extension\Slash\Renderer\Entry',
+ 'threadingrendererentry' => 'Zend\Feed\Writer\Extension\Threading\Renderer\Entry',
+ 'wellformedwebrendererentry' => 'Zend\Feed\Writer\Extension\WellFormedWeb\Renderer\Entry',
+ );
+
+ /**
+ * Do not share instances
+ *
+ * @var bool
+ */
+ protected $shareByDefault = false;
+
+ /**
+ * Validate the plugin
+ *
+ * Checks that the extension loaded is of a valid type.
+ *
+ * @param mixed $plugin
+ * @return void
+ * @throws Exception\InvalidArgumentException if invalid
+ */
+ public function validatePlugin($plugin)
+ {
+ if ($plugin instanceof Extension\AbstractRenderer) {
+ // we're okay
+ return;
+ }
+
+ if ('Feed' == substr(get_class($plugin), -4)) {
+ // we're okay
+ return;
+ }
+
+ if ('Entry' == substr(get_class($plugin), -5)) {
+ // we're okay
+ return;
+ }
+
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Plugin of type %s is invalid; must implement %s\Extension\RendererInterface '
+ . 'or the classname must end in "Feed" or "Entry"',
+ (is_object($plugin) ? get_class($plugin) : gettype($plugin)),
+ __NAMESPACE__
+ ));
+ }
+}
diff --git a/library/Zend/Feed/Writer/Feed.php b/library/Zend/Feed/Writer/Feed.php
new file mode 100755
index 0000000000..36b2b3ba9b
--- /dev/null
+++ b/library/Zend/Feed/Writer/Feed.php
@@ -0,0 +1,239 @@
+getEncoding()) {
+ $entry->setEncoding($this->getEncoding());
+ }
+ $entry->setType($this->getType());
+ return $entry;
+ }
+
+ /**
+ * Appends a Zend\Feed\Writer\Deleted object representing a new entry tombstone
+ * to the feed data container's internal group of entries.
+ *
+ * @param Deleted $deleted
+ * @return void
+ */
+ public function addTombstone(Deleted $deleted)
+ {
+ $this->entries[] = $deleted;
+ }
+
+ /**
+ * Creates a new Zend\Feed\Writer\Deleted data container for use. This is NOT
+ * added to the current feed automatically, but is necessary to create a
+ * container with some initial values preset based on the current feed data.
+ *
+ * @return Deleted
+ */
+ public function createTombstone()
+ {
+ $deleted = new Deleted;
+ if ($this->getEncoding()) {
+ $deleted->setEncoding($this->getEncoding());
+ }
+ $deleted->setType($this->getType());
+ return $deleted;
+ }
+
+ /**
+ * Appends a Zend\Feed\Writer\Entry object representing a new entry/item
+ * the feed data container's internal group of entries.
+ *
+ * @param Entry $entry
+ * @return Feed
+ */
+ public function addEntry(Entry $entry)
+ {
+ $this->entries[] = $entry;
+ return $this;
+ }
+
+ /**
+ * Removes a specific indexed entry from the internal queue. Entries must be
+ * added to a feed container in order to be indexed.
+ *
+ * @param int $index
+ * @throws Exception\InvalidArgumentException
+ * @return Feed
+ */
+ public function removeEntry($index)
+ {
+ if (!isset($this->entries[$index])) {
+ throw new Exception\InvalidArgumentException('Undefined index: ' . $index . '. Entry does not exist.');
+ }
+ unset($this->entries[$index]);
+
+ return $this;
+ }
+
+ /**
+ * Retrieve a specific indexed entry from the internal queue. Entries must be
+ * added to a feed container in order to be indexed.
+ *
+ * @param int $index
+ * @throws Exception\InvalidArgumentException
+ */
+ public function getEntry($index = 0)
+ {
+ if (isset($this->entries[$index])) {
+ return $this->entries[$index];
+ }
+ throw new Exception\InvalidArgumentException('Undefined index: ' . $index . '. Entry does not exist.');
+ }
+
+ /**
+ * Orders all indexed entries by date, thus offering date ordered readable
+ * content where a parser (or Homo Sapien) ignores the generic rule that
+ * XML element order is irrelevant and has no intrinsic meaning.
+ *
+ * Using this method will alter the original indexation.
+ *
+ * @return Feed
+ */
+ public function orderByDate()
+ {
+ /**
+ * Could do with some improvement for performance perhaps
+ */
+ $timestamp = time();
+ $entries = array();
+ foreach ($this->entries as $entry) {
+ if ($entry->getDateModified()) {
+ $timestamp = (int) $entry->getDateModified()->getTimestamp();
+ } elseif ($entry->getDateCreated()) {
+ $timestamp = (int) $entry->getDateCreated()->getTimestamp();
+ }
+ $entries[$timestamp] = $entry;
+ }
+ krsort($entries, SORT_NUMERIC);
+ $this->entries = array_values($entries);
+
+ return $this;
+ }
+
+ /**
+ * Get the number of feed entries.
+ * Required by the Iterator interface.
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->entries);
+ }
+
+ /**
+ * Return the current entry
+ *
+ * @return Entry
+ */
+ public function current()
+ {
+ return $this->entries[$this->key()];
+ }
+
+ /**
+ * Return the current feed key
+ *
+ * @return mixed
+ */
+ public function key()
+ {
+ return $this->entriesKey;
+ }
+
+ /**
+ * Move the feed pointer forward
+ *
+ * @return void
+ */
+ public function next()
+ {
+ ++$this->entriesKey;
+ }
+
+ /**
+ * Reset the pointer in the feed object
+ *
+ * @return void
+ */
+ public function rewind()
+ {
+ $this->entriesKey = 0;
+ }
+
+ /**
+ * Check to see if the iterator is still valid
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return 0 <= $this->entriesKey && $this->entriesKey < $this->count();
+ }
+
+ /**
+ * Attempt to build and return the feed resulting from the data set
+ *
+ * @param string $type The feed type "rss" or "atom" to export as
+ * @param bool $ignoreExceptions
+ * @throws Exception\InvalidArgumentException
+ * @return string
+ */
+ public function export($type, $ignoreExceptions = false)
+ {
+ $this->setType(strtolower($type));
+ $type = ucfirst($this->getType());
+ if ($type !== 'Rss' && $type !== 'Atom') {
+ throw new Exception\InvalidArgumentException('Invalid feed type specified: ' . $type . '.'
+ . ' Should be one of "rss" or "atom".');
+ }
+ $renderClass = 'Zend\\Feed\\Writer\\Renderer\\Feed\\' . $type;
+ $renderer = new $renderClass($this);
+ if ($ignoreExceptions) {
+ $renderer->ignoreExceptions();
+ }
+ return $renderer->render()->saveXml();
+ }
+}
diff --git a/library/Zend/Feed/Writer/FeedFactory.php b/library/Zend/Feed/Writer/FeedFactory.php
new file mode 100755
index 0000000000..15e7a3468c
--- /dev/null
+++ b/library/Zend/Feed/Writer/FeedFactory.php
@@ -0,0 +1,127 @@
+ $value) {
+ // Setters
+ $key = static::convertKey($key);
+ $method = 'set' . $key;
+ if (method_exists($feed, $method)) {
+ switch ($method) {
+ case 'setfeedlink':
+ if (!is_array($value)) {
+ // Need an array
+ break;
+ }
+ if (!array_key_exists('link', $value) || !array_key_exists('type', $value)) {
+ // Need both keys to set this correctly
+ break;
+ }
+ $feed->setFeedLink($value['link'], $value['type']);
+ break;
+ default:
+ $feed->$method($value);
+ break;
+ }
+ continue;
+ }
+
+ // Entries
+ if ('entries' == $key) {
+ static::createEntries($value, $feed);
+ continue;
+ }
+ }
+
+ return $feed;
+ }
+
+ /**
+ * Normalize a key
+ *
+ * @param string $key
+ * @return string
+ */
+ protected static function convertKey($key)
+ {
+ $key = str_replace('_', '', strtolower($key));
+ return $key;
+ }
+
+ /**
+ * Create and attach entries to a feed
+ *
+ * @param array|Traversable $entries
+ * @param Feed $feed
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ protected static function createEntries($entries, Feed $feed)
+ {
+ if (!is_array($entries) && !$entries instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s::factory expects the "entries" value to be an array or Traversable; received "%s"',
+ get_called_class(),
+ (is_object($entries) ? get_class($entries) : gettype($entries))
+ ));
+ }
+
+ foreach ($entries as $data) {
+ if (!is_array($data) && !$data instanceof Traversable && !$data instanceof Entry) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects an array, Traversable, or Zend\Feed\Writer\Entry argument; received "%s"',
+ __METHOD__,
+ (is_object($data) ? get_class($data) : gettype($data))
+ ));
+ }
+
+ // Use case 1: Entry item
+ if ($data instanceof Entry) {
+ $feed->addEntry($data);
+ continue;
+ }
+
+ // Use case 2: iterate item and populate entry
+ $entry = $feed->createEntry();
+ foreach ($data as $key => $value) {
+ $key = static::convertKey($key);
+ $method = 'set' . $key;
+ if (!method_exists($entry, $method)) {
+ continue;
+ }
+ $entry->$method($value);
+ }
+ $feed->addEntry($entry);
+ }
+ }
+}
diff --git a/library/Zend/Feed/Writer/Renderer/AbstractRenderer.php b/library/Zend/Feed/Writer/Renderer/AbstractRenderer.php
new file mode 100755
index 0000000000..e104501983
--- /dev/null
+++ b/library/Zend/Feed/Writer/Renderer/AbstractRenderer.php
@@ -0,0 +1,233 @@
+container = $container;
+ $this->setType($container->getType());
+ $this->_loadExtensions();
+ }
+
+ /**
+ * Save XML to string
+ *
+ * @return string
+ */
+ public function saveXml()
+ {
+ return $this->getDomDocument()->saveXml();
+ }
+
+ /**
+ * Get DOM document
+ *
+ * @return DOMDocument
+ */
+ public function getDomDocument()
+ {
+ return $this->dom;
+ }
+
+ /**
+ * Get document element from DOM
+ *
+ * @return DOMElement
+ */
+ public function getElement()
+ {
+ return $this->getDomDocument()->documentElement;
+ }
+
+ /**
+ * Get data container of items being rendered
+ *
+ * @return Writer\AbstractFeed
+ */
+ public function getDataContainer()
+ {
+ return $this->container;
+ }
+
+ /**
+ * Set feed encoding
+ *
+ * @param string $enc
+ * @return AbstractRenderer
+ */
+ public function setEncoding($enc)
+ {
+ $this->encoding = $enc;
+ return $this;
+ }
+
+ /**
+ * Get feed encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->encoding;
+ }
+
+ /**
+ * Indicate whether or not to ignore exceptions
+ *
+ * @param bool $bool
+ * @return AbstractRenderer
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ public function ignoreExceptions($bool = true)
+ {
+ if (!is_bool($bool)) {
+ throw new Writer\Exception\InvalidArgumentException('Invalid parameter: $bool. Should be TRUE or FALSE (defaults to TRUE if null)');
+ }
+ $this->ignoreExceptions = $bool;
+ return $this;
+ }
+
+ /**
+ * Get exception list
+ *
+ * @return array
+ */
+ public function getExceptions()
+ {
+ return $this->exceptions;
+ }
+
+ /**
+ * Set the current feed type being exported to "rss" or "atom". This allows
+ * other objects to gracefully choose whether to execute or not, depending
+ * on their appropriateness for the current type, e.g. renderers.
+ *
+ * @param string $type
+ */
+ public function setType($type)
+ {
+ $this->type = $type;
+ }
+
+ /**
+ * Retrieve the current or last feed type exported.
+ *
+ * @return string Value will be "rss" or "atom"
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * Sets the absolute root element for the XML feed being generated. This
+ * helps simplify the appending of namespace declarations, but also ensures
+ * namespaces are added to the root element - not scattered across the entire
+ * XML file - may assist namespace unsafe parsers and looks pretty ;).
+ *
+ * @param DOMElement $root
+ */
+ public function setRootElement(DOMElement $root)
+ {
+ $this->rootElement = $root;
+ }
+
+ /**
+ * Retrieve the absolute root element for the XML feed being generated.
+ *
+ * @return DOMElement
+ */
+ public function getRootElement()
+ {
+ return $this->rootElement;
+ }
+
+ /**
+ * Load extensions from Zend\Feed\Writer\Writer
+ *
+ * @return void
+ */
+ protected function _loadExtensions()
+ {
+ Writer\Writer::registerCoreExtensions();
+ $manager = Writer\Writer::getExtensionManager();
+ $all = Writer\Writer::getExtensions();
+ if (stripos(get_class($this), 'entry')) {
+ $exts = $all['entryRenderer'];
+ } else {
+ $exts = $all['feedRenderer'];
+ }
+ foreach ($exts as $extension) {
+ $plugin = $manager->get($extension);
+ $plugin->setDataContainer($this->getDataContainer());
+ $plugin->setEncoding($this->getEncoding());
+ $this->extensions[$extension] = $plugin;
+ }
+ }
+}
diff --git a/library/Zend/Feed/Writer/Renderer/Entry/Atom.php b/library/Zend/Feed/Writer/Renderer/Entry/Atom.php
new file mode 100755
index 0000000000..975aa72689
--- /dev/null
+++ b/library/Zend/Feed/Writer/Renderer/Entry/Atom.php
@@ -0,0 +1,424 @@
+dom = new DOMDocument('1.0', $this->container->getEncoding());
+ $this->dom->formatOutput = true;
+ $entry = $this->dom->createElementNS(Writer\Writer::NAMESPACE_ATOM_10, 'entry');
+ $this->dom->appendChild($entry);
+
+ $this->_setSource($this->dom, $entry);
+ $this->_setTitle($this->dom, $entry);
+ $this->_setDescription($this->dom, $entry);
+ $this->_setDateCreated($this->dom, $entry);
+ $this->_setDateModified($this->dom, $entry);
+ $this->_setLink($this->dom, $entry);
+ $this->_setId($this->dom, $entry);
+ $this->_setAuthors($this->dom, $entry);
+ $this->_setEnclosure($this->dom, $entry);
+ $this->_setContent($this->dom, $entry);
+ $this->_setCategories($this->dom, $entry);
+
+ foreach ($this->extensions as $ext) {
+ $ext->setType($this->getType());
+ $ext->setRootElement($this->getRootElement());
+ $ext->setDOMDocument($this->getDOMDocument(), $entry);
+ $ext->render();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set entry title
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ protected function _setTitle(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getTitle()) {
+ $message = 'Atom 1.0 entry elements MUST contain exactly one'
+ . ' atom:title element but a title has not been set';
+ $exception = new Writer\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+ $title = $dom->createElement('title');
+ $root->appendChild($title);
+ $title->setAttribute('type', 'html');
+ $cdata = $dom->createCDATASection($this->getDataContainer()->getTitle());
+ $title->appendChild($cdata);
+ }
+
+ /**
+ * Set entry description
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setDescription(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getDescription()) {
+ return; // unless src content or base64
+ }
+ $subtitle = $dom->createElement('summary');
+ $root->appendChild($subtitle);
+ $subtitle->setAttribute('type', 'html');
+ $cdata = $dom->createCDATASection(
+ $this->getDataContainer()->getDescription()
+ );
+ $subtitle->appendChild($cdata);
+ }
+
+ /**
+ * Set date entry was modified
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ protected function _setDateModified(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getDateModified()) {
+ $message = 'Atom 1.0 entry elements MUST contain exactly one'
+ . ' atom:updated element but a modification date has not been set';
+ $exception = new Writer\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+
+ $updated = $dom->createElement('updated');
+ $root->appendChild($updated);
+ $text = $dom->createTextNode(
+ $this->getDataContainer()->getDateModified()->format(DateTime::ISO8601)
+ );
+ $updated->appendChild($text);
+ }
+
+ /**
+ * Set date entry was created
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setDateCreated(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getDateCreated()) {
+ return;
+ }
+ $el = $dom->createElement('published');
+ $root->appendChild($el);
+ $text = $dom->createTextNode(
+ $this->getDataContainer()->getDateCreated()->format(DateTime::ISO8601)
+ );
+ $el->appendChild($text);
+ }
+
+ /**
+ * Set entry authors
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setAuthors(DOMDocument $dom, DOMElement $root)
+ {
+ $authors = $this->container->getAuthors();
+ if ((!$authors || empty($authors))) {
+ /**
+ * This will actually trigger an Exception at the feed level if
+ * a feed level author is not set.
+ */
+ return;
+ }
+ foreach ($authors as $data) {
+ $author = $this->dom->createElement('author');
+ $name = $this->dom->createElement('name');
+ $author->appendChild($name);
+ $root->appendChild($author);
+ $text = $dom->createTextNode($data['name']);
+ $name->appendChild($text);
+ if (array_key_exists('email', $data)) {
+ $email = $this->dom->createElement('email');
+ $author->appendChild($email);
+ $text = $dom->createTextNode($data['email']);
+ $email->appendChild($text);
+ }
+ if (array_key_exists('uri', $data)) {
+ $uri = $this->dom->createElement('uri');
+ $author->appendChild($uri);
+ $text = $dom->createTextNode($data['uri']);
+ $uri->appendChild($text);
+ }
+ }
+ }
+
+ /**
+ * Set entry enclosure
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setEnclosure(DOMDocument $dom, DOMElement $root)
+ {
+ $data = $this->container->getEnclosure();
+ if ((!$data || empty($data))) {
+ return;
+ }
+ $enclosure = $this->dom->createElement('link');
+ $enclosure->setAttribute('rel', 'enclosure');
+ if (isset($data['type'])) {
+ $enclosure->setAttribute('type', $data['type']);
+ }
+ if (isset($data['length'])) {
+ $enclosure->setAttribute('length', $data['length']);
+ }
+ $enclosure->setAttribute('href', $data['uri']);
+ $root->appendChild($enclosure);
+ }
+
+ protected function _setLink(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getLink()) {
+ return;
+ }
+ $link = $dom->createElement('link');
+ $root->appendChild($link);
+ $link->setAttribute('rel', 'alternate');
+ $link->setAttribute('type', 'text/html');
+ $link->setAttribute('href', $this->getDataContainer()->getLink());
+ }
+
+ /**
+ * Set entry identifier
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ protected function _setId(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getId()
+ && !$this->getDataContainer()->getLink()) {
+ $message = 'Atom 1.0 entry elements MUST contain exactly one '
+ . 'atom:id element, or as an alternative, we can use the same '
+ . 'value as atom:link however neither a suitable link nor an '
+ . 'id have been set';
+ $exception = new Writer\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+
+ if (!$this->getDataContainer()->getId()) {
+ $this->getDataContainer()->setId(
+ $this->getDataContainer()->getLink());
+ }
+ if (!Uri::factory($this->getDataContainer()->getId())->isValid()
+ && !preg_match(
+ "#^urn:[a-zA-Z0-9][a-zA-Z0-9\-]{1,31}:([a-zA-Z0-9\(\)\+\,\.\:\=\@\;\$\_\!\*\-]|%[0-9a-fA-F]{2})*#",
+ $this->getDataContainer()->getId())
+ && !$this->_validateTagUri($this->getDataContainer()->getId())
+ ) {
+ throw new Writer\Exception\InvalidArgumentException('Atom 1.0 IDs must be a valid URI/IRI');
+ }
+ $id = $dom->createElement('id');
+ $root->appendChild($id);
+ $text = $dom->createTextNode($this->getDataContainer()->getId());
+ $id->appendChild($text);
+ }
+
+ /**
+ * Validate a URI using the tag scheme (RFC 4151)
+ *
+ * @param string $id
+ * @return bool
+ */
+ protected function _validateTagUri($id)
+ {
+ if (preg_match('/^tag:(?P.*),(?P\d{4}-?\d{0,2}-?\d{0,2}):(?P.*)(.*:)*$/', $id, $matches)) {
+ $dvalid = false;
+ $date = $matches['date'];
+ $d6 = strtotime($date);
+ if ((strlen($date) == 4) && $date <= date('Y')) {
+ $dvalid = true;
+ } elseif ((strlen($date) == 7) && ($d6 < strtotime("now"))) {
+ $dvalid = true;
+ } elseif ((strlen($date) == 10) && ($d6 < strtotime("now"))) {
+ $dvalid = true;
+ }
+ $validator = new Validator\EmailAddress;
+ if ($validator->isValid($matches['name'])) {
+ $nvalid = true;
+ } else {
+ $nvalid = $validator->isValid('info@' . $matches['name']);
+ }
+ return $dvalid && $nvalid;
+ }
+ return false;
+ }
+
+ /**
+ * Set entry content
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ protected function _setContent(DOMDocument $dom, DOMElement $root)
+ {
+ $content = $this->getDataContainer()->getContent();
+ if (!$content && !$this->getDataContainer()->getLink()) {
+ $message = 'Atom 1.0 entry elements MUST contain exactly one '
+ . 'atom:content element, or as an alternative, at least one link '
+ . 'with a rel attribute of "alternate" to indicate an alternate '
+ . 'method to consume the content.';
+ $exception = new Writer\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+ if (!$content) {
+ return;
+ }
+ $element = $dom->createElement('content');
+ $element->setAttribute('type', 'xhtml');
+ $xhtmlElement = $this->_loadXhtml($content);
+ $xhtml = $dom->importNode($xhtmlElement, true);
+ $element->appendChild($xhtml);
+ $root->appendChild($element);
+ }
+
+ /**
+ * Load a HTML string and attempt to normalise to XML
+ */
+ protected function _loadXhtml($content)
+ {
+ if (class_exists('tidy', false)) {
+ $tidy = new \tidy;
+ $config = array(
+ 'output-xhtml' => true,
+ 'show-body-only' => true,
+ 'quote-nbsp' => false
+ );
+ $encoding = str_replace('-', '', $this->getEncoding());
+ $tidy->parseString($content, $config, $encoding);
+ $tidy->cleanRepair();
+ $xhtml = (string) $tidy;
+ } else {
+ $xhtml = $content;
+ }
+ $xhtml = preg_replace(array(
+ "/(<[\/]?)([a-zA-Z]+)/"
+ ), '$1xhtml:$2', $xhtml);
+ $dom = new DOMDocument('1.0', $this->getEncoding());
+ $dom->loadXML(''
+ . $xhtml . '');
+ return $dom->documentElement;
+ }
+
+ /**
+ * Set entry categories
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setCategories(DOMDocument $dom, DOMElement $root)
+ {
+ $categories = $this->getDataContainer()->getCategories();
+ if (!$categories) {
+ return;
+ }
+ foreach ($categories as $cat) {
+ $category = $dom->createElement('category');
+ $category->setAttribute('term', $cat['term']);
+ if (isset($cat['label'])) {
+ $category->setAttribute('label', $cat['label']);
+ } else {
+ $category->setAttribute('label', $cat['term']);
+ }
+ if (isset($cat['scheme'])) {
+ $category->setAttribute('scheme', $cat['scheme']);
+ }
+ $root->appendChild($category);
+ }
+ }
+
+ /**
+ * Append Source element (Atom 1.0 Feed Metadata)
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setSource(DOMDocument $dom, DOMElement $root)
+ {
+ $source = $this->getDataContainer()->getSource();
+ if (!$source) {
+ return;
+ }
+ $renderer = new Renderer\Feed\AtomSource($source);
+ $renderer->setType($this->getType());
+ $element = $renderer->render()->getElement();
+ $imported = $dom->importNode($element, true);
+ $root->appendChild($imported);
+ }
+}
diff --git a/library/Zend/Feed/Writer/Renderer/Entry/Atom/Deleted.php b/library/Zend/Feed/Writer/Renderer/Entry/Atom/Deleted.php
new file mode 100755
index 0000000000..65ace00bd7
--- /dev/null
+++ b/library/Zend/Feed/Writer/Renderer/Entry/Atom/Deleted.php
@@ -0,0 +1,102 @@
+dom = new DOMDocument('1.0', $this->container->getEncoding());
+ $this->dom->formatOutput = true;
+ $entry = $this->dom->createElement('at:deleted-entry');
+ $this->dom->appendChild($entry);
+
+ $entry->setAttribute('ref', $this->container->getReference());
+ $entry->setAttribute('when', $this->container->getWhen()->format(DateTime::ISO8601));
+
+ $this->_setBy($this->dom, $entry);
+ $this->_setComment($this->dom, $entry);
+
+ return $this;
+ }
+
+ /**
+ * Set tombstone comment
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setComment(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getComment()) {
+ return;
+ }
+ $c = $dom->createElement('at:comment');
+ $root->appendChild($c);
+ $c->setAttribute('type', 'html');
+ $cdata = $dom->createCDATASection($this->getDataContainer()->getComment());
+ $c->appendChild($cdata);
+ }
+
+ /**
+ * Set entry authors
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setBy(DOMDocument $dom, DOMElement $root)
+ {
+ $data = $this->container->getBy();
+ if ((!$data || empty($data))) {
+ return;
+ }
+ $author = $this->dom->createElement('at:by');
+ $name = $this->dom->createElement('name');
+ $author->appendChild($name);
+ $root->appendChild($author);
+ $text = $dom->createTextNode($data['name']);
+ $name->appendChild($text);
+ if (array_key_exists('email', $data)) {
+ $email = $this->dom->createElement('email');
+ $author->appendChild($email);
+ $text = $dom->createTextNode($data['email']);
+ $email->appendChild($text);
+ }
+ if (array_key_exists('uri', $data)) {
+ $uri = $this->dom->createElement('uri');
+ $author->appendChild($uri);
+ $text = $dom->createTextNode($data['uri']);
+ $uri->appendChild($text);
+ }
+ }
+}
diff --git a/library/Zend/Feed/Writer/Renderer/Entry/AtomDeleted.php b/library/Zend/Feed/Writer/Renderer/Entry/AtomDeleted.php
new file mode 100755
index 0000000000..1ed4aa3d9e
--- /dev/null
+++ b/library/Zend/Feed/Writer/Renderer/Entry/AtomDeleted.php
@@ -0,0 +1,104 @@
+dom = new DOMDocument('1.0', $this->container->getEncoding());
+ $this->dom->formatOutput = true;
+ $entry = $this->dom->createElement('at:deleted-entry');
+ $this->dom->appendChild($entry);
+
+ $entry->setAttribute('ref', $this->container->getReference());
+ $entry->setAttribute('when', $this->container->getWhen()->format(DateTime::ISO8601));
+
+ $this->_setBy($this->dom, $entry);
+ $this->_setComment($this->dom, $entry);
+
+ return $this;
+ }
+
+ /**
+ * Set tombstone comment
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setComment(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getComment()) {
+ return;
+ }
+ $c = $dom->createElement('at:comment');
+ $root->appendChild($c);
+ $c->setAttribute('type', 'html');
+ $cdata = $dom->createCDATASection($this->getDataContainer()->getComment());
+ $c->appendChild($cdata);
+ }
+
+ /**
+ * Set entry authors
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setBy(DOMDocument $dom, DOMElement $root)
+ {
+ $data = $this->container->getBy();
+ if ((!$data || empty($data))) {
+ return;
+ }
+ $author = $this->dom->createElement('at:by');
+ $name = $this->dom->createElement('name');
+ $author->appendChild($name);
+ $root->appendChild($author);
+ $text = $dom->createTextNode($data['name']);
+ $name->appendChild($text);
+ if (array_key_exists('email', $data)) {
+ $email = $this->dom->createElement('email');
+ $author->appendChild($email);
+ $text = $dom->createTextNode($data['email']);
+ $email->appendChild($text);
+ }
+ if (array_key_exists('uri', $data)) {
+ $uri = $this->dom->createElement('uri');
+ $author->appendChild($uri);
+ $text = $dom->createTextNode($data['uri']);
+ $uri->appendChild($text);
+ }
+ }
+}
diff --git a/library/Zend/Feed/Writer/Renderer/Entry/Rss.php b/library/Zend/Feed/Writer/Renderer/Entry/Rss.php
new file mode 100755
index 0000000000..2338cdc213
--- /dev/null
+++ b/library/Zend/Feed/Writer/Renderer/Entry/Rss.php
@@ -0,0 +1,329 @@
+dom = new DOMDocument('1.0', $this->container->getEncoding());
+ $this->dom->formatOutput = true;
+ $this->dom->substituteEntities = false;
+ $entry = $this->dom->createElement('item');
+ $this->dom->appendChild($entry);
+
+ $this->_setTitle($this->dom, $entry);
+ $this->_setDescription($this->dom, $entry);
+ $this->_setDateCreated($this->dom, $entry);
+ $this->_setDateModified($this->dom, $entry);
+ $this->_setLink($this->dom, $entry);
+ $this->_setId($this->dom, $entry);
+ $this->_setAuthors($this->dom, $entry);
+ $this->_setEnclosure($this->dom, $entry);
+ $this->_setCommentLink($this->dom, $entry);
+ $this->_setCategories($this->dom, $entry);
+ foreach ($this->extensions as $ext) {
+ $ext->setType($this->getType());
+ $ext->setRootElement($this->getRootElement());
+ $ext->setDOMDocument($this->getDOMDocument(), $entry);
+ $ext->render();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set entry title
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ protected function _setTitle(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getDescription()
+ && !$this->getDataContainer()->getTitle()) {
+ $message = 'RSS 2.0 entry elements SHOULD contain exactly one'
+ . ' title element but a title has not been set. In addition, there'
+ . ' is no description as required in the absence of a title.';
+ $exception = new Writer\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+ $title = $dom->createElement('title');
+ $root->appendChild($title);
+ $text = $dom->createTextNode($this->getDataContainer()->getTitle());
+ $title->appendChild($text);
+ }
+
+ /**
+ * Set entry description
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ protected function _setDescription(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getDescription()
+ && !$this->getDataContainer()->getTitle()) {
+ $message = 'RSS 2.0 entry elements SHOULD contain exactly one'
+ . ' description element but a description has not been set. In'
+ . ' addition, there is no title element as required in the absence'
+ . ' of a description.';
+ $exception = new Writer\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+ if (!$this->getDataContainer()->getDescription()) {
+ return;
+ }
+ $subtitle = $dom->createElement('description');
+ $root->appendChild($subtitle);
+ $text = $dom->createCDATASection($this->getDataContainer()->getDescription());
+ $subtitle->appendChild($text);
+ }
+
+ /**
+ * Set date entry was last modified
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setDateModified(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getDateModified()) {
+ return;
+ }
+
+ $updated = $dom->createElement('pubDate');
+ $root->appendChild($updated);
+ $text = $dom->createTextNode(
+ $this->getDataContainer()->getDateModified()->format(DateTime::RSS)
+ );
+ $updated->appendChild($text);
+ }
+
+ /**
+ * Set date entry was created
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setDateCreated(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getDateCreated()) {
+ return;
+ }
+ if (!$this->getDataContainer()->getDateModified()) {
+ $this->getDataContainer()->setDateModified(
+ $this->getDataContainer()->getDateCreated()
+ );
+ }
+ }
+
+ /**
+ * Set entry authors
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setAuthors(DOMDocument $dom, DOMElement $root)
+ {
+ $authors = $this->container->getAuthors();
+ if ((!$authors || empty($authors))) {
+ return;
+ }
+ foreach ($authors as $data) {
+ $author = $this->dom->createElement('author');
+ $name = $data['name'];
+ if (array_key_exists('email', $data)) {
+ $name = $data['email'] . ' (' . $data['name'] . ')';
+ }
+ $text = $dom->createTextNode($name);
+ $author->appendChild($text);
+ $root->appendChild($author);
+ }
+ }
+
+ /**
+ * Set entry enclosure
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ protected function _setEnclosure(DOMDocument $dom, DOMElement $root)
+ {
+ $data = $this->container->getEnclosure();
+ if ((!$data || empty($data))) {
+ return;
+ }
+ if (!isset($data['type'])) {
+ $exception = new Writer\Exception\InvalidArgumentException('Enclosure "type" is not set');
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+ if (!isset($data['length'])) {
+ $exception = new Writer\Exception\InvalidArgumentException('Enclosure "length" is not set');
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+ if (isset($data['length']) && (int) $data['length'] <= 0) {
+ $exception = new Writer\Exception\InvalidArgumentException('Enclosure "length" must be an integer'
+ . ' indicating the content\'s length in bytes');
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+ $enclosure = $this->dom->createElement('enclosure');
+ $enclosure->setAttribute('type', $data['type']);
+ $enclosure->setAttribute('length', $data['length']);
+ $enclosure->setAttribute('url', $data['uri']);
+ $root->appendChild($enclosure);
+ }
+
+ /**
+ * Set link to entry
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setLink(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getLink()) {
+ return;
+ }
+ $link = $dom->createElement('link');
+ $root->appendChild($link);
+ $text = $dom->createTextNode($this->getDataContainer()->getLink());
+ $link->appendChild($text);
+ }
+
+ /**
+ * Set entry identifier
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setId(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getId()
+ && !$this->getDataContainer()->getLink()) {
+ return;
+ }
+
+ $id = $dom->createElement('guid');
+ $root->appendChild($id);
+ if (!$this->getDataContainer()->getId()) {
+ $this->getDataContainer()->setId(
+ $this->getDataContainer()->getLink());
+ }
+ $text = $dom->createTextNode($this->getDataContainer()->getId());
+ $id->appendChild($text);
+ if (!Uri::factory($this->getDataContainer()->getId())->isValid()) {
+ $id->setAttribute('isPermaLink', 'false');
+ }
+ }
+
+ /**
+ * Set link to entry comments
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setCommentLink(DOMDocument $dom, DOMElement $root)
+ {
+ $link = $this->getDataContainer()->getCommentLink();
+ if (!$link) {
+ return;
+ }
+ $clink = $this->dom->createElement('comments');
+ $text = $dom->createTextNode($link);
+ $clink->appendChild($text);
+ $root->appendChild($clink);
+ }
+
+ /**
+ * Set entry categories
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setCategories(DOMDocument $dom, DOMElement $root)
+ {
+ $categories = $this->getDataContainer()->getCategories();
+ if (!$categories) {
+ return;
+ }
+ foreach ($categories as $cat) {
+ $category = $dom->createElement('category');
+ if (isset($cat['scheme'])) {
+ $category->setAttribute('domain', $cat['scheme']);
+ }
+ $text = $dom->createCDATASection($cat['term']);
+ $category->appendChild($text);
+ $root->appendChild($category);
+ }
+ }
+}
diff --git a/library/Zend/Feed/Writer/Renderer/Feed/AbstractAtom.php b/library/Zend/Feed/Writer/Renderer/Feed/AbstractAtom.php
new file mode 100755
index 0000000000..e7ad9f56ba
--- /dev/null
+++ b/library/Zend/Feed/Writer/Renderer/Feed/AbstractAtom.php
@@ -0,0 +1,403 @@
+getDataContainer()->getLanguage()) {
+ $root->setAttribute('xml:lang', $this->getDataContainer()
+ ->getLanguage());
+ }
+ }
+
+ /**
+ * Set feed title
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ protected function _setTitle(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getTitle()) {
+ $message = 'Atom 1.0 feed elements MUST contain exactly one'
+ . ' atom:title element but a title has not been set';
+ $exception = new Writer\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+
+ $title = $dom->createElement('title');
+ $root->appendChild($title);
+ $title->setAttribute('type', 'text');
+ $text = $dom->createTextNode($this->getDataContainer()->getTitle());
+ $title->appendChild($text);
+ }
+
+ /**
+ * Set feed description
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setDescription(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getDescription()) {
+ return;
+ }
+ $subtitle = $dom->createElement('subtitle');
+ $root->appendChild($subtitle);
+ $subtitle->setAttribute('type', 'text');
+ $text = $dom->createTextNode($this->getDataContainer()->getDescription());
+ $subtitle->appendChild($text);
+ }
+
+ /**
+ * Set date feed was last modified
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ protected function _setDateModified(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getDateModified()) {
+ $message = 'Atom 1.0 feed elements MUST contain exactly one'
+ . ' atom:updated element but a modification date has not been set';
+ $exception = new Writer\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+
+ $updated = $dom->createElement('updated');
+ $root->appendChild($updated);
+ $text = $dom->createTextNode(
+ $this->getDataContainer()->getDateModified()->format(DateTime::ISO8601)
+ );
+ $updated->appendChild($text);
+ }
+
+ /**
+ * Set feed generator string
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setGenerator(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getGenerator()) {
+ $this->getDataContainer()->setGenerator('Zend_Feed_Writer',
+ Version::VERSION, 'http://framework.zend.com');
+ }
+
+ $gdata = $this->getDataContainer()->getGenerator();
+ $generator = $dom->createElement('generator');
+ $root->appendChild($generator);
+ $text = $dom->createTextNode($gdata['name']);
+ $generator->appendChild($text);
+ if (array_key_exists('uri', $gdata)) {
+ $generator->setAttribute('uri', $gdata['uri']);
+ }
+ if (array_key_exists('version', $gdata)) {
+ $generator->setAttribute('version', $gdata['version']);
+ }
+ }
+
+ /**
+ * Set link to feed
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setLink(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getLink()) {
+ return;
+ }
+ $link = $dom->createElement('link');
+ $root->appendChild($link);
+ $link->setAttribute('rel', 'alternate');
+ $link->setAttribute('type', 'text/html');
+ $link->setAttribute('href', $this->getDataContainer()->getLink());
+ }
+
+ /**
+ * Set feed links
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ protected function _setFeedLinks(DOMDocument $dom, DOMElement $root)
+ {
+ $flinks = $this->getDataContainer()->getFeedLinks();
+ if (!$flinks || !array_key_exists('atom', $flinks)) {
+ $message = 'Atom 1.0 feed elements SHOULD contain one atom:link '
+ . 'element with a rel attribute value of "self". This is the '
+ . 'preferred URI for retrieving Atom Feed Documents representing '
+ . 'this Atom feed but a feed link has not been set';
+ $exception = new Writer\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+
+ foreach ($flinks as $type => $href) {
+ $mime = 'application/' . strtolower($type) . '+xml';
+ $flink = $dom->createElement('link');
+ $root->appendChild($flink);
+ $flink->setAttribute('rel', 'self');
+ $flink->setAttribute('type', $mime);
+ $flink->setAttribute('href', $href);
+ }
+ }
+
+ /**
+ * Set feed authors
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setAuthors(DOMDocument $dom, DOMElement $root)
+ {
+ $authors = $this->container->getAuthors();
+ if (!$authors || empty($authors)) {
+ /**
+ * Technically we should defer an exception until we can check
+ * that all entries contain an author. If any entry is missing
+ * an author, then a missing feed author element is invalid
+ */
+ return;
+ }
+ foreach ($authors as $data) {
+ $author = $this->dom->createElement('author');
+ $name = $this->dom->createElement('name');
+ $author->appendChild($name);
+ $root->appendChild($author);
+ $text = $dom->createTextNode($data['name']);
+ $name->appendChild($text);
+ if (array_key_exists('email', $data)) {
+ $email = $this->dom->createElement('email');
+ $author->appendChild($email);
+ $text = $dom->createTextNode($data['email']);
+ $email->appendChild($text);
+ }
+ if (array_key_exists('uri', $data)) {
+ $uri = $this->dom->createElement('uri');
+ $author->appendChild($uri);
+ $text = $dom->createTextNode($data['uri']);
+ $uri->appendChild($text);
+ }
+ }
+ }
+
+ /**
+ * Set feed identifier
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ protected function _setId(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getId()
+ && !$this->getDataContainer()->getLink()) {
+ $message = 'Atom 1.0 feed elements MUST contain exactly one '
+ . 'atom:id element, or as an alternative, we can use the same '
+ . 'value as atom:link however neither a suitable link nor an '
+ . 'id have been set';
+ $exception = new Writer\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+
+ if (!$this->getDataContainer()->getId()) {
+ $this->getDataContainer()->setId(
+ $this->getDataContainer()->getLink());
+ }
+ $id = $dom->createElement('id');
+ $root->appendChild($id);
+ $text = $dom->createTextNode($this->getDataContainer()->getId());
+ $id->appendChild($text);
+ }
+
+ /**
+ * Set feed copyright
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setCopyright(DOMDocument $dom, DOMElement $root)
+ {
+ $copyright = $this->getDataContainer()->getCopyright();
+ if (!$copyright) {
+ return;
+ }
+ $copy = $dom->createElement('rights');
+ $root->appendChild($copy);
+ $text = $dom->createTextNode($copyright);
+ $copy->appendChild($text);
+ }
+
+ /**
+ * Set feed level logo (image)
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setImage(DOMDocument $dom, DOMElement $root)
+ {
+ $image = $this->getDataContainer()->getImage();
+ if (!$image) {
+ return;
+ }
+ $img = $dom->createElement('logo');
+ $root->appendChild($img);
+ $text = $dom->createTextNode($image['uri']);
+ $img->appendChild($text);
+ }
+
+ /**
+ * Set date feed was created
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setDateCreated(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getDateCreated()) {
+ return;
+ }
+ if (!$this->getDataContainer()->getDateModified()) {
+ $this->getDataContainer()->setDateModified(
+ $this->getDataContainer()->getDateCreated()
+ );
+ }
+ }
+
+ /**
+ * Set base URL to feed links
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setBaseUrl(DOMDocument $dom, DOMElement $root)
+ {
+ $baseUrl = $this->getDataContainer()->getBaseUrl();
+ if (!$baseUrl) {
+ return;
+ }
+ $root->setAttribute('xml:base', $baseUrl);
+ }
+
+ /**
+ * Set hubs to which this feed pushes
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setHubs(DOMDocument $dom, DOMElement $root)
+ {
+ $hubs = $this->getDataContainer()->getHubs();
+ if (!$hubs) {
+ return;
+ }
+ foreach ($hubs as $hubUrl) {
+ $hub = $dom->createElement('link');
+ $hub->setAttribute('rel', 'hub');
+ $hub->setAttribute('href', $hubUrl);
+ $root->appendChild($hub);
+ }
+ }
+
+ /**
+ * Set feed categories
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setCategories(DOMDocument $dom, DOMElement $root)
+ {
+ $categories = $this->getDataContainer()->getCategories();
+ if (!$categories) {
+ return;
+ }
+ foreach ($categories as $cat) {
+ $category = $dom->createElement('category');
+ $category->setAttribute('term', $cat['term']);
+ if (isset($cat['label'])) {
+ $category->setAttribute('label', $cat['label']);
+ } else {
+ $category->setAttribute('label', $cat['term']);
+ }
+ if (isset($cat['scheme'])) {
+ $category->setAttribute('scheme', $cat['scheme']);
+ }
+ $root->appendChild($category);
+ }
+ }
+}
diff --git a/library/Zend/Feed/Writer/Renderer/Feed/Atom.php b/library/Zend/Feed/Writer/Renderer/Feed/Atom.php
new file mode 100755
index 0000000000..87b6b94ac0
--- /dev/null
+++ b/library/Zend/Feed/Writer/Renderer/Feed/Atom.php
@@ -0,0 +1,96 @@
+container->getEncoding()) {
+ $this->container->setEncoding('UTF-8');
+ }
+ $this->dom = new DOMDocument('1.0', $this->container->getEncoding());
+ $this->dom->formatOutput = true;
+ $root = $this->dom->createElementNS(
+ Writer\Writer::NAMESPACE_ATOM_10, 'feed'
+ );
+ $this->setRootElement($root);
+ $this->dom->appendChild($root);
+ $this->_setLanguage($this->dom, $root);
+ $this->_setBaseUrl($this->dom, $root);
+ $this->_setTitle($this->dom, $root);
+ $this->_setDescription($this->dom, $root);
+ $this->_setImage($this->dom, $root);
+ $this->_setDateCreated($this->dom, $root);
+ $this->_setDateModified($this->dom, $root);
+ $this->_setGenerator($this->dom, $root);
+ $this->_setLink($this->dom, $root);
+ $this->_setFeedLinks($this->dom, $root);
+ $this->_setId($this->dom, $root);
+ $this->_setAuthors($this->dom, $root);
+ $this->_setCopyright($this->dom, $root);
+ $this->_setCategories($this->dom, $root);
+ $this->_setHubs($this->dom, $root);
+
+ foreach ($this->extensions as $ext) {
+ $ext->setType($this->getType());
+ $ext->setRootElement($this->getRootElement());
+ $ext->setDOMDocument($this->getDOMDocument(), $root);
+ $ext->render();
+ }
+
+ foreach ($this->container as $entry) {
+ if ($this->getDataContainer()->getEncoding()) {
+ $entry->setEncoding($this->getDataContainer()->getEncoding());
+ }
+ if ($entry instanceof Writer\Entry) {
+ $renderer = new Renderer\Entry\Atom($entry);
+ } else {
+ if (!$this->dom->documentElement->hasAttribute('xmlns:at')) {
+ $this->dom->documentElement->setAttribute(
+ 'xmlns:at', 'http://purl.org/atompub/tombstones/1.0'
+ );
+ }
+ $renderer = new Renderer\Entry\AtomDeleted($entry);
+ }
+ if ($this->ignoreExceptions === true) {
+ $renderer->ignoreExceptions();
+ }
+ $renderer->setType($this->getType());
+ $renderer->setRootElement($this->dom->documentElement);
+ $renderer->render();
+ $element = $renderer->getElement();
+ $imported = $this->dom->importNode($element, true);
+ $root->appendChild($imported);
+ }
+ return $this;
+ }
+}
diff --git a/library/Zend/Feed/Writer/Renderer/Feed/Atom/AbstractAtom.php b/library/Zend/Feed/Writer/Renderer/Feed/Atom/AbstractAtom.php
new file mode 100755
index 0000000000..379cd5c9f7
--- /dev/null
+++ b/library/Zend/Feed/Writer/Renderer/Feed/Atom/AbstractAtom.php
@@ -0,0 +1,400 @@
+getDataContainer()->getLanguage()) {
+ $root->setAttribute('xml:lang', $this->getDataContainer()
+ ->getLanguage());
+ }
+ }
+
+ /**
+ * Set feed title
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ * @throws Feed\Exception\InvalidArgumentException
+ */
+ protected function _setTitle(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getTitle()) {
+ $message = 'Atom 1.0 feed elements MUST contain exactly one'
+ . ' atom:title element but a title has not been set';
+ $exception = new Feed\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+
+ $title = $dom->createElement('title');
+ $root->appendChild($title);
+ $title->setAttribute('type', 'text');
+ $text = $dom->createTextNode($this->getDataContainer()->getTitle());
+ $title->appendChild($text);
+ }
+
+ /**
+ * Set feed description
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setDescription(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getDescription()) {
+ return;
+ }
+ $subtitle = $dom->createElement('subtitle');
+ $root->appendChild($subtitle);
+ $subtitle->setAttribute('type', 'text');
+ $text = $dom->createTextNode($this->getDataContainer()->getDescription());
+ $subtitle->appendChild($text);
+ }
+
+ /**
+ * Set date feed was last modified
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ * @throws Feed\Exception\InvalidArgumentException
+ */
+ protected function _setDateModified(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getDateModified()) {
+ $message = 'Atom 1.0 feed elements MUST contain exactly one'
+ . ' atom:updated element but a modification date has not been set';
+ $exception = new Feed\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+
+ $updated = $dom->createElement('updated');
+ $root->appendChild($updated);
+ $text = $dom->createTextNode(
+ $this->getDataContainer()->getDateModified()->format(DateTime::ISO8601)
+ );
+ $updated->appendChild($text);
+ }
+
+ /**
+ * Set feed generator string
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setGenerator(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getGenerator()) {
+ $this->getDataContainer()->setGenerator('Zend_Feed_Writer',
+ Version::VERSION, 'http://framework.zend.com');
+ }
+
+ $gdata = $this->getDataContainer()->getGenerator();
+ $generator = $dom->createElement('generator');
+ $root->appendChild($generator);
+ $text = $dom->createTextNode($gdata['name']);
+ $generator->appendChild($text);
+ if (array_key_exists('uri', $gdata)) {
+ $generator->setAttribute('uri', $gdata['uri']);
+ }
+ if (array_key_exists('version', $gdata)) {
+ $generator->setAttribute('version', $gdata['version']);
+ }
+ }
+
+ /**
+ * Set link to feed
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setLink(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getLink()) {
+ return;
+ }
+ $link = $dom->createElement('link');
+ $root->appendChild($link);
+ $link->setAttribute('rel', 'alternate');
+ $link->setAttribute('type', 'text/html');
+ $link->setAttribute('href', $this->getDataContainer()->getLink());
+ }
+
+ /**
+ * Set feed links
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ * @throws Feed\Exception\InvalidArgumentException
+ */
+ protected function _setFeedLinks(DOMDocument $dom, DOMElement $root)
+ {
+ $flinks = $this->getDataContainer()->getFeedLinks();
+ if (!$flinks || !array_key_exists('atom', $flinks)) {
+ $message = 'Atom 1.0 feed elements SHOULD contain one atom:link '
+ . 'element with a rel attribute value of "self". This is the '
+ . 'preferred URI for retrieving Atom Feed Documents representing '
+ . 'this Atom feed but a feed link has not been set';
+ $exception = new Feed\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+
+ foreach ($flinks as $type => $href) {
+ $mime = 'application/' . strtolower($type) . '+xml';
+ $flink = $dom->createElement('link');
+ $root->appendChild($flink);
+ $flink->setAttribute('rel', 'self');
+ $flink->setAttribute('type', $mime);
+ $flink->setAttribute('href', $href);
+ }
+ }
+
+ /**
+ * Set feed authors
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setAuthors(DOMDocument $dom, DOMElement $root)
+ {
+ $authors = $this->container->getAuthors();
+ if (!$authors || empty($authors)) {
+ /**
+ * Technically we should defer an exception until we can check
+ * that all entries contain an author. If any entry is missing
+ * an author, then a missing feed author element is invalid
+ */
+ return;
+ }
+ foreach ($authors as $data) {
+ $author = $this->dom->createElement('author');
+ $name = $this->dom->createElement('name');
+ $author->appendChild($name);
+ $root->appendChild($author);
+ $text = $dom->createTextNode($data['name']);
+ $name->appendChild($text);
+ if (array_key_exists('email', $data)) {
+ $email = $this->dom->createElement('email');
+ $author->appendChild($email);
+ $text = $dom->createTextNode($data['email']);
+ $email->appendChild($text);
+ }
+ if (array_key_exists('uri', $data)) {
+ $uri = $this->dom->createElement('uri');
+ $author->appendChild($uri);
+ $text = $dom->createTextNode($data['uri']);
+ $uri->appendChild($text);
+ }
+ }
+ }
+
+ /**
+ * Set feed identifier
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ * @throws Feed\Exception\InvalidArgumentException
+ */
+ protected function _setId(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getId()
+ && !$this->getDataContainer()->getLink()) {
+ $message = 'Atom 1.0 feed elements MUST contain exactly one '
+ . 'atom:id element, or as an alternative, we can use the same '
+ . 'value as atom:link however neither a suitable link nor an '
+ . 'id have been set';
+ $exception = new Feed\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+
+ if (!$this->getDataContainer()->getId()) {
+ $this->getDataContainer()->setId(
+ $this->getDataContainer()->getLink());
+ }
+ $id = $dom->createElement('id');
+ $root->appendChild($id);
+ $text = $dom->createTextNode($this->getDataContainer()->getId());
+ $id->appendChild($text);
+ }
+
+ /**
+ * Set feed copyright
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setCopyright(DOMDocument $dom, DOMElement $root)
+ {
+ $copyright = $this->getDataContainer()->getCopyright();
+ if (!$copyright) {
+ return;
+ }
+ $copy = $dom->createElement('rights');
+ $root->appendChild($copy);
+ $text = $dom->createTextNode($copyright);
+ $copy->appendChild($text);
+ }
+ /**
+ * Set feed level logo (image)
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setImage(DOMDocument $dom, DOMElement $root)
+ {
+ $image = $this->getDataContainer()->getImage();
+ if (!$image) {
+ return;
+ }
+ $img = $dom->createElement('logo');
+ $root->appendChild($img);
+ $text = $dom->createTextNode($image['uri']);
+ $img->appendChild($text);
+ }
+
+
+ /**
+ * Set date feed was created
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setDateCreated(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getDateCreated()) {
+ return;
+ }
+ if (!$this->getDataContainer()->getDateModified()) {
+ $this->getDataContainer()->setDateModified(
+ $this->getDataContainer()->getDateCreated()
+ );
+ }
+ }
+
+ /**
+ * Set base URL to feed links
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setBaseUrl(DOMDocument $dom, DOMElement $root)
+ {
+ $baseUrl = $this->getDataContainer()->getBaseUrl();
+ if (!$baseUrl) {
+ return;
+ }
+ $root->setAttribute('xml:base', $baseUrl);
+ }
+
+ /**
+ * Set hubs to which this feed pushes
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setHubs(DOMDocument $dom, DOMElement $root)
+ {
+ $hubs = $this->getDataContainer()->getHubs();
+ if (!$hubs) {
+ return;
+ }
+ foreach ($hubs as $hubUrl) {
+ $hub = $dom->createElement('link');
+ $hub->setAttribute('rel', 'hub');
+ $hub->setAttribute('href', $hubUrl);
+ $root->appendChild($hub);
+ }
+ }
+
+ /**
+ * Set feed categories
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setCategories(DOMDocument $dom, DOMElement $root)
+ {
+ $categories = $this->getDataContainer()->getCategories();
+ if (!$categories) {
+ return;
+ }
+ foreach ($categories as $cat) {
+ $category = $dom->createElement('category');
+ $category->setAttribute('term', $cat['term']);
+ if (isset($cat['label'])) {
+ $category->setAttribute('label', $cat['label']);
+ } else {
+ $category->setAttribute('label', $cat['term']);
+ }
+ if (isset($cat['scheme'])) {
+ $category->setAttribute('scheme', $cat['scheme']);
+ }
+ $root->appendChild($category);
+ }
+ }
+}
diff --git a/library/Zend/Feed/Writer/Renderer/Feed/Atom/Source.php b/library/Zend/Feed/Writer/Renderer/Feed/Atom/Source.php
new file mode 100755
index 0000000000..30f576a1f5
--- /dev/null
+++ b/library/Zend/Feed/Writer/Renderer/Feed/Atom/Source.php
@@ -0,0 +1,92 @@
+container->getEncoding()) {
+ $this->container->setEncoding('UTF-8');
+ }
+ $this->dom = new DOMDocument('1.0', $this->container->getEncoding());
+ $this->dom->formatOutput = true;
+ $root = $this->dom->createElement('source');
+ $this->setRootElement($root);
+ $this->dom->appendChild($root);
+ $this->_setLanguage($this->dom, $root);
+ $this->_setBaseUrl($this->dom, $root);
+ $this->_setTitle($this->dom, $root);
+ $this->_setDescription($this->dom, $root);
+ $this->_setDateCreated($this->dom, $root);
+ $this->_setDateModified($this->dom, $root);
+ $this->_setGenerator($this->dom, $root);
+ $this->_setLink($this->dom, $root);
+ $this->_setFeedLinks($this->dom, $root);
+ $this->_setId($this->dom, $root);
+ $this->_setAuthors($this->dom, $root);
+ $this->_setCopyright($this->dom, $root);
+ $this->_setCategories($this->dom, $root);
+
+ foreach ($this->extensions as $ext) {
+ $ext->setType($this->getType());
+ $ext->setRootElement($this->getRootElement());
+ $ext->setDomDocument($this->getDomDocument(), $root);
+ $ext->render();
+ }
+ return $this;
+ }
+
+ /**
+ * Set feed generator string
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setGenerator(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getGenerator()) {
+ return;
+ }
+
+ $gdata = $this->getDataContainer()->getGenerator();
+ $generator = $dom->createElement('generator');
+ $root->appendChild($generator);
+ $text = $dom->createTextNode($gdata['name']);
+ $generator->appendChild($text);
+ if (array_key_exists('uri', $gdata)) {
+ $generator->setAttribute('uri', $gdata['uri']);
+ }
+ if (array_key_exists('version', $gdata)) {
+ $generator->setAttribute('version', $gdata['version']);
+ }
+ }
+}
diff --git a/library/Zend/Feed/Writer/Renderer/Feed/AtomSource.php b/library/Zend/Feed/Writer/Renderer/Feed/AtomSource.php
new file mode 100755
index 0000000000..bdadd4db02
--- /dev/null
+++ b/library/Zend/Feed/Writer/Renderer/Feed/AtomSource.php
@@ -0,0 +1,94 @@
+container->getEncoding()) {
+ $this->container->setEncoding('UTF-8');
+ }
+ $this->dom = new DOMDocument('1.0', $this->container->getEncoding());
+ $this->dom->formatOutput = true;
+ $root = $this->dom->createElement('source');
+ $this->setRootElement($root);
+ $this->dom->appendChild($root);
+ $this->_setLanguage($this->dom, $root);
+ $this->_setBaseUrl($this->dom, $root);
+ $this->_setTitle($this->dom, $root);
+ $this->_setDescription($this->dom, $root);
+ $this->_setDateCreated($this->dom, $root);
+ $this->_setDateModified($this->dom, $root);
+ $this->_setGenerator($this->dom, $root);
+ $this->_setLink($this->dom, $root);
+ $this->_setFeedLinks($this->dom, $root);
+ $this->_setId($this->dom, $root);
+ $this->_setAuthors($this->dom, $root);
+ $this->_setCopyright($this->dom, $root);
+ $this->_setCategories($this->dom, $root);
+
+ foreach ($this->extensions as $ext) {
+ $ext->setType($this->getType());
+ $ext->setRootElement($this->getRootElement());
+ $ext->setDOMDocument($this->getDOMDocument(), $root);
+ $ext->render();
+ }
+ return $this;
+ }
+
+ /**
+ * Set feed generator string
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setGenerator(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getGenerator()) {
+ return;
+ }
+
+ $gdata = $this->getDataContainer()->getGenerator();
+ $generator = $dom->createElement('generator');
+ $root->appendChild($generator);
+ $text = $dom->createTextNode($gdata['name']);
+ $generator->appendChild($text);
+ if (array_key_exists('uri', $gdata)) {
+ $generator->setAttribute('uri', $gdata['uri']);
+ }
+ if (array_key_exists('version', $gdata)) {
+ $generator->setAttribute('version', $gdata['version']);
+ }
+ }
+}
diff --git a/library/Zend/Feed/Writer/Renderer/Feed/Rss.php b/library/Zend/Feed/Writer/Renderer/Feed/Rss.php
new file mode 100755
index 0000000000..75c502e323
--- /dev/null
+++ b/library/Zend/Feed/Writer/Renderer/Feed/Rss.php
@@ -0,0 +1,484 @@
+dom = new DOMDocument('1.0', $this->container->getEncoding());
+ $this->dom->formatOutput = true;
+ $this->dom->substituteEntities = false;
+ $rss = $this->dom->createElement('rss');
+ $this->setRootElement($rss);
+ $rss->setAttribute('version', '2.0');
+
+ $channel = $this->dom->createElement('channel');
+ $rss->appendChild($channel);
+ $this->dom->appendChild($rss);
+ $this->_setLanguage($this->dom, $channel);
+ $this->_setBaseUrl($this->dom, $channel);
+ $this->_setTitle($this->dom, $channel);
+ $this->_setDescription($this->dom, $channel);
+ $this->_setImage($this->dom, $channel);
+ $this->_setDateCreated($this->dom, $channel);
+ $this->_setDateModified($this->dom, $channel);
+ $this->_setLastBuildDate($this->dom, $channel);
+ $this->_setGenerator($this->dom, $channel);
+ $this->_setLink($this->dom, $channel);
+ $this->_setAuthors($this->dom, $channel);
+ $this->_setCopyright($this->dom, $channel);
+ $this->_setCategories($this->dom, $channel);
+
+ foreach ($this->extensions as $ext) {
+ $ext->setType($this->getType());
+ $ext->setRootElement($this->getRootElement());
+ $ext->setDOMDocument($this->getDOMDocument(), $channel);
+ $ext->render();
+ }
+
+ foreach ($this->container as $entry) {
+ if ($this->getDataContainer()->getEncoding()) {
+ $entry->setEncoding($this->getDataContainer()->getEncoding());
+ }
+ if ($entry instanceof Writer\Entry) {
+ $renderer = new Renderer\Entry\Rss($entry);
+ } else {
+ continue;
+ }
+ if ($this->ignoreExceptions === true) {
+ $renderer->ignoreExceptions();
+ }
+ $renderer->setType($this->getType());
+ $renderer->setRootElement($this->dom->documentElement);
+ $renderer->render();
+ $element = $renderer->getElement();
+ $imported = $this->dom->importNode($element, true);
+ $channel->appendChild($imported);
+ }
+ return $this;
+ }
+
+ /**
+ * Set feed language
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setLanguage(DOMDocument $dom, DOMElement $root)
+ {
+ $lang = $this->getDataContainer()->getLanguage();
+ if (!$lang) {
+ return;
+ }
+ $language = $dom->createElement('language');
+ $root->appendChild($language);
+ $language->nodeValue = $lang;
+ }
+
+ /**
+ * Set feed title
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ protected function _setTitle(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getTitle()) {
+ $message = 'RSS 2.0 feed elements MUST contain exactly one'
+ . ' title element but a title has not been set';
+ $exception = new Writer\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+
+ $title = $dom->createElement('title');
+ $root->appendChild($title);
+ $text = $dom->createTextNode($this->getDataContainer()->getTitle());
+ $title->appendChild($text);
+ }
+
+ /**
+ * Set feed description
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ protected function _setDescription(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getDescription()) {
+ $message = 'RSS 2.0 feed elements MUST contain exactly one'
+ . ' description element but one has not been set';
+ $exception = new Writer\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+ $subtitle = $dom->createElement('description');
+ $root->appendChild($subtitle);
+ $text = $dom->createTextNode($this->getDataContainer()->getDescription());
+ $subtitle->appendChild($text);
+ }
+
+ /**
+ * Set date feed was last modified
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setDateModified(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getDateModified()) {
+ return;
+ }
+
+ $updated = $dom->createElement('pubDate');
+ $root->appendChild($updated);
+ $text = $dom->createTextNode(
+ $this->getDataContainer()->getDateModified()->format(DateTime::RSS)
+ );
+ $updated->appendChild($text);
+ }
+
+ /**
+ * Set feed generator string
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setGenerator(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getGenerator()) {
+ $this->getDataContainer()->setGenerator('Zend_Feed_Writer',
+ Version::VERSION, 'http://framework.zend.com');
+ }
+
+ $gdata = $this->getDataContainer()->getGenerator();
+ $generator = $dom->createElement('generator');
+ $root->appendChild($generator);
+ $name = $gdata['name'];
+ if (array_key_exists('version', $gdata)) {
+ $name .= ' ' . $gdata['version'];
+ }
+ if (array_key_exists('uri', $gdata)) {
+ $name .= ' (' . $gdata['uri'] . ')';
+ }
+ $text = $dom->createTextNode($name);
+ $generator->appendChild($text);
+ }
+
+ /**
+ * Set link to feed
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ protected function _setLink(DOMDocument $dom, DOMElement $root)
+ {
+ $value = $this->getDataContainer()->getLink();
+ if (!$value) {
+ $message = 'RSS 2.0 feed elements MUST contain exactly one'
+ . ' link element but one has not been set';
+ $exception = new Writer\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+ $link = $dom->createElement('link');
+ $root->appendChild($link);
+ $text = $dom->createTextNode($value);
+ $link->appendChild($text);
+ if (!Uri::factory($value)->isValid()) {
+ $link->setAttribute('isPermaLink', 'false');
+ }
+ }
+
+ /**
+ * Set feed authors
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setAuthors(DOMDocument $dom, DOMElement $root)
+ {
+ $authors = $this->getDataContainer()->getAuthors();
+ if (!$authors || empty($authors)) {
+ return;
+ }
+ foreach ($authors as $data) {
+ $author = $this->dom->createElement('author');
+ $name = $data['name'];
+ if (array_key_exists('email', $data)) {
+ $name = $data['email'] . ' (' . $data['name'] . ')';
+ }
+ $text = $dom->createTextNode($name);
+ $author->appendChild($text);
+ $root->appendChild($author);
+ }
+ }
+
+ /**
+ * Set feed copyright
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setCopyright(DOMDocument $dom, DOMElement $root)
+ {
+ $copyright = $this->getDataContainer()->getCopyright();
+ if (!$copyright) {
+ return;
+ }
+ $copy = $dom->createElement('copyright');
+ $root->appendChild($copy);
+ $text = $dom->createTextNode($copyright);
+ $copy->appendChild($text);
+ }
+
+ /**
+ * Set feed channel image
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ * @throws Writer\Exception\InvalidArgumentException
+ */
+ protected function _setImage(DOMDocument $dom, DOMElement $root)
+ {
+ $image = $this->getDataContainer()->getImage();
+ if (!$image) {
+ return;
+ }
+
+ if (!isset($image['title']) || empty($image['title'])
+ || !is_string($image['title'])
+ ) {
+ $message = 'RSS 2.0 feed images must include a title';
+ $exception = new Writer\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+
+ if (empty($image['link']) || !is_string($image['link'])
+ || !Uri::factory($image['link'])->isValid()
+ ) {
+ $message = 'Invalid parameter: parameter \'link\''
+ . ' must be a non-empty string and valid URI/IRI';
+ $exception = new Writer\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+
+ $img = $dom->createElement('image');
+ $root->appendChild($img);
+
+ $url = $dom->createElement('url');
+ $text = $dom->createTextNode($image['uri']);
+ $url->appendChild($text);
+
+ $title = $dom->createElement('title');
+ $text = $dom->createTextNode($image['title']);
+ $title->appendChild($text);
+
+ $link = $dom->createElement('link');
+ $text = $dom->createTextNode($image['link']);
+ $link->appendChild($text);
+
+ $img->appendChild($url);
+ $img->appendChild($title);
+ $img->appendChild($link);
+
+ if (isset($image['height'])) {
+ if (!ctype_digit((string) $image['height']) || $image['height'] > 400) {
+ $message = 'Invalid parameter: parameter \'height\''
+ . ' must be an integer not exceeding 400';
+ $exception = new Writer\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+ $height = $dom->createElement('height');
+ $text = $dom->createTextNode($image['height']);
+ $height->appendChild($text);
+ $img->appendChild($height);
+ }
+ if (isset($image['width'])) {
+ if (!ctype_digit((string) $image['width']) || $image['width'] > 144) {
+ $message = 'Invalid parameter: parameter \'width\''
+ . ' must be an integer not exceeding 144';
+ $exception = new Writer\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+ $width = $dom->createElement('width');
+ $text = $dom->createTextNode($image['width']);
+ $width->appendChild($text);
+ $img->appendChild($width);
+ }
+ if (isset($image['description'])) {
+ if (empty($image['description']) || !is_string($image['description'])) {
+ $message = 'Invalid parameter: parameter \'description\''
+ . ' must be a non-empty string';
+ $exception = new Writer\Exception\InvalidArgumentException($message);
+ if (!$this->ignoreExceptions) {
+ throw $exception;
+ } else {
+ $this->exceptions[] = $exception;
+ return;
+ }
+ }
+ $desc = $dom->createElement('description');
+ $text = $dom->createTextNode($image['description']);
+ $desc->appendChild($text);
+ $img->appendChild($desc);
+ }
+ }
+
+ /**
+ * Set date feed was created
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setDateCreated(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getDateCreated()) {
+ return;
+ }
+ if (!$this->getDataContainer()->getDateModified()) {
+ $this->getDataContainer()->setDateModified(
+ $this->getDataContainer()->getDateCreated()
+ );
+ }
+ }
+
+ /**
+ * Set date feed last build date
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setLastBuildDate(DOMDocument $dom, DOMElement $root)
+ {
+ if (!$this->getDataContainer()->getLastBuildDate()) {
+ return;
+ }
+
+ $lastBuildDate = $dom->createElement('lastBuildDate');
+ $root->appendChild($lastBuildDate);
+ $text = $dom->createTextNode(
+ $this->getDataContainer()->getLastBuildDate()->format(DateTime::RSS)
+ );
+ $lastBuildDate->appendChild($text);
+ }
+
+ /**
+ * Set base URL to feed links
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setBaseUrl(DOMDocument $dom, DOMElement $root)
+ {
+ $baseUrl = $this->getDataContainer()->getBaseUrl();
+ if (!$baseUrl) {
+ return;
+ }
+ $root->setAttribute('xml:base', $baseUrl);
+ }
+
+ /**
+ * Set feed categories
+ *
+ * @param DOMDocument $dom
+ * @param DOMElement $root
+ * @return void
+ */
+ protected function _setCategories(DOMDocument $dom, DOMElement $root)
+ {
+ $categories = $this->getDataContainer()->getCategories();
+ if (!$categories) {
+ return;
+ }
+ foreach ($categories as $cat) {
+ $category = $dom->createElement('category');
+ if (isset($cat['scheme'])) {
+ $category->setAttribute('domain', $cat['scheme']);
+ }
+ $text = $dom->createTextNode($cat['term']);
+ $category->appendChild($text);
+ $root->appendChild($category);
+ }
+ }
+}
diff --git a/library/Zend/Feed/Writer/Renderer/RendererInterface.php b/library/Zend/Feed/Writer/Renderer/RendererInterface.php
new file mode 100755
index 0000000000..b2e0e00a32
--- /dev/null
+++ b/library/Zend/Feed/Writer/Renderer/RendererInterface.php
@@ -0,0 +1,100 @@
+ array(),
+ 'feed' => array(),
+ 'entryRenderer' => array(),
+ 'feedRenderer' => array(),
+ );
+
+ /**
+ * Set plugin loader for use with Extensions
+ *
+ * @param ExtensionManagerInterface
+ */
+ public static function setExtensionManager(ExtensionManagerInterface $extensionManager)
+ {
+ static::$extensionManager = $extensionManager;
+ }
+
+ /**
+ * Get plugin manager for use with Extensions
+ *
+ * @return ExtensionManagerInterface
+ */
+ public static function getExtensionManager()
+ {
+ if (!isset(static::$extensionManager)) {
+ static::setExtensionManager(new ExtensionManager());
+ }
+ return static::$extensionManager;
+ }
+
+ /**
+ * Register an Extension by name
+ *
+ * @param string $name
+ * @return void
+ * @throws Exception\RuntimeException if unable to resolve Extension class
+ */
+ public static function registerExtension($name)
+ {
+ $feedName = $name . '\Feed';
+ $entryName = $name . '\Entry';
+ $feedRendererName = $name . '\Renderer\Feed';
+ $entryRendererName = $name . '\Renderer\Entry';
+ $manager = static::getExtensionManager();
+ if (static::isRegistered($name)) {
+ if ($manager->has($feedName)
+ || $manager->has($entryName)
+ || $manager->has($feedRendererName)
+ || $manager->has($entryRendererName)
+ ) {
+ return;
+ }
+ }
+ if (!$manager->has($feedName)
+ && !$manager->has($entryName)
+ && !$manager->has($feedRendererName)
+ && !$manager->has($entryRendererName)
+ ) {
+ throw new Exception\RuntimeException('Could not load extension: ' . $name
+ . 'using Plugin Loader. Check prefix paths are configured and extension exists.');
+ }
+ if ($manager->has($feedName)) {
+ static::$extensions['feed'][] = $feedName;
+ }
+ if ($manager->has($entryName)) {
+ static::$extensions['entry'][] = $entryName;
+ }
+ if ($manager->has($feedRendererName)) {
+ static::$extensions['feedRenderer'][] = $feedRendererName;
+ }
+ if ($manager->has($entryRendererName)) {
+ static::$extensions['entryRenderer'][] = $entryRendererName;
+ }
+ }
+
+ /**
+ * Is a given named Extension registered?
+ *
+ * @param string $extensionName
+ * @return bool
+ */
+ public static function isRegistered($extensionName)
+ {
+ $feedName = $extensionName . '\Feed';
+ $entryName = $extensionName . '\Entry';
+ $feedRendererName = $extensionName . '\Renderer\Feed';
+ $entryRendererName = $extensionName . '\Renderer\Entry';
+ if (in_array($feedName, static::$extensions['feed'])
+ || in_array($entryName, static::$extensions['entry'])
+ || in_array($feedRendererName, static::$extensions['feedRenderer'])
+ || in_array($entryRendererName, static::$extensions['entryRenderer'])
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get a list of extensions
+ *
+ * @return array
+ */
+ public static function getExtensions()
+ {
+ return static::$extensions;
+ }
+
+ /**
+ * Reset class state to defaults
+ *
+ * @return void
+ */
+ public static function reset()
+ {
+ static::$extensionManager = null;
+ static::$extensions = array(
+ 'entry' => array(),
+ 'feed' => array(),
+ 'entryRenderer' => array(),
+ 'feedRenderer' => array(),
+ );
+ }
+
+ /**
+ * Register core (default) extensions
+ *
+ * @return void
+ */
+ public static function registerCoreExtensions()
+ {
+ static::registerExtension('DublinCore');
+ static::registerExtension('Content');
+ static::registerExtension('Atom');
+ static::registerExtension('Slash');
+ static::registerExtension('WellFormedWeb');
+ static::registerExtension('Threading');
+ static::registerExtension('ITunes');
+ }
+
+ public static function lcfirst($str)
+ {
+ $str[0] = strtolower($str[0]);
+ return $str;
+ }
+}
diff --git a/library/Zend/Feed/composer.json b/library/Zend/Feed/composer.json
new file mode 100755
index 0000000000..0c4d7dc181
--- /dev/null
+++ b/library/Zend/Feed/composer.json
@@ -0,0 +1,41 @@
+{
+ "name": "zendframework/zend-feed",
+ "description": "provides functionality for consuming RSS and Atom feeds",
+ "license": "BSD-3-Clause",
+ "keywords": [
+ "zf2",
+ "feed"
+ ],
+ "homepage": "https://github.com/zendframework/zf2",
+ "autoload": {
+ "psr-0": {
+ "Zend\\Feed\\": ""
+ }
+ },
+ "target-dir": "Zend/Feed",
+ "require": {
+ "php": ">=5.3.23",
+ "zendframework/zend-escaper": "self.version",
+ "zendframework/zend-stdlib": "self.version"
+ },
+ "require-dev": {
+ "zendframework/zend-db": "self.version",
+ "zendframework/zend-cache": "self.version",
+ "zendframework/zend-http": "self.version",
+ "zendframework/zend-servicemanager": "self.version",
+ "zendframework/zend-validator": "self.version"
+ },
+ "suggest": {
+ "zendframework/zend-cache": "Zend\\Cache component",
+ "zendframework/zend-db": "Zend\\Db component",
+ "zendframework/zend-http": "Zend\\Http for PubSubHubbub, and optionally for use with Zend\\Feed\\Reader",
+ "zendframework/zend-servicemanager": "Zend\\ServiceManager component, for default/recommended ExtensionManager implementations",
+ "zendframework/zend-validator": "Zend\\Validator component"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev",
+ "dev-develop": "2.4-dev"
+ }
+ }
+}
diff --git a/library/Zend/File/CONTRIBUTING.md b/library/Zend/File/CONTRIBUTING.md
new file mode 100755
index 0000000000..e77f5d2d5b
--- /dev/null
+++ b/library/Zend/File/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# CONTRIBUTING
+
+Please don't open pull requests against this repository, please use https://github.com/zendframework/zf2.
\ No newline at end of file
diff --git a/library/Zend/File/ClassFileLocator.php b/library/Zend/File/ClassFileLocator.php
new file mode 100755
index 0000000000..9ef7821a60
--- /dev/null
+++ b/library/Zend/File/ClassFileLocator.php
@@ -0,0 +1,160 @@
+setInfoClass('Zend\File\PhpClassFile');
+ }
+
+ /**
+ * Filter for files containing PHP classes, interfaces, or abstracts
+ *
+ * @return bool
+ */
+ public function accept()
+ {
+ $file = $this->getInnerIterator()->current();
+ // If we somehow have something other than an SplFileInfo object, just
+ // return false
+ if (!$file instanceof SplFileInfo) {
+ return false;
+ }
+
+ // If we have a directory, it's not a file, so return false
+ if (!$file->isFile()) {
+ return false;
+ }
+
+ // If not a PHP file, skip
+ if ($file->getBasename('.php') == $file->getBasename()) {
+ return false;
+ }
+
+ $contents = file_get_contents($file->getRealPath());
+ $tokens = token_get_all($contents);
+ $count = count($tokens);
+ $t_trait = defined('T_TRAIT') ? T_TRAIT : -1; // For preserve PHP 5.3 compatibility
+ for ($i = 0; $i < $count; $i++) {
+ $token = $tokens[$i];
+ if (!is_array($token)) {
+ // single character token found; skip
+ $i++;
+ continue;
+ }
+ switch ($token[0]) {
+ case T_NAMESPACE:
+ // Namespace found; grab it for later
+ $namespace = '';
+ for ($i++; $i < $count; $i++) {
+ $token = $tokens[$i];
+ if (is_string($token)) {
+ if (';' === $token) {
+ $saveNamespace = false;
+ break;
+ }
+ if ('{' === $token) {
+ $saveNamespace = true;
+ break;
+ }
+ continue;
+ }
+ list($type, $content, $line) = $token;
+ switch ($type) {
+ case T_STRING:
+ case T_NS_SEPARATOR:
+ $namespace .= $content;
+ break;
+ }
+ }
+ if ($saveNamespace) {
+ $savedNamespace = $namespace;
+ }
+ break;
+ case $t_trait:
+ case T_CLASS:
+ case T_INTERFACE:
+ // Abstract class, class, interface or trait found
+
+ // Get the classname
+ for ($i++; $i < $count; $i++) {
+ $token = $tokens[$i];
+ if (is_string($token)) {
+ continue;
+ }
+ list($type, $content, $line) = $token;
+ if (T_STRING == $type) {
+ // If a classname was found, set it in the object, and
+ // return boolean true (found)
+ if (!isset($namespace) || null === $namespace) {
+ if (isset($saveNamespace) && $saveNamespace) {
+ $namespace = $savedNamespace;
+ } else {
+ $namespace = null;
+ }
+ }
+ $class = (null === $namespace) ? $content : $namespace . '\\' . $content;
+ $file->addClass($class);
+ if ($namespace) {
+ $file->addNamespace($namespace);
+ }
+ $namespace = null;
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ $classes = $file->getClasses();
+ if (!empty($classes)) {
+ return true;
+ }
+ // No class-type tokens found; return false
+ return false;
+ }
+}
diff --git a/library/Zend/File/Exception/BadMethodCallException.php b/library/Zend/File/Exception/BadMethodCallException.php
new file mode 100755
index 0000000000..04be86936a
--- /dev/null
+++ b/library/Zend/File/Exception/BadMethodCallException.php
@@ -0,0 +1,15 @@
+classes;
+ }
+
+ /**
+ * Get namespaces
+ *
+ * @return array
+ */
+ public function getNamespaces()
+ {
+ return $this->namespaces;
+ }
+
+ /**
+ * Add class
+ *
+ * @param string $class
+ * @return self
+ */
+ public function addClass($class)
+ {
+ $this->classes[] = $class;
+ return $this;
+ }
+
+ /**
+ * Add namespace
+ *
+ * @param string $namespace
+ * @return self
+ */
+ public function addNamespace($namespace)
+ {
+ if (in_array($namespace, $this->namespaces)) {
+ return $this;
+ }
+ $this->namespaces[] = $namespace;
+ return $this;
+ }
+}
diff --git a/library/Zend/File/README.md b/library/Zend/File/README.md
new file mode 100755
index 0000000000..8e1ab787f5
--- /dev/null
+++ b/library/Zend/File/README.md
@@ -0,0 +1,15 @@
+File Component from ZF2
+=======================
+
+This is the File component for ZF2.
+
+- File issues at https://github.com/zendframework/zf2/issues
+- Create pull requests against https://github.com/zendframework/zf2
+- Documentation is at http://framework.zend.com/docs
+
+LICENSE
+-------
+
+The files in this archive are released under the [Zend Framework
+license](http://framework.zend.com/license), which is a 3-clause BSD license.
+
diff --git a/library/Zend/File/Transfer/Adapter/AbstractAdapter.php b/library/Zend/File/Transfer/Adapter/AbstractAdapter.php
new file mode 100755
index 0000000000..a46935f69e
--- /dev/null
+++ b/library/Zend/File/Transfer/Adapter/AbstractAdapter.php
@@ -0,0 +1,1504 @@
+ array( - Form is the name within the form or, if not set the filename
+ * name, - Original name of this file
+ * type, - Mime type of this file
+ * size, - Filesize in bytes
+ * tmp_name, - Internally temporary filename for uploaded files
+ * error, - Error which has occurred
+ * destination, - New destination for this file
+ * validators, - Set validator names for this file
+ * files - Set file names for this file
+ * ))
+ *
+ * @var array
+ */
+ protected $files = array();
+
+ /**
+ * TMP directory
+ * @var string
+ */
+ protected $tmpDir;
+
+ /**
+ * Available options for file transfers
+ */
+ protected $options = array(
+ 'ignoreNoFile' => false,
+ 'useByteString' => true,
+ 'magicFile' => null,
+ 'detectInfos' => true,
+ );
+
+ /**
+ * Send file
+ *
+ * @param mixed $options
+ * @return bool
+ */
+ abstract public function send($options = null);
+
+ /**
+ * Receive file
+ *
+ * @param mixed $options
+ * @return bool
+ */
+ abstract public function receive($options = null);
+
+ /**
+ * Is file sent?
+ *
+ * @param array|string|null $files
+ * @return bool
+ */
+ abstract public function isSent($files = null);
+
+ /**
+ * Is file received?
+ *
+ * @param array|string|null $files
+ * @return bool
+ */
+ abstract public function isReceived($files = null);
+
+ /**
+ * Has a file been uploaded ?
+ *
+ * @param array|string|null $files
+ * @return bool
+ */
+ abstract public function isUploaded($files = null);
+
+ /**
+ * Has the file been filtered ?
+ *
+ * @param array|string|null $files
+ * @return bool
+ */
+ abstract public function isFiltered($files = null);
+
+ /**
+ * Adds one or more files
+ *
+ * @param string|array $file File to add
+ * @param string|array $validator Validators to use for this file, must be set before
+ * @param string|array $filter Filters to use for this file, must be set before
+ * @return AbstractAdapter
+ * @throws Exception Not implemented
+ */
+ //abstract public function addFile($file, $validator = null, $filter = null);
+
+ /**
+ * Returns all set types
+ *
+ * @return array List of set types
+ * @throws Exception Not implemented
+ */
+ //abstract public function getType();
+
+ /**
+ * Adds one or more type of files
+ *
+ * @param string|array $type Type of files to add
+ * @param string|array $validator Validators to use for this file, must be set before
+ * @param string|array $filter Filters to use for this file, must be set before
+ * @return AbstractAdapter
+ * @throws Exception Not implemented
+ */
+ //abstract public function addType($type, $validator = null, $filter = null);
+
+ /**
+ * Returns all set files
+ *
+ * @return array List of set files
+ */
+ //abstract public function getFile();
+
+ /**
+ * Set the filter plugin manager instance
+ *
+ * @param FilterPluginManager $filterManager
+ * @return AbstractAdapter
+ */
+ public function setFilterManager(FilterPluginManager $filterManager)
+ {
+ $this->filterManager = $filterManager;
+ return $this;
+ }
+
+ /**
+ * Get the filter plugin manager instance
+ *
+ * @return FilterPluginManager
+ */
+ public function getFilterManager()
+ {
+ if (!$this->filterManager instanceof FilterPluginManager) {
+ $this->setFilterManager(new FilterPluginManager());
+ }
+ return $this->filterManager;
+ }
+
+ /**
+ * Set the validator plugin manager instance
+ *
+ * @param ValidatorPluginManager $validatorManager
+ * @return AbstractAdapter
+ */
+ public function setValidatorManager(ValidatorPluginManager $validatorManager)
+ {
+ $this->validatorManager = $validatorManager;
+ return $this;
+ }
+
+ /**
+ * Get the validator plugin manager instance
+ *
+ * @return ValidatorPluginManager
+ */
+ public function getValidatorManager()
+ {
+ if (!$this->validatorManager instanceof ValidatorPluginManager) {
+ $this->setValidatorManager(new ValidatorPluginManager());
+ }
+ return $this->validatorManager;
+ }
+
+ /**
+ * Adds a new validator for this class
+ *
+ * @param string|Validator\ValidatorInterface $validator Type of validator to add
+ * @param bool $breakChainOnFailure If the validation chain should stop a failure
+ * @param string|array $options Options to set for the validator
+ * @param string|array $files Files to limit this validator to
+ * @return AbstractAdapter
+ * @throws Exception\InvalidArgumentException for invalid type
+ */
+ public function addValidator($validator, $breakChainOnFailure = false, $options = null, $files = null)
+ {
+ if (is_string($validator)) {
+ $validator = $this->getValidatorManager()->get($validator, $options);
+ if (is_array($options) && isset($options['messages'])) {
+ if (is_array($options['messages'])) {
+ $validator->setMessages($options['messages']);
+ } elseif (is_string($options['messages'])) {
+ $validator->setMessage($options['messages']);
+ }
+
+ unset($options['messages']);
+ }
+ }
+
+ if (!$validator instanceof Validator\ValidatorInterface) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid validator provided to addValidator; ' .
+ 'must be string or Zend\Validator\ValidatorInterface'
+ );
+ }
+
+ $name = get_class($validator);
+
+ $this->validators[$name] = $validator;
+ $this->break[$name] = $breakChainOnFailure;
+ $files = $this->getFiles($files, true, true);
+ foreach ($files as $file) {
+ if ($name == 'NotEmpty') {
+ $temp = $this->files[$file]['validators'];
+ $this->files[$file]['validators'] = array($name);
+ $this->files[$file]['validators'] += $temp;
+ } else {
+ $this->files[$file]['validators'][] = $name;
+ }
+
+ $this->files[$file]['validated'] = false;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add Multiple validators at once
+ *
+ * @param array $validators
+ * @param string|array $files
+ * @return AbstractAdapter
+ * @throws Exception\InvalidArgumentException for invalid type
+ */
+ public function addValidators(array $validators, $files = null)
+ {
+ foreach ($validators as $name => $validatorInfo) {
+ if ($validatorInfo instanceof Validator\ValidatorInterface) {
+ $this->addValidator($validatorInfo, null, null, $files);
+ } elseif (is_string($validatorInfo)) {
+ if (!is_int($name)) {
+ $this->addValidator($name, null, $validatorInfo, $files);
+ } else {
+ $this->addValidator($validatorInfo, null, null, $files);
+ }
+ } elseif (is_array($validatorInfo)) {
+ $argc = count($validatorInfo);
+ $breakChainOnFailure = false;
+ $options = array();
+ if (isset($validatorInfo['validator'])) {
+ $validator = $validatorInfo['validator'];
+ if (isset($validatorInfo['breakChainOnFailure'])) {
+ $breakChainOnFailure = $validatorInfo['breakChainOnFailure'];
+ }
+
+ if (isset($validatorInfo['options'])) {
+ $options = $validatorInfo['options'];
+ }
+
+ $this->addValidator($validator, $breakChainOnFailure, $options, $files);
+ } else {
+ if (is_string($name)) {
+ $validator = $name;
+ $options = $validatorInfo;
+ $this->addValidator($validator, $breakChainOnFailure, $options, $files);
+ } else {
+ $file = $files;
+ switch (true) {
+ case (0 == $argc):
+ break;
+ case (1 <= $argc):
+ $validator = array_shift($validatorInfo);
+ case (2 <= $argc):
+ $breakChainOnFailure = array_shift($validatorInfo);
+ case (3 <= $argc):
+ $options = array_shift($validatorInfo);
+ case (4 <= $argc):
+ if (!empty($validatorInfo)) {
+ $file = array_shift($validatorInfo);
+ }
+ default:
+ $this->addValidator($validator, $breakChainOnFailure, $options, $file);
+ break;
+ }
+ }
+ }
+ } else {
+ throw new Exception\InvalidArgumentException('Invalid validator passed to addValidators()');
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets a validator for the class, erasing all previous set
+ *
+ * @param array $validators Validators to set
+ * @param string|array $files Files to limit this validator to
+ * @return AbstractAdapter
+ */
+ public function setValidators(array $validators, $files = null)
+ {
+ $this->clearValidators();
+ return $this->addValidators($validators, $files);
+ }
+
+ /**
+ * Determine if a given validator has already been registered
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function hasValidator($name)
+ {
+ return (false !== $this->getValidatorIdentifier($name));
+ }
+
+ /**
+ * Retrieve individual validator
+ *
+ * @param string $name
+ * @return Validator\ValidatorInterface|null
+ */
+ public function getValidator($name)
+ {
+ if (false === ($identifier = $this->getValidatorIdentifier($name))) {
+ return null;
+ }
+ return $this->validators[$identifier];
+ }
+
+ /**
+ * Returns all set validators
+ *
+ * @param string|array $files (Optional) Returns the validator for this files
+ * @return null|array List of set validators
+ */
+ public function getValidators($files = null)
+ {
+ if ($files == null) {
+ return $this->validators;
+ }
+
+ $files = $this->getFiles($files, true, true);
+ $validators = array();
+ foreach ($files as $file) {
+ if (!empty($this->files[$file]['validators'])) {
+ $validators += $this->files[$file]['validators'];
+ }
+ }
+
+ $validators = array_unique($validators);
+ $result = array();
+ foreach ($validators as $validator) {
+ $result[$validator] = $this->validators[$validator];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Remove an individual validator
+ *
+ * @param string $name
+ * @return AbstractAdapter
+ */
+ public function removeValidator($name)
+ {
+ if (false === ($key = $this->getValidatorIdentifier($name))) {
+ return $this;
+ }
+
+ unset($this->validators[$key]);
+ foreach (array_keys($this->files) as $file) {
+ if (empty($this->files[$file]['validators'])) {
+ continue;
+ }
+
+ $index = array_search($key, $this->files[$file]['validators']);
+ if ($index === false) {
+ continue;
+ }
+
+ unset($this->files[$file]['validators'][$index]);
+ $this->files[$file]['validated'] = false;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Remove all validators
+ *
+ * @return AbstractAdapter
+ */
+ public function clearValidators()
+ {
+ $this->validators = array();
+ foreach (array_keys($this->files) as $file) {
+ $this->files[$file]['validators'] = array();
+ $this->files[$file]['validated'] = false;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets Options for adapters
+ *
+ * @param array $options Options to set
+ * @param array $files (Optional) Files to set the options for
+ * @return AbstractAdapter
+ */
+ public function setOptions($options = array(), $files = null)
+ {
+ $file = $this->getFiles($files, false, true);
+
+ if (is_array($options)) {
+ if (empty($file)) {
+ $this->options = array_merge($this->options, $options);
+ }
+
+ foreach ($options as $name => $value) {
+ foreach ($file as $key => $content) {
+ switch ($name) {
+ case 'magicFile' :
+ $this->files[$key]['options'][$name] = (string) $value;
+ break;
+
+ case 'ignoreNoFile' :
+ case 'useByteString' :
+ case 'detectInfos' :
+ $this->files[$key]['options'][$name] = (bool) $value;
+ break;
+
+ default:
+ continue;
+ }
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns set options for adapters or files
+ *
+ * @param array $files (Optional) Files to return the options for
+ * @return array Options for given files
+ */
+ public function getOptions($files = null)
+ {
+ $file = $this->getFiles($files, false, true);
+
+ foreach ($file as $key => $content) {
+ if (isset($this->files[$key]['options'])) {
+ $options[$key] = $this->files[$key]['options'];
+ } else {
+ $options[$key] = array();
+ }
+ }
+
+ return $options;
+ }
+
+ /**
+ * Checks if the files are valid
+ *
+ * @param string|array $files (Optional) Files to check
+ * @return bool True if all checks are valid
+ */
+ public function isValid($files = null)
+ {
+ $check = $this->getFiles($files, false, true);
+ if (empty($check)) {
+ return false;
+ }
+
+ $translator = $this->getTranslator();
+ $this->messages = array();
+ $break = false;
+ foreach ($check as $content) {
+ if (array_key_exists('validators', $content) &&
+ in_array('Zend\Validator\File\Count', $content['validators'])) {
+ $validator = $this->validators['Zend\Validator\File\Count'];
+ $count = $content;
+ if (empty($content['tmp_name'])) {
+ continue;
+ }
+
+ if (array_key_exists('destination', $content)) {
+ $checkit = $content['destination'];
+ } else {
+ $checkit = dirname($content['tmp_name']);
+ }
+
+ $checkit .= DIRECTORY_SEPARATOR . $content['name'];
+ $validator->addFile($checkit);
+ }
+ }
+
+ if (isset($count)) {
+ if (!$validator->isValid($count['tmp_name'], $count)) {
+ $this->messages += $validator->getMessages();
+ }
+ }
+
+ foreach ($check as $key => $content) {
+ $fileerrors = array();
+ if (array_key_exists('validators', $content) && $content['validated']) {
+ continue;
+ }
+
+ if (array_key_exists('validators', $content)) {
+ foreach ($content['validators'] as $class) {
+ $validator = $this->validators[$class];
+ if (method_exists($validator, 'setTranslator')) {
+ $validator->setTranslator($translator);
+ }
+
+ if (($class === 'Zend\Validator\File\Upload') && (empty($content['tmp_name']))) {
+ $tocheck = $key;
+ } else {
+ $tocheck = $content['tmp_name'];
+ }
+
+ if (!$validator->isValid($tocheck, $content)) {
+ $fileerrors += $validator->getMessages();
+ }
+
+ if (!empty($content['options']['ignoreNoFile']) && (isset($fileerrors['fileUploadErrorNoFile']))) {
+ unset($fileerrors['fileUploadErrorNoFile']);
+ break;
+ }
+
+ if (($class === 'Zend\Validator\File\Upload') && (count($fileerrors) > 0)) {
+ break;
+ }
+
+ if (($this->break[$class]) && (count($fileerrors) > 0)) {
+ $break = true;
+ break;
+ }
+ }
+ }
+
+ if (count($fileerrors) > 0) {
+ $this->files[$key]['validated'] = false;
+ } else {
+ $this->files[$key]['validated'] = true;
+ }
+
+ $this->messages += $fileerrors;
+ if ($break) {
+ break;
+ }
+ }
+
+ if (count($this->messages) > 0) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns found validation messages
+ *
+ * @return array
+ */
+ public function getMessages()
+ {
+ return $this->messages;
+ }
+
+ /**
+ * Retrieve error codes
+ *
+ * @return array
+ */
+ public function getErrors()
+ {
+ return array_keys($this->messages);
+ }
+
+ /**
+ * Are there errors registered?
+ *
+ * @return bool
+ */
+ public function hasErrors()
+ {
+ return (!empty($this->messages));
+ }
+
+ /**
+ * Adds a new filter for this class
+ *
+ * @param string|Filter\FilterInterface $filter Type of filter to add
+ * @param string|array $options Options to set for the filter
+ * @param string|array $files Files to limit this filter to
+ * @return AbstractAdapter
+ * @throws Exception\InvalidArgumentException for invalid type
+ */
+ public function addFilter($filter, $options = null, $files = null)
+ {
+ if (is_string($filter)) {
+ $filter = $this->getFilterManager()->get($filter, $options);
+ }
+
+ if (!$filter instanceof Filter\FilterInterface) {
+ throw new Exception\InvalidArgumentException('Invalid filter specified');
+ }
+
+ $class = get_class($filter);
+ $this->filters[$class] = $filter;
+ $files = $this->getFiles($files, true, true);
+ foreach ($files as $file) {
+ $this->files[$file]['filters'][] = $class;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add Multiple filters at once
+ *
+ * @param array $filters
+ * @param string|array $files
+ * @return AbstractAdapter
+ */
+ public function addFilters(array $filters, $files = null)
+ {
+ foreach ($filters as $key => $spec) {
+ if ($spec instanceof Filter\FilterInterface) {
+ $this->addFilter($spec, null, $files);
+ continue;
+ }
+
+ if (is_string($key)) {
+ $this->addFilter($key, $spec, $files);
+ continue;
+ }
+
+ if (is_int($key)) {
+ if (is_string($spec)) {
+ $this->addFilter($spec, null, $files);
+ continue;
+ }
+
+ if (is_array($spec)) {
+ if (!array_key_exists('filter', $spec)) {
+ continue;
+ }
+
+ $filter = $spec['filter'];
+ unset($spec['filter']);
+ $this->addFilter($filter, $spec, $files);
+ continue;
+ }
+
+ continue;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets a filter for the class, erasing all previous set
+ *
+ * @param array $filters Filter to set
+ * @param string|array $files Files to limit this filter to
+ * @return Filter\AbstractFilter
+ */
+ public function setFilters(array $filters, $files = null)
+ {
+ $this->clearFilters();
+ return $this->addFilters($filters, $files);
+ }
+
+ /**
+ * Determine if a given filter has already been registered
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function hasFilter($name)
+ {
+ return (false !== $this->getFilterIdentifier($name));
+ }
+
+ /**
+ * Retrieve individual filter
+ *
+ * @param string $name
+ * @return Filter\FilterInterface|null
+ */
+ public function getFilter($name)
+ {
+ if (false === ($identifier = $this->getFilterIdentifier($name))) {
+ return null;
+ }
+
+ return $this->filters[$identifier];
+ }
+
+ /**
+ * Returns all set filters
+ *
+ * @param string|array $files (Optional) Returns the filter for this files
+ * @return array List of set filters
+ * @throws Exception\RuntimeException When file not found
+ */
+ public function getFilters($files = null)
+ {
+ if ($files === null) {
+ return $this->filters;
+ }
+
+ $files = $this->getFiles($files, true, true);
+ $filters = array();
+ foreach ($files as $file) {
+ if (!empty($this->files[$file]['filters'])) {
+ $filters += $this->files[$file]['filters'];
+ }
+ }
+
+ $filters = array_unique($filters);
+ $result = array();
+ foreach ($filters as $filter) {
+ $result[] = $this->filters[$filter];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Remove an individual filter
+ *
+ * @param string $name
+ * @return AbstractAdapter
+ */
+ public function removeFilter($name)
+ {
+ if (false === ($key = $this->getFilterIdentifier($name))) {
+ return $this;
+ }
+
+ unset($this->filters[$key]);
+ foreach (array_keys($this->files) as $file) {
+ if (empty($this->files[$file]['filters'])) {
+ continue;
+ }
+
+ $index = array_search($key, $this->files[$file]['filters']);
+ if ($index === false) {
+ continue;
+ }
+
+ unset($this->files[$file]['filters'][$index]);
+ }
+ return $this;
+ }
+
+ /**
+ * Remove all filters
+ *
+ * @return AbstractAdapter
+ */
+ public function clearFilters()
+ {
+ $this->filters = array();
+ foreach (array_keys($this->files) as $file) {
+ $this->files[$file]['filters'] = array();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Retrieves the filename of transferred files.
+ *
+ * @param string $file (Optional) Element to return the filename for
+ * @param bool $path (Optional) Should the path also be returned ?
+ * @return string|array
+ */
+ public function getFileName($file = null, $path = true)
+ {
+ $files = $this->getFiles($file, true, true);
+ $result = array();
+ $directory = "";
+ foreach ($files as $file) {
+ if (empty($this->files[$file]['name'])) {
+ continue;
+ }
+
+ if ($path === true) {
+ $directory = $this->getDestination($file) . DIRECTORY_SEPARATOR;
+ }
+
+ $result[$file] = $directory . $this->files[$file]['name'];
+ }
+
+ if (count($result) == 1) {
+ return current($result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Retrieve additional internal file informations for files
+ *
+ * @param string $file (Optional) File to get informations for
+ * @return array
+ */
+ public function getFileInfo($file = null)
+ {
+ return $this->getFiles($file);
+ }
+
+ /**
+ * Sets a new destination for the given files
+ *
+ * @deprecated Will be changed to be a filter!!!
+ * @param string $destination New destination directory
+ * @param string|array $files Files to set the new destination for
+ * @return AbstractAdapter
+ * @throws Exception\InvalidArgumentException when the given destination is not a directory or does not exist
+ */
+ public function setDestination($destination, $files = null)
+ {
+ $orig = $files;
+ $destination = rtrim($destination, "/\\");
+ if (!is_dir($destination)) {
+ throw new Exception\InvalidArgumentException('The given destination is not a directory or does not exist');
+ }
+
+ if (!is_writable($destination)) {
+ throw new Exception\InvalidArgumentException('The given destination is not writeable');
+ }
+
+ if ($files === null) {
+ foreach ($this->files as $file => $content) {
+ $this->files[$file]['destination'] = $destination;
+ }
+ } else {
+ $files = $this->getFiles($files, true, true);
+ if (empty($files) and is_string($orig)) {
+ $this->files[$orig]['destination'] = $destination;
+ }
+
+ foreach ($files as $file) {
+ $this->files[$file]['destination'] = $destination;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Retrieve destination directory value
+ *
+ * @param null|string|array $files
+ * @throws Exception\InvalidArgumentException
+ * @return null|string|array
+ */
+ public function getDestination($files = null)
+ {
+ $orig = $files;
+ $files = $this->getFiles($files, false, true);
+ $destinations = array();
+ if (empty($files) and is_string($orig)) {
+ if (isset($this->files[$orig]['destination'])) {
+ $destinations[$orig] = $this->files[$orig]['destination'];
+ } else {
+ throw new Exception\InvalidArgumentException(
+ sprintf('The file transfer adapter can not find "%s"', $orig)
+ );
+ }
+ }
+
+ foreach ($files as $key => $content) {
+ if (isset($this->files[$key]['destination'])) {
+ $destinations[$key] = $this->files[$key]['destination'];
+ } else {
+ $tmpdir = $this->getTmpDir();
+ $this->setDestination($tmpdir, $key);
+ $destinations[$key] = $tmpdir;
+ }
+ }
+
+ if (empty($destinations)) {
+ $destinations = $this->getTmpDir();
+ } elseif (count($destinations) == 1) {
+ $destinations = current($destinations);
+ }
+
+ return $destinations;
+ }
+
+ /**
+ * Sets translator to use in helper
+ *
+ * @param Translator $translator [optional] translator.
+ * Default is null, which sets no translator.
+ * @param string $textDomain [optional] text domain
+ * Default is null, which skips setTranslatorTextDomain
+ * @return AbstractAdapter
+ */
+ public function setTranslator(Translator $translator = null, $textDomain = null)
+ {
+ $this->translator = $translator;
+ if (null !== $textDomain) {
+ $this->setTranslatorTextDomain($textDomain);
+ }
+ return $this;
+ }
+
+ /**
+ * Retrieve localization translator object
+ *
+ * @return Translator|null
+ */
+ public function getTranslator()
+ {
+ if ($this->isTranslatorEnabled()) {
+ return null;
+ }
+
+ return $this->translator;
+ }
+
+ /**
+ * Checks if the helper has a translator
+ *
+ * @return bool
+ */
+ public function hasTranslator()
+ {
+ return (bool) $this->getTranslator();
+ }
+
+ /**
+ * Indicate whether or not translation should be enabled
+ *
+ * @param bool $flag
+ * @return AbstractAdapter
+ */
+ public function setTranslatorEnabled($flag = true)
+ {
+ $this->translatorEnabled = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Is translation enabled?
+ *
+ * @return bool
+ */
+ public function isTranslatorEnabled()
+ {
+ return $this->translatorEnabled;
+ }
+
+ /**
+ * Set translation text domain
+ *
+ * @param string $textDomain
+ * @return AbstractAdapter
+ */
+ public function setTranslatorTextDomain($textDomain = 'default')
+ {
+ $this->translatorTextDomain = $textDomain;
+ return $this;
+ }
+
+ /**
+ * Return the translation text domain
+ *
+ * @return string
+ */
+ public function getTranslatorTextDomain()
+ {
+ return $this->translatorTextDomain;
+ }
+
+ /**
+ * Returns the hash for a given file
+ *
+ * @param string $hash Hash algorithm to use
+ * @param string|array $files Files to return the hash for
+ * @return string|array Hashstring
+ * @throws Exception\InvalidArgumentException On unknown hash algorithm
+ */
+ public function getHash($hash = 'crc32', $files = null)
+ {
+ if (!in_array($hash, hash_algos())) {
+ throw new Exception\InvalidArgumentException('Unknown hash algorithm');
+ }
+
+ $files = $this->getFiles($files);
+ $result = array();
+ foreach ($files as $key => $value) {
+ if (file_exists($value['name'])) {
+ $result[$key] = hash_file($hash, $value['name']);
+ } elseif (file_exists($value['tmp_name'])) {
+ $result[$key] = hash_file($hash, $value['tmp_name']);
+ } elseif (empty($value['options']['ignoreNoFile'])) {
+ throw new Exception\InvalidArgumentException("The file '{$value['name']}' does not exist");
+ }
+ }
+
+ if (count($result) == 1) {
+ return current($result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns the real filesize of the file
+ *
+ * @param string|array $files Files to get the filesize from
+ * @return string|array Filesize
+ * @throws Exception\InvalidArgumentException When the file does not exist
+ */
+ public function getFileSize($files = null)
+ {
+ $files = $this->getFiles($files);
+ $result = array();
+ foreach ($files as $key => $value) {
+ if (file_exists($value['name']) || file_exists($value['tmp_name'])) {
+ if ($value['options']['useByteString']) {
+ $result[$key] = static::toByteString($value['size']);
+ } else {
+ $result[$key] = $value['size'];
+ }
+ } elseif (empty($value['options']['ignoreNoFile'])) {
+ throw new Exception\InvalidArgumentException("The file '{$value['name']}' does not exist");
+ } else {
+ continue;
+ }
+ }
+
+ if (count($result) == 1) {
+ return current($result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to detect the size of a file
+ *
+ * @param array $value File infos
+ * @return string Filesize of given file
+ */
+ protected function detectFileSize($value)
+ {
+ if (file_exists($value['name'])) {
+ $filename = $value['name'];
+ } elseif (file_exists($value['tmp_name'])) {
+ $filename = $value['tmp_name'];
+ } else {
+ return null;
+ }
+
+ ErrorHandler::start();
+ $filesize = filesize($filename);
+ $return = ErrorHandler::stop();
+ if ($return instanceof ErrorException) {
+ $filesize = 0;
+ }
+
+ return sprintf("%u", $filesize);
+ }
+
+ /**
+ * Returns the real mimetype of the file
+ * Uses fileinfo, when not available mime_magic and as last fallback a manual given mimetype
+ *
+ * @param string|array $files Files to get the mimetype from
+ * @return string|array MimeType
+ * @throws Exception\InvalidArgumentException When the file does not exist
+ */
+ public function getMimeType($files = null)
+ {
+ $files = $this->getFiles($files);
+ $result = array();
+ foreach ($files as $key => $value) {
+ if (file_exists($value['name']) || file_exists($value['tmp_name'])) {
+ $result[$key] = $value['type'];
+ } elseif (empty($value['options']['ignoreNoFile'])) {
+ throw new Exception\InvalidArgumentException("the file '{$value['name']}' does not exist");
+ } else {
+ continue;
+ }
+ }
+
+ if (count($result) == 1) {
+ return current($result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to detect the mime type of a file
+ *
+ * @param array $value File infos
+ * @return string Mimetype of given file
+ */
+ protected function detectMimeType($value)
+ {
+ if (file_exists($value['name'])) {
+ $file = $value['name'];
+ } elseif (file_exists($value['tmp_name'])) {
+ $file = $value['tmp_name'];
+ } else {
+ return null;
+ }
+
+ if (class_exists('finfo', false)) {
+ if (!empty($value['options']['magicFile'])) {
+ ErrorHandler::start();
+ $mime = finfo_open(FILEINFO_MIME_TYPE, $value['options']['magicFile']);
+ ErrorHandler::stop();
+ }
+
+ if (empty($mime)) {
+ ErrorHandler::start();
+ $mime = finfo_open(FILEINFO_MIME_TYPE);
+ ErrorHandler::stop();
+ }
+
+ if (!empty($mime)) {
+ $result = finfo_file($mime, $file);
+ }
+
+ unset($mime);
+ }
+
+ if (empty($result) && (function_exists('mime_content_type')
+ && ini_get('mime_magic.magicfile'))) {
+ $result = mime_content_type($file);
+ }
+
+ if (empty($result)) {
+ $result = 'application/octet-stream';
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns the formatted size
+ *
+ * @param int $size
+ * @return string
+ */
+ protected static function toByteString($size)
+ {
+ $sizes = array('B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
+ for ($i=0; $size >= 1024 && $i < 9; $i++) {
+ $size /= 1024;
+ }
+
+ return round($size, 2) . $sizes[$i];
+ }
+
+ /**
+ * Internal function to filter all given files
+ *
+ * @param string|array $files (Optional) Files to check
+ * @return bool False on error
+ */
+ protected function filter($files = null)
+ {
+ $check = $this->getFiles($files);
+ foreach ($check as $name => $content) {
+ if (array_key_exists('filters', $content)) {
+ foreach ($content['filters'] as $class) {
+ $filter = $this->filters[$class];
+ try {
+ $result = $filter->filter($this->getFileName($name));
+
+ $this->files[$name]['destination'] = dirname($result);
+ $this->files[$name]['name'] = basename($result);
+ } catch (FilterException\ExceptionInterface $e) {
+ $this->messages += array($e->getMessage());
+ }
+ }
+ }
+ }
+
+ if (count($this->messages) > 0) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Determine system TMP directory and detect if we have read access
+ *
+ * @return string
+ * @throws Exception\RuntimeException if unable to determine directory
+ */
+ protected function getTmpDir()
+ {
+ if (null === $this->tmpDir) {
+ $tmpdir = array();
+ if (function_exists('sys_get_temp_dir')) {
+ $tmpdir[] = sys_get_temp_dir();
+ }
+
+ if (!empty($_ENV['TMP'])) {
+ $tmpdir[] = realpath($_ENV['TMP']);
+ }
+
+ if (!empty($_ENV['TMPDIR'])) {
+ $tmpdir[] = realpath($_ENV['TMPDIR']);
+ }
+
+ if (!empty($_ENV['TEMP'])) {
+ $tmpdir[] = realpath($_ENV['TEMP']);
+ }
+
+ $upload = ini_get('upload_tmp_dir');
+ if ($upload) {
+ $tmpdir[] = realpath($upload);
+ }
+
+ foreach ($tmpdir as $directory) {
+ if ($this->isPathWriteable($directory)) {
+ $this->tmpDir = $directory;
+ }
+ }
+
+ if (empty($this->tmpDir)) {
+ // Attemp to detect by creating a temporary file
+ $tempFile = tempnam(md5(uniqid(rand(), true)), '');
+ if ($tempFile) {
+ $this->tmpDir = realpath(dirname($tempFile));
+ unlink($tempFile);
+ } else {
+ throw new Exception\RuntimeException('Could not determine a temporary directory');
+ }
+ }
+
+ $this->tmpDir = rtrim($this->tmpDir, "/\\");
+ }
+ return $this->tmpDir;
+ }
+
+ /**
+ * Tries to detect if we can read and write to the given path
+ *
+ * @param string $path
+ * @return bool
+ */
+ protected function isPathWriteable($path)
+ {
+ $tempFile = rtrim($path, "/\\");
+ $tempFile .= '/' . 'test.1';
+
+ ErrorHandler::start();
+ $result = file_put_contents($tempFile, 'TEST');
+ ErrorHandler::stop();
+
+ if ($result == false) {
+ return false;
+ }
+
+ ErrorHandler::start();
+ $result = unlink($tempFile);
+ ErrorHandler::stop();
+
+ if ($result == false) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns found files based on internal file array and given files
+ *
+ * @param string|array $files (Optional) Files to return
+ * @param bool $names (Optional) Returns only names on true, else complete info
+ * @param bool $noexception (Optional) Allows throwing an exception, otherwise returns an empty array
+ * @return array Found files
+ * @throws Exception\RuntimeException On false filename
+ */
+ protected function getFiles($files, $names = false, $noexception = false)
+ {
+ $check = array();
+
+ if (is_string($files)) {
+ $files = array($files);
+ }
+
+ if (is_array($files)) {
+ foreach ($files as $find) {
+ $found = array();
+ foreach ($this->files as $file => $content) {
+ if (!isset($content['name'])) {
+ continue;
+ }
+
+ if (($content['name'] === $find) && isset($content['multifiles'])) {
+ foreach ($content['multifiles'] as $multifile) {
+ $found[] = $multifile;
+ }
+ break;
+ }
+
+ if ($file === $find) {
+ $found[] = $file;
+ break;
+ }
+
+ if ($content['name'] === $find) {
+ $found[] = $file;
+ break;
+ }
+ }
+
+ if (empty($found)) {
+ if ($noexception !== false) {
+ return array();
+ }
+
+ throw new Exception\RuntimeException(sprintf('The file transfer adapter can not find "%s"', $find));
+ }
+
+ foreach ($found as $checked) {
+ $check[$checked] = $this->files[$checked];
+ }
+ }
+ }
+
+ if ($files === null) {
+ $check = $this->files;
+ $keys = array_keys($check);
+ foreach ($keys as $key) {
+ if (isset($check[$key]['multifiles'])) {
+ unset($check[$key]);
+ }
+ }
+ }
+
+ if ($names) {
+ $check = array_keys($check);
+ }
+
+ return $check;
+ }
+
+ /**
+ * Retrieve internal identifier for a named validator
+ *
+ * @param string $name
+ * @return string
+ */
+ protected function getValidatorIdentifier($name)
+ {
+ if (array_key_exists($name, $this->validators)) {
+ return $name;
+ }
+
+ foreach (array_keys($this->validators) as $test) {
+ if (preg_match('/' . preg_quote($name) . '$/i', $test)) {
+ return $test;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Retrieve internal identifier for a named filter
+ *
+ * @param string $name
+ * @return string
+ */
+ protected function getFilterIdentifier($name)
+ {
+ if (array_key_exists($name, $this->filters)) {
+ return $name;
+ }
+
+ foreach (array_keys($this->filters) as $test) {
+ if (preg_match('/' . preg_quote($name) . '$/i', $test)) {
+ return $test;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/library/Zend/File/Transfer/Adapter/FilterPluginManager.php b/library/Zend/File/Transfer/Adapter/FilterPluginManager.php
new file mode 100755
index 0000000000..28da7ee4a0
--- /dev/null
+++ b/library/Zend/File/Transfer/Adapter/FilterPluginManager.php
@@ -0,0 +1,35 @@
+ 'filedecrypt',
+ 'encrypt' => 'fileencrypt',
+ 'lowercase' => 'filelowercase',
+ 'rename' => 'filerename',
+ 'uppercase' => 'fileuppercase',
+ );
+}
diff --git a/library/Zend/File/Transfer/Adapter/Http.php b/library/Zend/File/Transfer/Adapter/Http.php
new file mode 100755
index 0000000000..3cba2184d5
--- /dev/null
+++ b/library/Zend/File/Transfer/Adapter/Http.php
@@ -0,0 +1,464 @@
+setOptions($options);
+ $this->prepareFiles();
+ $this->addValidator('Upload', false, $this->files);
+ }
+
+ /**
+ * Sets a validator for the class, erasing all previous set
+ *
+ * @param array $validators Validator to set
+ * @param string|array $files Files to limit this validator to
+ * @return AbstractAdapter
+ */
+ public function setValidators(array $validators, $files = null)
+ {
+ $this->clearValidators();
+ return $this->addValidators($validators, $files);
+ }
+
+ /**
+ * Remove an individual validator
+ *
+ * @param string $name
+ * @return AbstractAdapter
+ */
+ public function removeValidator($name)
+ {
+ if ($name == 'Upload') {
+ return $this;
+ }
+
+ return parent::removeValidator($name);
+ }
+
+ /**
+ * Clear the validators
+ *
+ * @return AbstractAdapter
+ */
+ public function clearValidators()
+ {
+ parent::clearValidators();
+ $this->addValidator('Upload', false, $this->files);
+
+ return $this;
+ }
+
+ /**
+ * Send the file to the client (Download)
+ *
+ * @param string|array $options Options for the file(s) to send
+ * @return void
+ * @throws Exception\BadMethodCallException Not implemented
+ */
+ public function send($options = null)
+ {
+ throw new Exception\BadMethodCallException('Method not implemented');
+ }
+
+ /**
+ * Checks if the files are valid
+ *
+ * @param string|array $files (Optional) Files to check
+ * @return bool True if all checks are valid
+ */
+ public function isValid($files = null)
+ {
+ // Workaround for WebServer not conforming HTTP and omitting CONTENT_LENGTH
+ $content = 0;
+ if (isset($_SERVER['CONTENT_LENGTH'])) {
+ $content = $_SERVER['CONTENT_LENGTH'];
+ } elseif (!empty($_POST)) {
+ $content = serialize($_POST);
+ }
+
+ // Workaround for a PHP error returning empty $_FILES when form data exceeds php settings
+ if (empty($this->files) && ($content > 0)) {
+ if (is_array($files)) {
+ $files = current($files);
+ }
+
+ $temp = array($files => array(
+ 'name' => $files,
+ 'error' => 1));
+ $validator = $this->validators['Zend\Validator\File\Upload'];
+ $validator->setTranslator($this->getTranslator())
+ ->setFiles($temp)
+ ->isValid($files, null);
+ $this->messages += $validator->getMessages();
+ return false;
+ }
+
+ return parent::isValid($files);
+ }
+
+ /**
+ * Receive the file from the client (Upload)
+ *
+ * @param string|array $files (Optional) Files to receive
+ * @return bool
+ */
+ public function receive($files = null)
+ {
+ if (!$this->isValid($files)) {
+ return false;
+ }
+
+ $check = $this->getFiles($files);
+ foreach ($check as $file => $content) {
+ if (!$content['received']) {
+ $directory = '';
+ $destination = $this->getDestination($file);
+ if ($destination !== null) {
+ $directory = $destination . DIRECTORY_SEPARATOR;
+ }
+
+ $filename = $directory . $content['name'];
+ $rename = $this->getFilter('Rename');
+ if ($rename !== null) {
+ $tmp = $rename->getNewName($content['tmp_name']);
+ if ($tmp != $content['tmp_name']) {
+ $filename = $tmp;
+ }
+
+ if (dirname($filename) == '.') {
+ $filename = $directory . $filename;
+ }
+
+ $key = array_search(get_class($rename), $this->files[$file]['filters']);
+ unset($this->files[$file]['filters'][$key]);
+ }
+
+ // Should never return false when it's tested by the upload validator
+ if (!move_uploaded_file($content['tmp_name'], $filename)) {
+ if ($content['options']['ignoreNoFile']) {
+ $this->files[$file]['received'] = true;
+ $this->files[$file]['filtered'] = true;
+ continue;
+ }
+
+ $this->files[$file]['received'] = false;
+ return false;
+ }
+
+ if ($rename !== null) {
+ $this->files[$file]['destination'] = dirname($filename);
+ $this->files[$file]['name'] = basename($filename);
+ }
+
+ $this->files[$file]['tmp_name'] = $filename;
+ $this->files[$file]['received'] = true;
+ }
+
+ if (!$content['filtered']) {
+ if (!$this->filter($file)) {
+ $this->files[$file]['filtered'] = false;
+ return false;
+ }
+
+ $this->files[$file]['filtered'] = true;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if the file was already sent
+ *
+ * @param string|array $files Files to check
+ * @return bool
+ * @throws Exception\BadMethodCallException Not implemented
+ */
+ public function isSent($files = null)
+ {
+ throw new Exception\BadMethodCallException('Method not implemented');
+ }
+
+ /**
+ * Checks if the file was already received
+ *
+ * @param string|array $files (Optional) Files to check
+ * @return bool
+ */
+ public function isReceived($files = null)
+ {
+ $files = $this->getFiles($files, false, true);
+ if (empty($files)) {
+ return false;
+ }
+
+ foreach ($files as $content) {
+ if ($content['received'] !== true) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if the file was already filtered
+ *
+ * @param string|array $files (Optional) Files to check
+ * @return bool
+ */
+ public function isFiltered($files = null)
+ {
+ $files = $this->getFiles($files, false, true);
+ if (empty($files)) {
+ return false;
+ }
+
+ foreach ($files as $content) {
+ if ($content['filtered'] !== true) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Has a file been uploaded ?
+ *
+ * @param array|string|null $files
+ * @return bool
+ */
+ public function isUploaded($files = null)
+ {
+ $files = $this->getFiles($files, false, true);
+ if (empty($files)) {
+ return false;
+ }
+
+ foreach ($files as $file) {
+ if (empty($file['name'])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the actual progress of file up-/downloads
+ *
+ * @param string|array $id The upload to get the progress for
+ * @return array|null
+ * @throws Exception\PhpEnvironmentException whether APC nor UploadProgress extension installed
+ * @throws Exception\RuntimeException
+ */
+ public static function getProgress($id = null)
+ {
+ if (!static::isApcAvailable() && !static::isUploadProgressAvailable()) {
+ throw new Exception\PhpEnvironmentException('Neither APC nor UploadProgress extension installed');
+ }
+
+ $session = 'Zend\File\Transfer\Adapter\Http\ProgressBar';
+ $status = array(
+ 'total' => 0,
+ 'current' => 0,
+ 'rate' => 0,
+ 'message' => '',
+ 'done' => false
+ );
+
+ if (is_array($id)) {
+ if (isset($id['progress'])) {
+ $adapter = $id['progress'];
+ }
+
+ if (isset($id['session'])) {
+ $session = $id['session'];
+ }
+
+ if (isset($id['id'])) {
+ $id = $id['id'];
+ } else {
+ unset($id);
+ }
+ }
+
+ if (!empty($id) && (($id instanceof Adapter\AbstractAdapter) || ($id instanceof ProgressBar\ProgressBar))) {
+ $adapter = $id;
+ unset($id);
+ }
+
+ if (empty($id)) {
+ if (!isset($_GET['progress_key'])) {
+ $status['message'] = 'No upload in progress';
+ $status['done'] = true;
+ } else {
+ $id = $_GET['progress_key'];
+ }
+ }
+
+ if (!empty($id)) {
+ if (static::isApcAvailable()) {
+ $call = call_user_func(static::$callbackApc, ini_get('apc.rfc1867_prefix') . $id);
+ if (is_array($call)) {
+ $status = $call + $status;
+ }
+ } elseif (static::isUploadProgressAvailable()) {
+ $call = call_user_func(static::$callbackUploadProgress, $id);
+ if (is_array($call)) {
+ $status = $call + $status;
+ $status['total'] = $status['bytes_total'];
+ $status['current'] = $status['bytes_uploaded'];
+ $status['rate'] = $status['speed_average'];
+ if ($status['total'] == $status['current']) {
+ $status['done'] = true;
+ }
+ }
+ }
+
+ if (!is_array($call)) {
+ $status['done'] = true;
+ $status['message'] = 'Failure while retrieving the upload progress';
+ } elseif (!empty($status['cancel_upload'])) {
+ $status['done'] = true;
+ $status['message'] = 'The upload has been canceled';
+ } else {
+ $status['message'] = static::toByteString($status['current']) . " - " . static::toByteString($status['total']);
+ }
+
+ $status['id'] = $id;
+ }
+
+ if (isset($adapter) && isset($status['id'])) {
+ if ($adapter instanceof Adapter\AbstractAdapter) {
+ $adapter = new ProgressBar\ProgressBar($adapter, 0, $status['total'], $session);
+ }
+
+ if (!($adapter instanceof ProgressBar\ProgressBar)) {
+ throw new Exception\RuntimeException('Unknown Adapter given');
+ }
+
+ if ($status['done']) {
+ $adapter->finish();
+ } else {
+ $adapter->update($status['current'], $status['message']);
+ }
+
+ $status['progress'] = $adapter;
+ }
+
+ return $status;
+ }
+
+ /**
+ * Checks the APC extension for progress information
+ *
+ * @return bool
+ */
+ public static function isApcAvailable()
+ {
+ return (bool) ini_get('apc.enabled') && (bool) ini_get('apc.rfc1867') && is_callable(static::$callbackApc);
+ }
+
+ /**
+ * Checks the UploadProgress extension for progress information
+ *
+ * @return bool
+ */
+ public static function isUploadProgressAvailable()
+ {
+ return is_callable(static::$callbackUploadProgress);
+ }
+
+ /**
+ * Prepare the $_FILES array to match the internal syntax of one file per entry
+ *
+ * @return Http
+ */
+ protected function prepareFiles()
+ {
+ $this->files = array();
+ foreach ($_FILES as $form => $content) {
+ if (is_array($content['name'])) {
+ foreach ($content as $param => $file) {
+ foreach ($file as $number => $target) {
+ $this->files[$form . '_' . $number . '_'][$param] = $target;
+ $this->files[$form]['multifiles'][$number] = $form . '_' . $number . '_';
+ }
+ }
+
+ $this->files[$form]['name'] = $form;
+ foreach ($this->files[$form]['multifiles'] as $key => $value) {
+ $this->files[$value]['options'] = $this->options;
+ $this->files[$value]['validated'] = false;
+ $this->files[$value]['received'] = false;
+ $this->files[$value]['filtered'] = false;
+
+ $mimetype = $this->detectMimeType($this->files[$value]);
+ $this->files[$value]['type'] = $mimetype;
+
+ $filesize = $this->detectFileSize($this->files[$value]);
+ $this->files[$value]['size'] = $filesize;
+
+ if ($this->options['detectInfos']) {
+ $_FILES[$form]['type'][$key] = $mimetype;
+ $_FILES[$form]['size'][$key] = $filesize;
+ }
+ }
+ } else {
+ $this->files[$form] = $content;
+ $this->files[$form]['options'] = $this->options;
+ $this->files[$form]['validated'] = false;
+ $this->files[$form]['received'] = false;
+ $this->files[$form]['filtered'] = false;
+
+ $mimetype = $this->detectMimeType($this->files[$form]);
+ $this->files[$form]['type'] = $mimetype;
+
+ $filesize = $this->detectFileSize($this->files[$form]);
+ $this->files[$form]['size'] = $filesize;
+
+ if ($this->options['detectInfos']) {
+ $_FILES[$form]['type'] = $mimetype;
+ $_FILES[$form]['size'] = $filesize;
+ }
+ }
+ }
+
+ return $this;
+ }
+}
diff --git a/library/Zend/File/Transfer/Adapter/ValidatorPluginManager.php b/library/Zend/File/Transfer/Adapter/ValidatorPluginManager.php
new file mode 100755
index 0000000000..f24f5d459c
--- /dev/null
+++ b/library/Zend/File/Transfer/Adapter/ValidatorPluginManager.php
@@ -0,0 +1,36 @@
+ 'filecount',
+ 'crc32' => 'filecrc32',
+ 'excludeextension' => 'fileexcludeextension',
+ 'excludemimetype' => 'fileexcludemimetype',
+ 'exists' => 'fileexists',
+ 'extension' => 'fileextension',
+ 'filessize' => 'filefilessize',
+ 'hash' => 'filehash',
+ 'imagesize' => 'fileimagesize',
+ 'iscompressed' => 'fileiscompressed',
+ 'isimage' => 'fileisimage',
+ 'md5' => 'filemd5',
+ 'mimetype' => 'filemimetype',
+ 'notexists' => 'filenotexists',
+ 'sha1' => 'filesha1',
+ 'size' => 'filesize',
+ 'upload' => 'fileupload',
+ 'wordcount' => 'filewordcount',
+ );
+}
diff --git a/library/Zend/File/Transfer/Exception/BadMethodCallException.php b/library/Zend/File/Transfer/Exception/BadMethodCallException.php
new file mode 100755
index 0000000000..0d47a905b6
--- /dev/null
+++ b/library/Zend/File/Transfer/Exception/BadMethodCallException.php
@@ -0,0 +1,17 @@
+setAdapter($adapter, $direction, $options);
+ }
+
+ /**
+ * Sets a new adapter
+ *
+ * @param string $adapter Adapter to use
+ * @param bool $direction OPTIONAL False means Download, true means upload
+ * @param array $options OPTIONAL Options to set for this adapter
+ * @return Transfer
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setAdapter($adapter, $direction = false, $options = array())
+ {
+ if (!is_string($adapter)) {
+ throw new Exception\InvalidArgumentException('Adapter must be a string');
+ }
+
+ if ($adapter[0] != '\\') {
+ $adapter = '\Zend\File\Transfer\Adapter\\' . ucfirst($adapter);
+ }
+
+ $direction = (int) $direction;
+ $this->adapter[$direction] = new $adapter($options);
+ if (!$this->adapter[$direction] instanceof Adapter\AbstractAdapter) {
+ throw new Exception\InvalidArgumentException(
+ 'Adapter ' . $adapter . ' does not extend Zend\File\Transfer\Adapter\AbstractAdapter'
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns all set adapters
+ *
+ * @param bool $direction On null, all directions are returned
+ * On false, download direction is returned
+ * On true, upload direction is returned
+ * @return array|Adapter\AbstractAdapter
+ */
+ public function getAdapter($direction = null)
+ {
+ if ($direction === null) {
+ return $this->adapter;
+ }
+
+ $direction = (int) $direction;
+ return $this->adapter[$direction];
+ }
+
+ /**
+ * Calls all methods from the adapter
+ *
+ * @param string $method Method to call
+ * @param array $options Options for this method
+ * @throws Exception\BadMethodCallException if unknown method
+ * @return mixed
+ */
+ public function __call($method, array $options)
+ {
+ if (array_key_exists('direction', $options)) {
+ $direction = (int) $options['direction'];
+ } else {
+ $direction = 0;
+ }
+
+ if (method_exists($this->adapter[$direction], $method)) {
+ return call_user_func_array(array($this->adapter[$direction], $method), $options);
+ }
+
+ throw new Exception\BadMethodCallException("Unknown method '" . $method . "' called!");
+ }
+}
diff --git a/library/Zend/File/composer.json b/library/Zend/File/composer.json
new file mode 100755
index 0000000000..d3d86cea94
--- /dev/null
+++ b/library/Zend/File/composer.json
@@ -0,0 +1,36 @@
+{
+ "name": "zendframework/zend-file",
+ "description": " ",
+ "license": "BSD-3-Clause",
+ "keywords": [
+ "zf2",
+ "file"
+ ],
+ "homepage": "https://github.com/zendframework/zf2",
+ "autoload": {
+ "psr-0": {
+ "Zend\\File\\": ""
+ }
+ },
+ "target-dir": "Zend/File",
+ "require": {
+ "php": ">=5.3.23",
+ "zendframework/zend-stdlib": "self.version"
+ },
+ "require-dev": {
+ "zendframework/zend-filter": "self.version",
+ "zendframework/zend-i18n": "self.version",
+ "zendframework/zend-validator": "self.version"
+ },
+ "suggest": {
+ "zendframework/zend-filter": "Zend\\Filter component",
+ "zendframework/zend-i18n": "Zend\\I18n component",
+ "zendframework/zend-validator": "Zend\\Validator component"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev",
+ "dev-develop": "2.4-dev"
+ }
+ }
+}
diff --git a/library/Zend/Filter/AbstractFilter.php b/library/Zend/Filter/AbstractFilter.php
new file mode 100755
index 0000000000..a52bcfa739
--- /dev/null
+++ b/library/Zend/Filter/AbstractFilter.php
@@ -0,0 +1,96 @@
+ $value) {
+ $setter = 'set' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key)));
+ if (method_exists($this, $setter)) {
+ $this->{$setter}($value);
+ } elseif (array_key_exists($key, $this->options)) {
+ $this->options[$key] = $value;
+ } else {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'The option "%s" does not have a matching %s setter method or options[%s] array key',
+ $key, $setter, $key
+ ));
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Retrieve options representing object state
+ *
+ * @return array
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * Invoke filter as a command
+ *
+ * Proxies to {@link filter()}
+ *
+ * @param mixed $value
+ * @throws Exception\ExceptionInterface If filtering $value is impossible
+ * @return mixed
+ */
+ public function __invoke($value)
+ {
+ return $this->filter($value);
+ }
+
+ /**
+ * @param mixed $options
+ * @return bool
+ */
+ protected static function isOptions($options)
+ {
+ return (is_array($options) || $options instanceof Traversable);
+ }
+}
diff --git a/library/Zend/Filter/AbstractUnicode.php b/library/Zend/Filter/AbstractUnicode.php
new file mode 100755
index 0000000000..685608f7d7
--- /dev/null
+++ b/library/Zend/Filter/AbstractUnicode.php
@@ -0,0 +1,59 @@
+options['encoding'] = $encoding;
+ return $this;
+ }
+
+ /**
+ * Returns the set encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ if ($this->options['encoding'] === null && function_exists('mb_internal_encoding')) {
+ $this->options['encoding'] = mb_internal_encoding();
+ }
+
+ return $this->options['encoding'];
+ }
+}
diff --git a/library/Zend/Filter/BaseName.php b/library/Zend/Filter/BaseName.php
new file mode 100755
index 0000000000..48807ca415
--- /dev/null
+++ b/library/Zend/Filter/BaseName.php
@@ -0,0 +1,33 @@
+ 'boolean',
+ self::TYPE_INTEGER => 'integer',
+ self::TYPE_FLOAT => 'float',
+ self::TYPE_STRING => 'string',
+ self::TYPE_ZERO_STRING => 'zero',
+ self::TYPE_EMPTY_ARRAY => 'array',
+ self::TYPE_NULL => 'null',
+ self::TYPE_PHP => 'php',
+ self::TYPE_FALSE_STRING => 'false',
+ self::TYPE_LOCALIZED => 'localized',
+ self::TYPE_ALL => 'all',
+ );
+
+ /**
+ * @var array
+ */
+ protected $options = array(
+ 'type' => self::TYPE_PHP,
+ 'casting' => true,
+ 'translations' => array(),
+ );
+
+ /**
+ * Constructor
+ *
+ * @param array|Traversable|int|null $typeOrOptions
+ * @param bool $casting
+ * @param array $translations
+ */
+ public function __construct($typeOrOptions = null, $casting = true, $translations = array())
+ {
+ if ($typeOrOptions !== null) {
+ if ($typeOrOptions instanceof Traversable) {
+ $typeOrOptions = ArrayUtils::iteratorToArray($typeOrOptions);
+ }
+
+ if (is_array($typeOrOptions)) {
+ if (isset($typeOrOptions['type'])
+ || isset($typeOrOptions['casting'])
+ || isset($typeOrOptions['translations'])
+ ) {
+ $this->setOptions($typeOrOptions);
+ } else {
+ $this->setType($typeOrOptions);
+ $this->setCasting($casting);
+ $this->setTranslations($translations);
+ }
+ } else {
+ $this->setType($typeOrOptions);
+ $this->setCasting($casting);
+ $this->setTranslations($translations);
+ }
+ }
+ }
+
+ /**
+ * Set boolean types
+ *
+ * @param int|array $type
+ * @throws Exception\InvalidArgumentException
+ * @return self
+ */
+ public function setType($type = null)
+ {
+ if (is_array($type)) {
+ $detected = 0;
+ foreach ($type as $value) {
+ if (is_int($value)) {
+ $detected += $value;
+ } elseif (in_array($value, $this->constants)) {
+ $detected += array_search($value, $this->constants);
+ }
+ }
+
+ $type = $detected;
+ } elseif (is_string($type) && in_array($type, $this->constants)) {
+ $type = array_search($type, $this->constants);
+ }
+
+ if (!is_int($type) || ($type < 0) || ($type > self::TYPE_ALL)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Unknown type value "%s" (%s)',
+ $type,
+ gettype($type)
+ ));
+ }
+
+ $this->options['type'] = $type;
+ return $this;
+ }
+
+ /**
+ * Returns defined boolean types
+ *
+ * @return int
+ */
+ public function getType()
+ {
+ return $this->options['type'];
+ }
+
+ /**
+ * Set the working mode
+ *
+ * @param bool $flag When true this filter works like cast
+ * When false it recognises only true and false
+ * and all other values are returned as is
+ * @return self
+ */
+ public function setCasting($flag = true)
+ {
+ $this->options['casting'] = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Returns the casting option
+ *
+ * @return bool
+ */
+ public function getCasting()
+ {
+ return $this->options['casting'];
+ }
+
+ /**
+ * @param array|Traversable $translations
+ * @throws Exception\InvalidArgumentException
+ * @return self
+ */
+ public function setTranslations($translations)
+ {
+ if (!is_array($translations) && !$translations instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '"%s" expects an array or Traversable; received "%s"',
+ __METHOD__,
+ (is_object($translations) ? get_class($translations) : gettype($translations))
+ ));
+ }
+
+ foreach ($translations as $message => $flag) {
+ $this->options['translations'][$message] = (bool) $flag;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getTranslations()
+ {
+ return $this->options['translations'];
+ }
+
+ /**
+ * Defined by Zend\Filter\FilterInterface
+ *
+ * Returns a boolean representation of $value
+ *
+ * @param string $value
+ * @return string
+ */
+ public function filter($value)
+ {
+ $type = $this->getType();
+ $casting = $this->getCasting();
+
+ // LOCALIZED
+ if ($type >= self::TYPE_LOCALIZED) {
+ $type -= self::TYPE_LOCALIZED;
+ if (is_string($value)) {
+ if (isset($this->options['translations'][$value])) {
+ return (bool) $this->options['translations'][$value];
+ }
+ }
+ }
+
+ // FALSE_STRING ('false')
+ if ($type >= self::TYPE_FALSE_STRING) {
+ $type -= self::TYPE_FALSE_STRING;
+ if (is_string($value) && (strtolower($value) == 'false')) {
+ return false;
+ }
+
+ if (!$casting && is_string($value) && (strtolower($value) == 'true')) {
+ return true;
+ }
+ }
+
+ // NULL (null)
+ if ($type >= self::TYPE_NULL) {
+ $type -= self::TYPE_NULL;
+ if ($value === null) {
+ return false;
+ }
+ }
+
+ // EMPTY_ARRAY (array())
+ if ($type >= self::TYPE_EMPTY_ARRAY) {
+ $type -= self::TYPE_EMPTY_ARRAY;
+ if (is_array($value) && ($value == array())) {
+ return false;
+ }
+ }
+
+ // ZERO_STRING ('0')
+ if ($type >= self::TYPE_ZERO_STRING) {
+ $type -= self::TYPE_ZERO_STRING;
+ if (is_string($value) && ($value == '0')) {
+ return false;
+ }
+
+ if (!$casting && (is_string($value)) && ($value == '1')) {
+ return true;
+ }
+ }
+
+ // STRING ('')
+ if ($type >= self::TYPE_STRING) {
+ $type -= self::TYPE_STRING;
+ if (is_string($value) && ($value == '')) {
+ return false;
+ }
+ }
+
+ // FLOAT (0.0)
+ if ($type >= self::TYPE_FLOAT) {
+ $type -= self::TYPE_FLOAT;
+ if (is_float($value) && ($value == 0.0)) {
+ return false;
+ }
+
+ if (!$casting && is_float($value) && ($value == 1.0)) {
+ return true;
+ }
+ }
+
+ // INTEGER (0)
+ if ($type >= self::TYPE_INTEGER) {
+ $type -= self::TYPE_INTEGER;
+ if (is_int($value) && ($value == 0)) {
+ return false;
+ }
+
+ if (!$casting && is_int($value) && ($value == 1)) {
+ return true;
+ }
+ }
+
+ // BOOLEAN (false)
+ if ($type >= self::TYPE_BOOLEAN) {
+ $type -= self::TYPE_BOOLEAN;
+ if (is_bool($value)) {
+ return $value;
+ }
+ }
+
+ if ($casting) {
+ return true;
+ }
+
+ return $value;
+ }
+}
diff --git a/library/Zend/Filter/CONTRIBUTING.md b/library/Zend/Filter/CONTRIBUTING.md
new file mode 100755
index 0000000000..e77f5d2d5b
--- /dev/null
+++ b/library/Zend/Filter/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# CONTRIBUTING
+
+Please don't open pull requests against this repository, please use https://github.com/zendframework/zf2.
\ No newline at end of file
diff --git a/library/Zend/Filter/Callback.php b/library/Zend/Filter/Callback.php
new file mode 100755
index 0000000000..51392e1667
--- /dev/null
+++ b/library/Zend/Filter/Callback.php
@@ -0,0 +1,102 @@
+ null,
+ 'callback_params' => array()
+ );
+
+ /**
+ * @param callable|array|Traversable $callbackOrOptions
+ * @param array $callbackParams
+ */
+ public function __construct($callbackOrOptions, $callbackParams = array())
+ {
+ if (is_callable($callbackOrOptions)) {
+ $this->setCallback($callbackOrOptions);
+ $this->setCallbackParams($callbackParams);
+ } else {
+ $this->setOptions($callbackOrOptions);
+ }
+ }
+
+ /**
+ * Sets a new callback for this filter
+ *
+ * @param callable $callback
+ * @throws Exception\InvalidArgumentException
+ * @return self
+ */
+ public function setCallback($callback)
+ {
+ if (!is_callable($callback)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid parameter for callback: must be callable'
+ );
+ }
+
+ $this->options['callback'] = $callback;
+ return $this;
+ }
+
+ /**
+ * Returns the set callback
+ *
+ * @return callable
+ */
+ public function getCallback()
+ {
+ return $this->options['callback'];
+ }
+
+ /**
+ * Sets parameters for the callback
+ *
+ * @param array $params
+ * @return self
+ */
+ public function setCallbackParams($params)
+ {
+ $this->options['callback_params'] = (array) $params;
+ return $this;
+ }
+
+ /**
+ * Get parameters for the callback
+ *
+ * @return array
+ */
+ public function getCallbackParams()
+ {
+ return $this->options['callback_params'];
+ }
+
+ /**
+ * Calls the filter per callback
+ *
+ * @param mixed $value Options for the set callable
+ * @return mixed Result from the filter which was called
+ */
+ public function filter($value)
+ {
+ $params = (array) $this->options['callback_params'];
+ array_unshift($params, $value);
+
+ return call_user_func_array($this->options['callback'], $params);
+ }
+}
diff --git a/library/Zend/Filter/Compress.php b/library/Zend/Filter/Compress.php
new file mode 100755
index 0000000000..f6a49e779c
--- /dev/null
+++ b/library/Zend/Filter/Compress.php
@@ -0,0 +1,210 @@
+setAdapter($options);
+ } elseif ($options instanceof Compress\CompressionAlgorithmInterface) {
+ $this->setAdapter($options);
+ } elseif (is_array($options)) {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * Set filter setate
+ *
+ * @param array $options
+ * @throws Exception\InvalidArgumentException if options is not an array or Traversable
+ * @return self
+ */
+ public function setOptions($options)
+ {
+ if (!is_array($options) && !$options instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '"%s" expects an array or Traversable; received "%s"',
+ __METHOD__,
+ (is_object($options) ? get_class($options) : gettype($options))
+ ));
+ }
+
+ foreach ($options as $key => $value) {
+ if ($key == 'options') {
+ $key = 'adapterOptions';
+ }
+ $method = 'set' . ucfirst($key);
+ if (method_exists($this, $method)) {
+ $this->$method($value);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Returns the current adapter, instantiating it if necessary
+ *
+ * @throws Exception\RuntimeException
+ * @throws Exception\InvalidArgumentException
+ * @return Compress\CompressionAlgorithmInterface
+ */
+ public function getAdapter()
+ {
+ if ($this->adapter instanceof Compress\CompressionAlgorithmInterface) {
+ return $this->adapter;
+ }
+
+ $adapter = $this->adapter;
+ $options = $this->getAdapterOptions();
+ if (!class_exists($adapter)) {
+ $adapter = 'Zend\\Filter\\Compress\\' . ucfirst($adapter);
+ if (!class_exists($adapter)) {
+ throw new Exception\RuntimeException(sprintf(
+ '%s unable to load adapter; class "%s" not found',
+ __METHOD__,
+ $this->adapter
+ ));
+ }
+ }
+
+ $this->adapter = new $adapter($options);
+ if (!$this->adapter instanceof Compress\CompressionAlgorithmInterface) {
+ throw new Exception\InvalidArgumentException("Compression adapter '" . $adapter . "' does not implement Zend\\Filter\\Compress\\CompressionAlgorithmInterface");
+ }
+ return $this->adapter;
+ }
+
+ /**
+ * Retrieve adapter name
+ *
+ * @return string
+ */
+ public function getAdapterName()
+ {
+ return $this->getAdapter()->toString();
+ }
+
+ /**
+ * Sets compression adapter
+ *
+ * @param string|Compress\CompressionAlgorithmInterface $adapter Adapter to use
+ * @return self
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setAdapter($adapter)
+ {
+ if ($adapter instanceof Compress\CompressionAlgorithmInterface) {
+ $this->adapter = $adapter;
+ return $this;
+ }
+ if (!is_string($adapter)) {
+ throw new Exception\InvalidArgumentException('Invalid adapter provided; must be string or instance of Zend\\Filter\\Compress\\CompressionAlgorithmInterface');
+ }
+ $this->adapter = $adapter;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve adapter options
+ *
+ * @return array
+ */
+ public function getAdapterOptions()
+ {
+ return $this->adapterOptions;
+ }
+
+ /**
+ * Set adapter options
+ *
+ * @param array $options
+ * @return self
+ */
+ public function setAdapterOptions(array $options)
+ {
+ $this->adapterOptions = $options;
+ return $this;
+ }
+
+ /**
+ * Get individual or all options from underlying adapter
+ *
+ * @param null|string $option
+ * @return mixed
+ */
+ public function getOptions($option = null)
+ {
+ $adapter = $this->getAdapter();
+ return $adapter->getOptions($option);
+ }
+
+ /**
+ * Calls adapter methods
+ *
+ * @param string $method Method to call
+ * @param string|array $options Options for this method
+ * @return mixed
+ * @throws Exception\BadMethodCallException
+ */
+ public function __call($method, $options)
+ {
+ $adapter = $this->getAdapter();
+ if (!method_exists($adapter, $method)) {
+ throw new Exception\BadMethodCallException("Unknown method '{$method}'");
+ }
+
+ return call_user_func_array(array($adapter, $method), $options);
+ }
+
+ /**
+ * Defined by Zend\Filter\FilterInterface
+ *
+ * Compresses the content $value with the defined settings
+ *
+ * @param string $value Content to compress
+ * @return string The compressed content
+ */
+ public function filter($value)
+ {
+ if (!is_string($value)) {
+ return $value;
+ }
+
+ return $this->getAdapter()->compress($value);
+ }
+}
diff --git a/library/Zend/Filter/Compress/AbstractCompressionAlgorithm.php b/library/Zend/Filter/Compress/AbstractCompressionAlgorithm.php
new file mode 100755
index 0000000000..b97e68cf12
--- /dev/null
+++ b/library/Zend/Filter/Compress/AbstractCompressionAlgorithm.php
@@ -0,0 +1,77 @@
+setOptions($options);
+ }
+ }
+
+ /**
+ * Returns one or all set options
+ *
+ * @param string $option (Optional) Option to return
+ * @return mixed
+ */
+ public function getOptions($option = null)
+ {
+ if ($option === null) {
+ return $this->options;
+ }
+
+ if (!array_key_exists($option, $this->options)) {
+ return null;
+ }
+
+ return $this->options[$option];
+ }
+
+ /**
+ * Sets all or one option
+ *
+ * @param array $options
+ * @return self
+ */
+ public function setOptions(array $options)
+ {
+ foreach ($options as $key => $option) {
+ $method = 'set' . $key;
+ if (method_exists($this, $method)) {
+ $this->$method($option);
+ }
+ }
+
+ return $this;
+ }
+}
diff --git a/library/Zend/Filter/Compress/Bz2.php b/library/Zend/Filter/Compress/Bz2.php
new file mode 100755
index 0000000000..79716118b3
--- /dev/null
+++ b/library/Zend/Filter/Compress/Bz2.php
@@ -0,0 +1,170 @@
+ Blocksize to use from 0-9
+ * 'archive' => Archive to use
+ * )
+ *
+ * @var array
+ */
+ protected $options = array(
+ 'blocksize' => 4,
+ 'archive' => null,
+ );
+
+ /**
+ * Class constructor
+ *
+ * @param null|array|\Traversable $options (Optional) Options to set
+ * @throws Exception\ExtensionNotLoadedException if bz2 extension not loaded
+ */
+ public function __construct($options = null)
+ {
+ if (!extension_loaded('bz2')) {
+ throw new Exception\ExtensionNotLoadedException('This filter needs the bz2 extension');
+ }
+ parent::__construct($options);
+ }
+
+ /**
+ * Returns the set blocksize
+ *
+ * @return int
+ */
+ public function getBlocksize()
+ {
+ return $this->options['blocksize'];
+ }
+
+ /**
+ * Sets a new blocksize
+ *
+ * @param int $blocksize
+ * @throws Exception\InvalidArgumentException
+ * @return self
+ */
+ public function setBlocksize($blocksize)
+ {
+ if (($blocksize < 0) || ($blocksize > 9)) {
+ throw new Exception\InvalidArgumentException('Blocksize must be between 0 and 9');
+ }
+
+ $this->options['blocksize'] = (int) $blocksize;
+ return $this;
+ }
+
+ /**
+ * Returns the set archive
+ *
+ * @return string
+ */
+ public function getArchive()
+ {
+ return $this->options['archive'];
+ }
+
+ /**
+ * Sets the archive to use for de-/compression
+ *
+ * @param string $archive Archive to use
+ * @return self
+ */
+ public function setArchive($archive)
+ {
+ $this->options['archive'] = (string) $archive;
+ return $this;
+ }
+
+ /**
+ * Compresses the given content
+ *
+ * @param string $content
+ * @return string
+ * @throws Exception\RuntimeException
+ */
+ public function compress($content)
+ {
+ $archive = $this->getArchive();
+ if (!empty($archive)) {
+ $file = bzopen($archive, 'w');
+ if (!$file) {
+ throw new Exception\RuntimeException("Error opening the archive '" . $archive . "'");
+ }
+
+ bzwrite($file, $content);
+ bzclose($file);
+ $compressed = true;
+ } else {
+ $compressed = bzcompress($content, $this->getBlocksize());
+ }
+
+ if (is_int($compressed)) {
+ throw new Exception\RuntimeException('Error during compression');
+ }
+
+ return $compressed;
+ }
+
+ /**
+ * Decompresses the given content
+ *
+ * @param string $content
+ * @return string
+ * @throws Exception\RuntimeException
+ */
+ public function decompress($content)
+ {
+ $archive = $this->getArchive();
+
+ //check if there are null byte characters before doing a file_exists check
+ if (!strstr($content, "\0") && file_exists($content)) {
+ $archive = $content;
+ }
+
+ if (file_exists($archive)) {
+ $file = bzopen($archive, 'r');
+ if (!$file) {
+ throw new Exception\RuntimeException("Error opening the archive '" . $content . "'");
+ }
+
+ $compressed = bzread($file);
+ bzclose($file);
+ } else {
+ $compressed = bzdecompress($content);
+ }
+
+ if (is_int($compressed)) {
+ throw new Exception\RuntimeException('Error during decompression');
+ }
+
+ return $compressed;
+ }
+
+ /**
+ * Returns the adapter name
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return 'Bz2';
+ }
+}
diff --git a/library/Zend/Filter/Compress/CompressionAlgorithmInterface.php b/library/Zend/Filter/Compress/CompressionAlgorithmInterface.php
new file mode 100755
index 0000000000..cf4e5f3cb1
--- /dev/null
+++ b/library/Zend/Filter/Compress/CompressionAlgorithmInterface.php
@@ -0,0 +1,39 @@
+ Compression level 0-9
+ * 'mode' => Compression mode, can be 'compress', 'deflate'
+ * 'archive' => Archive to use
+ * )
+ *
+ * @var array
+ */
+ protected $options = array(
+ 'level' => 9,
+ 'mode' => 'compress',
+ 'archive' => null,
+ );
+
+ /**
+ * Class constructor
+ *
+ * @param null|array|\Traversable $options (Optional) Options to set
+ * @throws Exception\ExtensionNotLoadedException if zlib extension not loaded
+ */
+ public function __construct($options = null)
+ {
+ if (!extension_loaded('zlib')) {
+ throw new Exception\ExtensionNotLoadedException('This filter needs the zlib extension');
+ }
+ parent::__construct($options);
+ }
+
+ /**
+ * Returns the set compression level
+ *
+ * @return int
+ */
+ public function getLevel()
+ {
+ return $this->options['level'];
+ }
+
+ /**
+ * Sets a new compression level
+ *
+ * @param int $level
+ * @throws Exception\InvalidArgumentException
+ * @return self
+ */
+ public function setLevel($level)
+ {
+ if (($level < 0) || ($level > 9)) {
+ throw new Exception\InvalidArgumentException('Level must be between 0 and 9');
+ }
+
+ $this->options['level'] = (int) $level;
+ return $this;
+ }
+
+ /**
+ * Returns the set compression mode
+ *
+ * @return string
+ */
+ public function getMode()
+ {
+ return $this->options['mode'];
+ }
+
+ /**
+ * Sets a new compression mode
+ *
+ * @param string $mode Supported are 'compress', 'deflate' and 'file'
+ * @return self
+ * @throws Exception\InvalidArgumentException for invalid $mode value
+ */
+ public function setMode($mode)
+ {
+ if (($mode != 'compress') && ($mode != 'deflate')) {
+ throw new Exception\InvalidArgumentException('Given compression mode not supported');
+ }
+
+ $this->options['mode'] = $mode;
+ return $this;
+ }
+
+ /**
+ * Returns the set archive
+ *
+ * @return string
+ */
+ public function getArchive()
+ {
+ return $this->options['archive'];
+ }
+
+ /**
+ * Sets the archive to use for de-/compression
+ *
+ * @param string $archive Archive to use
+ * @return self
+ */
+ public function setArchive($archive)
+ {
+ $this->options['archive'] = (string) $archive;
+ return $this;
+ }
+
+ /**
+ * Compresses the given content
+ *
+ * @param string $content
+ * @return string
+ * @throws Exception\RuntimeException if unable to open archive or error during decompression
+ */
+ public function compress($content)
+ {
+ $archive = $this->getArchive();
+ if (!empty($archive)) {
+ $file = gzopen($archive, 'w' . $this->getLevel());
+ if (!$file) {
+ throw new Exception\RuntimeException("Error opening the archive '" . $this->options['archive'] . "'");
+ }
+
+ gzwrite($file, $content);
+ gzclose($file);
+ $compressed = true;
+ } elseif ($this->options['mode'] == 'deflate') {
+ $compressed = gzdeflate($content, $this->getLevel());
+ } else {
+ $compressed = gzcompress($content, $this->getLevel());
+ }
+
+ if (!$compressed) {
+ throw new Exception\RuntimeException('Error during compression');
+ }
+
+ return $compressed;
+ }
+
+ /**
+ * Decompresses the given content
+ *
+ * @param string $content
+ * @return string
+ * @throws Exception\RuntimeException if unable to open archive or error during decompression
+ */
+ public function decompress($content)
+ {
+ $archive = $this->getArchive();
+ $mode = $this->getMode();
+
+ //check if there are null byte characters before doing a file_exists check
+ if (!strstr($content, "\0") && file_exists($content)) {
+ $archive = $content;
+ }
+
+ if (file_exists($archive)) {
+ $handler = fopen($archive, "rb");
+ if (!$handler) {
+ throw new Exception\RuntimeException("Error opening the archive '" . $archive . "'");
+ }
+
+ fseek($handler, -4, SEEK_END);
+ $packet = fread($handler, 4);
+ $bytes = unpack("V", $packet);
+ $size = end($bytes);
+ fclose($handler);
+
+ $file = gzopen($archive, 'r');
+ $compressed = gzread($file, $size);
+ gzclose($file);
+ } elseif ($mode == 'deflate') {
+ $compressed = gzinflate($content);
+ } else {
+ $compressed = gzuncompress($content);
+ }
+
+ if ($compressed === false) {
+ throw new Exception\RuntimeException('Error during decompression');
+ }
+
+ return $compressed;
+ }
+
+ /**
+ * Returns the adapter name
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return 'Gz';
+ }
+}
diff --git a/library/Zend/Filter/Compress/Lzf.php b/library/Zend/Filter/Compress/Lzf.php
new file mode 100755
index 0000000000..ea4fd0c60d
--- /dev/null
+++ b/library/Zend/Filter/Compress/Lzf.php
@@ -0,0 +1,75 @@
+ Callback for compression
+ * 'archive' => Archive to use
+ * 'password' => Password to use
+ * 'target' => Target to write the files to
+ * )
+ *
+ * @var array
+ */
+ protected $options = array(
+ 'callback' => null,
+ 'archive' => null,
+ 'password' => null,
+ 'target' => '.',
+ );
+
+ /**
+ * Class constructor
+ *
+ * @param array $options (Optional) Options to set
+ * @throws Exception\ExtensionNotLoadedException if rar extension not loaded
+ */
+ public function __construct($options = null)
+ {
+ if (!extension_loaded('rar')) {
+ throw new Exception\ExtensionNotLoadedException('This filter needs the rar extension');
+ }
+ parent::__construct($options);
+ }
+
+ /**
+ * Returns the set callback for compression
+ *
+ * @return string
+ */
+ public function getCallback()
+ {
+ return $this->options['callback'];
+ }
+
+ /**
+ * Sets the callback to use
+ *
+ * @param string $callback
+ * @return self
+ * @throws Exception\InvalidArgumentException if invalid callback provided
+ */
+ public function setCallback($callback)
+ {
+ if (!is_callable($callback)) {
+ throw new Exception\InvalidArgumentException('Invalid callback provided');
+ }
+
+ $this->options['callback'] = $callback;
+ return $this;
+ }
+
+ /**
+ * Returns the set archive
+ *
+ * @return string
+ */
+ public function getArchive()
+ {
+ return $this->options['archive'];
+ }
+
+ /**
+ * Sets the archive to use for de-/compression
+ *
+ * @param string $archive Archive to use
+ * @return self
+ */
+ public function setArchive($archive)
+ {
+ $archive = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $archive);
+ $this->options['archive'] = (string) $archive;
+
+ return $this;
+ }
+
+ /**
+ * Returns the set password
+ *
+ * @return string
+ */
+ public function getPassword()
+ {
+ return $this->options['password'];
+ }
+
+ /**
+ * Sets the password to use
+ *
+ * @param string $password
+ * @return self
+ */
+ public function setPassword($password)
+ {
+ $this->options['password'] = (string) $password;
+ return $this;
+ }
+
+ /**
+ * Returns the set targetpath
+ *
+ * @return string
+ */
+ public function getTarget()
+ {
+ return $this->options['target'];
+ }
+
+ /**
+ * Sets the targetpath to use
+ *
+ * @param string $target
+ * @return self
+ * @throws Exception\InvalidArgumentException if specified target directory does not exist
+ */
+ public function setTarget($target)
+ {
+ if (!file_exists(dirname($target))) {
+ throw new Exception\InvalidArgumentException("The directory '$target' does not exist");
+ }
+
+ $target = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, (string) $target);
+ $this->options['target'] = $target;
+ return $this;
+ }
+
+ /**
+ * Compresses the given content
+ *
+ * @param string|array $content
+ * @return string
+ * @throws Exception\RuntimeException if no callback available, or error during compression
+ */
+ public function compress($content)
+ {
+ $callback = $this->getCallback();
+ if ($callback === null) {
+ throw new Exception\RuntimeException('No compression callback available');
+ }
+
+ $options = $this->getOptions();
+ unset($options['callback']);
+
+ $result = call_user_func($callback, $options, $content);
+ if ($result !== true) {
+ throw new Exception\RuntimeException('Error compressing the RAR Archive');
+ }
+
+ return $this->getArchive();
+ }
+
+ /**
+ * Decompresses the given content
+ *
+ * @param string $content
+ * @return bool
+ * @throws Exception\RuntimeException if archive not found, cannot be opened,
+ * or error during decompression
+ */
+ public function decompress($content)
+ {
+ if (!file_exists($content)) {
+ throw new Exception\RuntimeException('RAR Archive not found');
+ }
+
+ $archive = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, realpath($content));
+ $password = $this->getPassword();
+ if ($password !== null) {
+ $archive = rar_open($archive, $password);
+ } else {
+ $archive = rar_open($archive);
+ }
+
+ if (!$archive) {
+ throw new Exception\RuntimeException("Error opening the RAR Archive");
+ }
+
+ $target = $this->getTarget();
+ if (!is_dir($target)) {
+ $target = dirname($target);
+ }
+
+ $filelist = rar_list($archive);
+ if (!$filelist) {
+ throw new Exception\RuntimeException("Error reading the RAR Archive");
+ }
+
+ foreach ($filelist as $file) {
+ $file->extract($target);
+ }
+
+ rar_close($archive);
+ return true;
+ }
+
+ /**
+ * Returns the adapter name
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return 'Rar';
+ }
+}
diff --git a/library/Zend/Filter/Compress/Snappy.php b/library/Zend/Filter/Compress/Snappy.php
new file mode 100755
index 0000000000..33a9c61627
--- /dev/null
+++ b/library/Zend/Filter/Compress/Snappy.php
@@ -0,0 +1,77 @@
+ Archive to use
+ * 'target' => Target to write the files to
+ * )
+ *
+ * @var array
+ */
+ protected $options = array(
+ 'archive' => null,
+ 'target' => '.',
+ 'mode' => null,
+ );
+
+ /**
+ * Class constructor
+ *
+ * @param array $options (Optional) Options to set
+ * @throws Exception\ExtensionNotLoadedException if Archive_Tar component not available
+ */
+ public function __construct($options = null)
+ {
+ if (!class_exists('Archive_Tar')) {
+ throw new Exception\ExtensionNotLoadedException(
+ 'This filter needs PEAR\'s Archive_Tar component. '
+ . 'Ensure loading Archive_Tar (registering autoload or require_once)');
+ }
+
+ parent::__construct($options);
+ }
+
+ /**
+ * Returns the set archive
+ *
+ * @return string
+ */
+ public function getArchive()
+ {
+ return $this->options['archive'];
+ }
+
+ /**
+ * Sets the archive to use for de-/compression
+ *
+ * @param string $archive Archive to use
+ * @return self
+ */
+ public function setArchive($archive)
+ {
+ $archive = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, (string) $archive);
+ $this->options['archive'] = $archive;
+
+ return $this;
+ }
+
+ /**
+ * Returns the set target path
+ *
+ * @return string
+ */
+ public function getTarget()
+ {
+ return $this->options['target'];
+ }
+
+ /**
+ * Sets the target path to use
+ *
+ * @param string $target
+ * @return self
+ * @throws Exception\InvalidArgumentException if target path does not exist
+ */
+ public function setTarget($target)
+ {
+ if (!file_exists(dirname($target))) {
+ throw new Exception\InvalidArgumentException("The directory '$target' does not exist");
+ }
+
+ $target = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, (string) $target);
+ $this->options['target'] = $target;
+ return $this;
+ }
+
+ /**
+ * Returns the set compression mode
+ *
+ * @return string
+ */
+ public function getMode()
+ {
+ return $this->options['mode'];
+ }
+
+ /**
+ * Compression mode to use
+ *
+ * Either Gz or Bz2.
+ *
+ * @param string $mode
+ * @return self
+ * @throws Exception\InvalidArgumentException for invalid $mode values
+ * @throws Exception\ExtensionNotLoadedException if bz2 mode selected but extension not loaded
+ * @throws Exception\ExtensionNotLoadedException if gz mode selected but extension not loaded
+ */
+ public function setMode($mode)
+ {
+ $mode = strtolower($mode);
+ if (($mode != 'bz2') && ($mode != 'gz')) {
+ throw new Exception\InvalidArgumentException("The mode '$mode' is unknown");
+ }
+
+ if (($mode == 'bz2') && (!extension_loaded('bz2'))) {
+ throw new Exception\ExtensionNotLoadedException('This mode needs the bz2 extension');
+ }
+
+ if (($mode == 'gz') && (!extension_loaded('zlib'))) {
+ throw new Exception\ExtensionNotLoadedException('This mode needs the zlib extension');
+ }
+
+ $this->options['mode'] = $mode;
+ return $this;
+ }
+
+ /**
+ * Compresses the given content
+ *
+ * @param string $content
+ * @return string
+ * @throws Exception\RuntimeException if unable to create temporary file
+ * @throws Exception\RuntimeException if unable to create archive
+ */
+ public function compress($content)
+ {
+ $archive = new Archive_Tar($this->getArchive(), $this->getMode());
+ if (!file_exists($content)) {
+ $file = $this->getTarget();
+ if (is_dir($file)) {
+ $file .= DIRECTORY_SEPARATOR . "tar.tmp";
+ }
+
+ $result = file_put_contents($file, $content);
+ if ($result === false) {
+ throw new Exception\RuntimeException('Error creating the temporary file');
+ }
+
+ $content = $file;
+ }
+
+ if (is_dir($content)) {
+ // collect all file infos
+ foreach (new RecursiveIteratorIterator(
+ new RecursiveDirectoryIterator($content, RecursiveDirectoryIterator::KEY_AS_PATHNAME),
+ RecursiveIteratorIterator::SELF_FIRST
+ ) as $directory => $info
+ ) {
+ if ($info->isFile()) {
+ $file[] = $directory;
+ }
+ }
+
+ $content = $file;
+ }
+
+ $result = $archive->create($content);
+ if ($result === false) {
+ throw new Exception\RuntimeException('Error creating the Tar archive');
+ }
+
+ return $this->getArchive();
+ }
+
+ /**
+ * Decompresses the given content
+ *
+ * @param string $content
+ * @return string
+ * @throws Exception\RuntimeException if unable to find archive
+ * @throws Exception\RuntimeException if error occurs decompressing archive
+ */
+ public function decompress($content)
+ {
+ $archive = $this->getArchive();
+ if (empty($archive) || !file_exists($archive)) {
+ throw new Exception\RuntimeException('Tar Archive not found');
+ }
+
+ $archive = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, realpath($content));
+ $archive = new Archive_Tar($archive, $this->getMode());
+ $target = $this->getTarget();
+ if (!is_dir($target)) {
+ $target = dirname($target) . DIRECTORY_SEPARATOR;
+ }
+
+ $result = $archive->extract($target);
+ if ($result === false) {
+ throw new Exception\RuntimeException('Error while extracting the Tar archive');
+ }
+
+ return $target;
+ }
+
+ /**
+ * Returns the adapter name
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return 'Tar';
+ }
+}
diff --git a/library/Zend/Filter/Compress/Zip.php b/library/Zend/Filter/Compress/Zip.php
new file mode 100755
index 0000000000..5a6f01a095
--- /dev/null
+++ b/library/Zend/Filter/Compress/Zip.php
@@ -0,0 +1,314 @@
+ Archive to use
+ * 'password' => Password to use
+ * 'target' => Target to write the files to
+ * )
+ *
+ * @var array
+ */
+ protected $options = array(
+ 'archive' => null,
+ 'target' => null,
+ );
+
+ /**
+ * Class constructor
+ *
+ * @param null|array|\Traversable $options (Optional) Options to set
+ * @throws Exception\ExtensionNotLoadedException if zip extension not loaded
+ */
+ public function __construct($options = null)
+ {
+ if (!extension_loaded('zip')) {
+ throw new Exception\ExtensionNotLoadedException('This filter needs the zip extension');
+ }
+ parent::__construct($options);
+ }
+
+ /**
+ * Returns the set archive
+ *
+ * @return string
+ */
+ public function getArchive()
+ {
+ return $this->options['archive'];
+ }
+
+ /**
+ * Sets the archive to use for de-/compression
+ *
+ * @param string $archive Archive to use
+ * @return self
+ */
+ public function setArchive($archive)
+ {
+ $archive = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, (string) $archive);
+ $this->options['archive'] = $archive;
+
+ return $this;
+ }
+
+ /**
+ * Returns the set targetpath
+ *
+ * @return string
+ */
+ public function getTarget()
+ {
+ return $this->options['target'];
+ }
+
+ /**
+ * Sets the target to use
+ *
+ * @param string $target
+ * @throws Exception\InvalidArgumentException
+ * @return self
+ */
+ public function setTarget($target)
+ {
+ if (!file_exists(dirname($target))) {
+ throw new Exception\InvalidArgumentException("The directory '$target' does not exist");
+ }
+
+ $target = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, (string) $target);
+ $this->options['target'] = $target;
+ return $this;
+ }
+
+ /**
+ * Compresses the given content
+ *
+ * @param string $content
+ * @return string Compressed archive
+ * @throws Exception\RuntimeException if unable to open zip archive, or error during compression
+ */
+ public function compress($content)
+ {
+ $zip = new ZipArchive();
+ $res = $zip->open($this->getArchive(), ZipArchive::CREATE | ZipArchive::OVERWRITE);
+
+ if ($res !== true) {
+ throw new Exception\RuntimeException($this->errorString($res));
+ }
+
+ if (file_exists($content)) {
+ $content = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, realpath($content));
+ $basename = substr($content, strrpos($content, DIRECTORY_SEPARATOR) + 1);
+ if (is_dir($content)) {
+ $index = strrpos($content, DIRECTORY_SEPARATOR) + 1;
+ $content .= DIRECTORY_SEPARATOR;
+ $stack = array($content);
+ while (!empty($stack)) {
+ $current = array_pop($stack);
+ $files = array();
+
+ $dir = dir($current);
+ while (false !== ($node = $dir->read())) {
+ if (($node == '.') || ($node == '..')) {
+ continue;
+ }
+
+ if (is_dir($current . $node)) {
+ array_push($stack, $current . $node . DIRECTORY_SEPARATOR);
+ }
+
+ if (is_file($current . $node)) {
+ $files[] = $node;
+ }
+ }
+
+ $local = substr($current, $index);
+ $zip->addEmptyDir(substr($local, 0, -1));
+
+ foreach ($files as $file) {
+ $zip->addFile($current . $file, $local . $file);
+ if ($res !== true) {
+ throw new Exception\RuntimeException($this->errorString($res));
+ }
+ }
+ }
+ } else {
+ $res = $zip->addFile($content, $basename);
+ if ($res !== true) {
+ throw new Exception\RuntimeException($this->errorString($res));
+ }
+ }
+ } else {
+ $file = $this->getTarget();
+ if (!is_dir($file)) {
+ $file = basename($file);
+ } else {
+ $file = "zip.tmp";
+ }
+
+ $res = $zip->addFromString($file, $content);
+ if ($res !== true) {
+ throw new Exception\RuntimeException($this->errorString($res));
+ }
+ }
+
+ $zip->close();
+ return $this->options['archive'];
+ }
+
+ /**
+ * Decompresses the given content
+ *
+ * @param string $content
+ * @return string
+ * @throws Exception\RuntimeException If archive file not found, target directory not found,
+ * or error during decompression
+ */
+ public function decompress($content)
+ {
+ $archive = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, realpath($content));
+
+ if (empty($archive) || !file_exists($archive)) {
+ throw new Exception\RuntimeException('ZIP Archive not found');
+ }
+
+ $zip = new ZipArchive();
+ $res = $zip->open($archive);
+
+ $target = $this->getTarget();
+ if (!empty($target) && !is_dir($target)) {
+ $target = dirname($target);
+ }
+
+ if (!empty($target)) {
+ $target = rtrim($target, '/\\') . DIRECTORY_SEPARATOR;
+ }
+
+ if (empty($target) || !is_dir($target)) {
+ throw new Exception\RuntimeException('No target for ZIP decompression set');
+ }
+
+ if ($res !== true) {
+ throw new Exception\RuntimeException($this->errorString($res));
+ }
+
+ $res = $zip->extractTo($target);
+ if ($res !== true) {
+ throw new Exception\RuntimeException($this->errorString($res));
+ }
+
+ $zip->close();
+ return $target;
+ }
+
+ /**
+ * Returns the proper string based on the given error constant
+ *
+ * @param string $error
+ * @return string
+ */
+ public function errorString($error)
+ {
+ switch ($error) {
+ case ZipArchive::ER_MULTIDISK :
+ return 'Multidisk ZIP Archives not supported';
+
+ case ZipArchive::ER_RENAME :
+ return 'Failed to rename the temporary file for ZIP';
+
+ case ZipArchive::ER_CLOSE :
+ return 'Failed to close the ZIP Archive';
+
+ case ZipArchive::ER_SEEK :
+ return 'Failure while seeking the ZIP Archive';
+
+ case ZipArchive::ER_READ :
+ return 'Failure while reading the ZIP Archive';
+
+ case ZipArchive::ER_WRITE :
+ return 'Failure while writing the ZIP Archive';
+
+ case ZipArchive::ER_CRC :
+ return 'CRC failure within the ZIP Archive';
+
+ case ZipArchive::ER_ZIPCLOSED :
+ return 'ZIP Archive already closed';
+
+ case ZipArchive::ER_NOENT :
+ return 'No such file within the ZIP Archive';
+
+ case ZipArchive::ER_EXISTS :
+ return 'ZIP Archive already exists';
+
+ case ZipArchive::ER_OPEN :
+ return 'Can not open ZIP Archive';
+
+ case ZipArchive::ER_TMPOPEN :
+ return 'Failure creating temporary ZIP Archive';
+
+ case ZipArchive::ER_ZLIB :
+ return 'ZLib Problem';
+
+ case ZipArchive::ER_MEMORY :
+ return 'Memory allocation problem while working on a ZIP Archive';
+
+ case ZipArchive::ER_CHANGED :
+ return 'ZIP Entry has been changed';
+
+ case ZipArchive::ER_COMPNOTSUPP :
+ return 'Compression method not supported within ZLib';
+
+ case ZipArchive::ER_EOF :
+ return 'Premature EOF within ZIP Archive';
+
+ case ZipArchive::ER_INVAL :
+ return 'Invalid argument for ZLIB';
+
+ case ZipArchive::ER_NOZIP :
+ return 'Given file is no zip archive';
+
+ case ZipArchive::ER_INTERNAL :
+ return 'Internal error while working on a ZIP Archive';
+
+ case ZipArchive::ER_INCONS :
+ return 'Inconsistent ZIP archive';
+
+ case ZipArchive::ER_REMOVE :
+ return 'Can not remove ZIP Archive';
+
+ case ZipArchive::ER_DELETED :
+ return 'ZIP Entry has been deleted';
+
+ default :
+ return 'Unknown error within ZIP Archive';
+ }
+ }
+
+ /**
+ * Returns the adapter name
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return 'Zip';
+ }
+}
diff --git a/library/Zend/Filter/DateTimeFormatter.php b/library/Zend/Filter/DateTimeFormatter.php
new file mode 100755
index 0000000000..b24897b2f3
--- /dev/null
+++ b/library/Zend/Filter/DateTimeFormatter.php
@@ -0,0 +1,96 @@
+setOptions($options);
+ }
+ }
+
+ /**
+ * Set the format string accepted by date() to use when formatting a string
+ *
+ * @param string $format
+ * @return self
+ */
+ public function setFormat($format)
+ {
+ $this->format = $format;
+
+ return $this;
+ }
+
+ /**
+ * Filter a datetime string by normalizing it to the filters specified format
+ *
+ * @param DateTime|string|integer $value
+ * @throws Exception\InvalidArgumentException
+ * @return string
+ */
+ public function filter($value)
+ {
+ try {
+ $result = $this->normalizeDateTime($value);
+ } catch (\Exception $e) {
+ // DateTime threw an exception, an invalid date string was provided
+ throw new Exception\InvalidArgumentException('Invalid date string provided', $e->getCode(), $e);
+ }
+
+ if ($result === false) {
+ return $value;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Normalize the provided value to a formatted string
+ *
+ * @param string|int|DateTime $value
+ * @return string
+ */
+ protected function normalizeDateTime($value)
+ {
+ if ($value === '' || $value === null) {
+ return $value;
+ }
+
+ if (!is_string($value) && !is_int($value) && !$value instanceof DateTime) {
+ return $value;
+ }
+
+ if (is_int($value)) {
+ //timestamp
+ $value = new DateTime('@' . $value);
+ } elseif (!$value instanceof DateTime) {
+ $value = new DateTime($value);
+ }
+
+ return $value->format($this->format);
+ }
+}
diff --git a/library/Zend/Filter/Decompress.php b/library/Zend/Filter/Decompress.php
new file mode 100755
index 0000000000..3489c70c16
--- /dev/null
+++ b/library/Zend/Filter/Decompress.php
@@ -0,0 +1,46 @@
+filter($value);
+ }
+
+ /**
+ * Defined by FilterInterface
+ *
+ * Decompresses the content $value with the defined settings
+ *
+ * @param string $value Content to decompress
+ * @return string The decompressed content
+ */
+ public function filter($value)
+ {
+ if (!is_string($value) && $value !== null) {
+ return $value;
+ }
+
+ return $this->getAdapter()->decompress($value);
+ }
+}
diff --git a/library/Zend/Filter/Decrypt.php b/library/Zend/Filter/Decrypt.php
new file mode 100755
index 0000000000..9c8e8492bb
--- /dev/null
+++ b/library/Zend/Filter/Decrypt.php
@@ -0,0 +1,33 @@
+adapter->decrypt($value);
+ }
+}
diff --git a/library/Zend/Filter/Digits.php b/library/Zend/Filter/Digits.php
new file mode 100755
index 0000000000..c5e856fbd5
--- /dev/null
+++ b/library/Zend/Filter/Digits.php
@@ -0,0 +1,49 @@
+setAdapter($options);
+ }
+
+ /**
+ * Returns the name of the set adapter
+ * @todo inconsitent: get adapter should return the adapter and not the name
+ *
+ * @return string
+ */
+ public function getAdapter()
+ {
+ return $this->adapter->toString();
+ }
+
+ /**
+ * Sets new encryption options
+ *
+ * @param string|array $options (Optional) Encryption options
+ * @return self
+ * @throws Exception\DomainException
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setAdapter($options = null)
+ {
+ if (is_string($options)) {
+ $adapter = $options;
+ } elseif (isset($options['adapter'])) {
+ $adapter = $options['adapter'];
+ unset($options['adapter']);
+ } else {
+ $adapter = 'BlockCipher';
+ }
+
+ if (!is_array($options)) {
+ $options = array();
+ }
+
+ if (class_exists('Zend\Filter\Encrypt\\' . ucfirst($adapter))) {
+ $adapter = 'Zend\Filter\Encrypt\\' . ucfirst($adapter);
+ } elseif (!class_exists($adapter)) {
+ throw new Exception\DomainException(
+ sprintf('%s expects a valid registry class name; received "%s", which did not resolve',
+ __METHOD__,
+ $adapter
+ ));
+ }
+
+ $this->adapter = new $adapter($options);
+ if (!$this->adapter instanceof Encrypt\EncryptionAlgorithmInterface) {
+ throw new Exception\InvalidArgumentException(
+ "Encoding adapter '" . $adapter
+ . "' does not implement Zend\\Filter\\Encrypt\\EncryptionAlgorithmInterface");
+ }
+
+ return $this;
+ }
+
+ /**
+ * Calls adapter methods
+ *
+ * @param string $method Method to call
+ * @param string|array $options Options for this method
+ * @return mixed
+ * @throws Exception\BadMethodCallException
+ */
+ public function __call($method, $options)
+ {
+ $part = substr($method, 0, 3);
+ if ((($part != 'get') && ($part != 'set')) || !method_exists($this->adapter, $method)) {
+ throw new Exception\BadMethodCallException("Unknown method '{$method}'");
+ }
+
+ return call_user_func_array(array($this->adapter, $method), $options);
+ }
+
+ /**
+ * Defined by Zend\Filter\Filter
+ *
+ * Encrypts the content $value with the defined settings
+ *
+ * @param string $value Content to encrypt
+ * @return string The encrypted content
+ */
+ public function filter($value)
+ {
+ if (!is_string($value)) {
+ return $value;
+ }
+
+ return $this->adapter->encrypt($value);
+ }
+}
diff --git a/library/Zend/Filter/Encrypt/BlockCipher.php b/library/Zend/Filter/Encrypt/BlockCipher.php
new file mode 100755
index 0000000000..5b7c13664c
--- /dev/null
+++ b/library/Zend/Filter/Encrypt/BlockCipher.php
@@ -0,0 +1,288 @@
+ encryption key string
+ * 'key_iteration' => the number of iterations for the PBKDF2 key generation
+ * 'algorithm => cipher algorithm to use
+ * 'hash' => algorithm to use for the authentication
+ * 'vector' => initialization vector
+ * )
+ */
+ protected $encryption = array(
+ 'key_iteration' => 5000,
+ 'algorithm' => 'aes',
+ 'hash' => 'sha256',
+ );
+
+ /**
+ * BlockCipher
+ *
+ * @var BlockCipher
+ */
+ protected $blockCipher;
+
+ /**
+ * Internal compression
+ *
+ * @var array
+ */
+ protected $compression;
+
+ /**
+ * Class constructor
+ *
+ * @param string|array|Traversable $options Encryption Options
+ * @throws Exception\RuntimeException
+ * @throws Exception\InvalidArgumentException
+ */
+ public function __construct($options)
+ {
+ try {
+ $this->blockCipher = CryptBlockCipher::factory('mcrypt', $this->encryption);
+ } catch (SymmetricException\RuntimeException $e) {
+ throw new Exception\RuntimeException('The BlockCipher cannot be used without the Mcrypt extension');
+ }
+
+ if ($options instanceof Traversable) {
+ $options = ArrayUtils::iteratorToArray($options);
+ } elseif (is_string($options)) {
+ $options = array('key' => $options);
+ } elseif (!is_array($options)) {
+ throw new Exception\InvalidArgumentException('Invalid options argument provided to filter');
+ }
+
+ if (array_key_exists('compression', $options)) {
+ $this->setCompression($options['compression']);
+ unset($options['compress']);
+ }
+
+ $this->setEncryption($options);
+ }
+
+ /**
+ * Returns the set encryption options
+ *
+ * @return array
+ */
+ public function getEncryption()
+ {
+ return $this->encryption;
+ }
+
+ /**
+ * Sets new encryption options
+ *
+ * @param string|array $options Encryption options
+ * @return self
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setEncryption($options)
+ {
+ if (is_string($options)) {
+ $this->blockCipher->setKey($options);
+ $this->encryption['key'] = $options;
+ return $this;
+ }
+
+ if (!is_array($options)) {
+ throw new Exception\InvalidArgumentException('Invalid options argument provided to filter');
+ }
+
+ $options = $options + $this->encryption;
+
+ if (isset($options['key'])) {
+ $this->blockCipher->setKey($options['key']);
+ }
+
+ if (isset($options['algorithm'])) {
+ try {
+ $this->blockCipher->setCipherAlgorithm($options['algorithm']);
+ } catch (CryptException\InvalidArgumentException $e) {
+ throw new Exception\InvalidArgumentException("The algorithm '{$options['algorithm']}' is not supported");
+ }
+ }
+
+ if (isset($options['hash'])) {
+ try {
+ $this->blockCipher->setHashAlgorithm($options['hash']);
+ } catch (CryptException\InvalidArgumentException $e) {
+ throw new Exception\InvalidArgumentException("The algorithm '{$options['hash']}' is not supported");
+ }
+ }
+
+ if (isset($options['vector'])) {
+ $this->setVector($options['vector']);
+ }
+
+ if (isset($options['key_iteration'])) {
+ $this->blockCipher->setKeyIteration($options['key_iteration']);
+ }
+
+ $this->encryption = $options;
+
+ return $this;
+ }
+
+ /**
+ * Returns the initialization vector
+ *
+ * @return string
+ */
+ public function getVector()
+ {
+ return $this->encryption['vector'];
+ }
+
+ /**
+ * Set the inizialization vector
+ *
+ * @param string $vector
+ * @return self
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setVector($vector)
+ {
+ try {
+ $this->blockCipher->setSalt($vector);
+ } catch (CryptException\InvalidArgumentException $e) {
+ throw new Exception\InvalidArgumentException($e->getMessage());
+ }
+ $this->encryption['vector'] = $vector;
+ return $this;
+ }
+
+ /**
+ * Set the encryption key
+ *
+ * @param string $key
+ * @return self
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setKey($key)
+ {
+ try {
+ $this->blockCipher->setKey($key);
+ } catch (CryptException\InvalidArgumentException $e) {
+ throw new Exception\InvalidArgumentException($e->getMessage());
+ }
+ $this->encryption['key'] = $key;
+ return $this;
+ }
+
+ /**
+ * Get the encryption key
+ *
+ * @return string
+ */
+ public function getKey()
+ {
+ return $this->encryption['key'];
+ }
+
+ /**
+ * Returns the compression
+ *
+ * @return array
+ */
+ public function getCompression()
+ {
+ return $this->compression;
+ }
+
+ /**
+ * Sets an internal compression for values to encrypt
+ *
+ * @param string|array $compression
+ * @return self
+ */
+ public function setCompression($compression)
+ {
+ if (is_string($this->compression)) {
+ $compression = array('adapter' => $compression);
+ }
+
+ $this->compression = $compression;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend\Filter\FilterInterface
+ *
+ * Encrypts $value with the defined settings
+ *
+ * @param string $value The content to encrypt
+ * @throws Exception\InvalidArgumentException
+ * @return string The encrypted content
+ */
+ public function encrypt($value)
+ {
+ // compress prior to encryption
+ if (!empty($this->compression)) {
+ $compress = new Compress($this->compression);
+ $value = $compress($value);
+ }
+
+ try {
+ $encrypted = $this->blockCipher->encrypt($value);
+ } catch (CryptException\InvalidArgumentException $e) {
+ throw new Exception\InvalidArgumentException($e->getMessage());
+ }
+ return $encrypted;
+ }
+
+ /**
+ * Defined by Zend\Filter\FilterInterface
+ *
+ * Decrypts $value with the defined settings
+ *
+ * @param string $value Content to decrypt
+ * @return string The decrypted content
+ */
+ public function decrypt($value)
+ {
+ $decrypted = $this->blockCipher->decrypt($value);
+
+ // decompress after decryption
+ if (!empty($this->compression)) {
+ $decompress = new Decompress($this->compression);
+ $decrypted = $decompress($decrypted);
+ }
+
+ return $decrypted;
+ }
+
+ /**
+ * Returns the adapter name
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return 'BlockCipher';
+ }
+}
diff --git a/library/Zend/Filter/Encrypt/EncryptionAlgorithmInterface.php b/library/Zend/Filter/Encrypt/EncryptionAlgorithmInterface.php
new file mode 100755
index 0000000000..faf0c518cc
--- /dev/null
+++ b/library/Zend/Filter/Encrypt/EncryptionAlgorithmInterface.php
@@ -0,0 +1,39 @@
+ public keys
+ * 'private' => private keys
+ * 'envelope' => resulting envelope keys
+ * )
+ */
+ protected $keys = array(
+ 'public' => array(),
+ 'private' => array(),
+ 'envelope' => array(),
+ );
+
+ /**
+ * Internal passphrase
+ *
+ * @var string
+ */
+ protected $passphrase;
+
+ /**
+ * Internal compression
+ *
+ * @var array
+ */
+ protected $compression;
+
+ /**
+ * Internal create package
+ *
+ * @var bool
+ */
+ protected $package = false;
+
+ /**
+ * Class constructor
+ * Available options
+ * 'public' => public key
+ * 'private' => private key
+ * 'envelope' => envelope key
+ * 'passphrase' => passphrase
+ * 'compression' => compress value with this compression adapter
+ * 'package' => pack envelope keys into encrypted string, simplifies decryption
+ *
+ * @param string|array|Traversable $options Options for this adapter
+ * @throws Exception\ExtensionNotLoadedException
+ */
+ public function __construct($options = array())
+ {
+ if (!extension_loaded('openssl')) {
+ throw new Exception\ExtensionNotLoadedException('This filter needs the openssl extension');
+ }
+
+ if ($options instanceof Traversable) {
+ $options = ArrayUtils::iteratorToArray($options);
+ }
+
+ if (!is_array($options)) {
+ $options = array('public' => $options);
+ }
+
+ if (array_key_exists('passphrase', $options)) {
+ $this->setPassphrase($options['passphrase']);
+ unset($options['passphrase']);
+ }
+
+ if (array_key_exists('compression', $options)) {
+ $this->setCompression($options['compression']);
+ unset($options['compress']);
+ }
+
+ if (array_key_exists('package', $options)) {
+ $this->setPackage($options['package']);
+ unset($options['package']);
+ }
+
+ $this->_setKeys($options);
+ }
+
+ /**
+ * Sets the encryption keys
+ *
+ * @param string|array $keys Key with type association
+ * @return self
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function _setKeys($keys)
+ {
+ if (!is_array($keys)) {
+ throw new Exception\InvalidArgumentException('Invalid options argument provided to filter');
+ }
+
+ foreach ($keys as $type => $key) {
+ if (is_file($key) and is_readable($key)) {
+ $file = fopen($key, 'r');
+ $cert = fread($file, 8192);
+ fclose($file);
+ } else {
+ $cert = $key;
+ $key = count($this->keys[$type]);
+ }
+
+ switch ($type) {
+ case 'public':
+ $test = openssl_pkey_get_public($cert);
+ if ($test === false) {
+ throw new Exception\InvalidArgumentException("Public key '{$cert}' not valid");
+ }
+
+ openssl_free_key($test);
+ $this->keys['public'][$key] = $cert;
+ break;
+ case 'private':
+ $test = openssl_pkey_get_private($cert, $this->passphrase);
+ if ($test === false) {
+ throw new Exception\InvalidArgumentException("Private key '{$cert}' not valid");
+ }
+
+ openssl_free_key($test);
+ $this->keys['private'][$key] = $cert;
+ break;
+ case 'envelope':
+ $this->keys['envelope'][$key] = $cert;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns all public keys
+ *
+ * @return array
+ */
+ public function getPublicKey()
+ {
+ $key = $this->keys['public'];
+ return $key;
+ }
+
+ /**
+ * Sets public keys
+ *
+ * @param string|array $key Public keys
+ * @return self
+ */
+ public function setPublicKey($key)
+ {
+ if (is_array($key)) {
+ foreach ($key as $type => $option) {
+ if ($type !== 'public') {
+ $key['public'] = $option;
+ unset($key[$type]);
+ }
+ }
+ } else {
+ $key = array('public' => $key);
+ }
+
+ return $this->_setKeys($key);
+ }
+
+ /**
+ * Returns all private keys
+ *
+ * @return array
+ */
+ public function getPrivateKey()
+ {
+ $key = $this->keys['private'];
+ return $key;
+ }
+
+ /**
+ * Sets private keys
+ *
+ * @param string $key Private key
+ * @param string $passphrase
+ * @return self
+ */
+ public function setPrivateKey($key, $passphrase = null)
+ {
+ if (is_array($key)) {
+ foreach ($key as $type => $option) {
+ if ($type !== 'private') {
+ $key['private'] = $option;
+ unset($key[$type]);
+ }
+ }
+ } else {
+ $key = array('private' => $key);
+ }
+
+ if ($passphrase !== null) {
+ $this->setPassphrase($passphrase);
+ }
+
+ return $this->_setKeys($key);
+ }
+
+ /**
+ * Returns all envelope keys
+ *
+ * @return array
+ */
+ public function getEnvelopeKey()
+ {
+ $key = $this->keys['envelope'];
+ return $key;
+ }
+
+ /**
+ * Sets envelope keys
+ *
+ * @param string|array $key Envelope keys
+ * @return self
+ */
+ public function setEnvelopeKey($key)
+ {
+ if (is_array($key)) {
+ foreach ($key as $type => $option) {
+ if ($type !== 'envelope') {
+ $key['envelope'] = $option;
+ unset($key[$type]);
+ }
+ }
+ } else {
+ $key = array('envelope' => $key);
+ }
+
+ return $this->_setKeys($key);
+ }
+
+ /**
+ * Returns the passphrase
+ *
+ * @return string
+ */
+ public function getPassphrase()
+ {
+ return $this->passphrase;
+ }
+
+ /**
+ * Sets a new passphrase
+ *
+ * @param string $passphrase
+ * @return self
+ */
+ public function setPassphrase($passphrase)
+ {
+ $this->passphrase = $passphrase;
+ return $this;
+ }
+
+ /**
+ * Returns the compression
+ *
+ * @return array
+ */
+ public function getCompression()
+ {
+ return $this->compression;
+ }
+
+ /**
+ * Sets an internal compression for values to encrypt
+ *
+ * @param string|array $compression
+ * @return self
+ */
+ public function setCompression($compression)
+ {
+ if (is_string($this->compression)) {
+ $compression = array('adapter' => $compression);
+ }
+
+ $this->compression = $compression;
+ return $this;
+ }
+
+ /**
+ * Returns if header should be packaged
+ *
+ * @return bool
+ */
+ public function getPackage()
+ {
+ return $this->package;
+ }
+
+ /**
+ * Sets if the envelope keys should be included in the encrypted value
+ *
+ * @param bool $package
+ * @return self
+ */
+ public function setPackage($package)
+ {
+ $this->package = (bool) $package;
+ return $this;
+ }
+
+ /**
+ * Encrypts $value with the defined settings
+ * Note that you also need the "encrypted" keys to be able to decrypt
+ *
+ * @param string $value Content to encrypt
+ * @return string The encrypted content
+ * @throws Exception\RuntimeException
+ */
+ public function encrypt($value)
+ {
+ $encrypted = array();
+ $encryptedkeys = array();
+
+ if (count($this->keys['public']) == 0) {
+ throw new Exception\RuntimeException('Openssl can not encrypt without public keys');
+ }
+
+ $keys = array();
+ $fingerprints = array();
+ $count = -1;
+ foreach ($this->keys['public'] as $key => $cert) {
+ $keys[$key] = openssl_pkey_get_public($cert);
+ if ($this->package) {
+ $details = openssl_pkey_get_details($keys[$key]);
+ if ($details === false) {
+ $details = array('key' => 'ZendFramework');
+ }
+
+ ++$count;
+ $fingerprints[$count] = md5($details['key']);
+ }
+ }
+
+ // compress prior to encryption
+ if (!empty($this->compression)) {
+ $compress = new Compress($this->compression);
+ $value = $compress($value);
+ }
+
+ $crypt = openssl_seal($value, $encrypted, $encryptedkeys, $keys);
+ foreach ($keys as $key) {
+ openssl_free_key($key);
+ }
+
+ if ($crypt === false) {
+ throw new Exception\RuntimeException('Openssl was not able to encrypt your content with the given options');
+ }
+
+ $this->keys['envelope'] = $encryptedkeys;
+
+ // Pack data and envelope keys into single string
+ if ($this->package) {
+ $header = pack('n', count($this->keys['envelope']));
+ foreach ($this->keys['envelope'] as $key => $envKey) {
+ $header .= pack('H32n', $fingerprints[$key], strlen($envKey)) . $envKey;
+ }
+
+ $encrypted = $header . $encrypted;
+ }
+
+ return $encrypted;
+ }
+
+ /**
+ * Defined by Zend\Filter\FilterInterface
+ *
+ * Decrypts $value with the defined settings
+ *
+ * @param string $value Content to decrypt
+ * @return string The decrypted content
+ * @throws Exception\RuntimeException
+ */
+ public function decrypt($value)
+ {
+ $decrypted = "";
+ $envelope = current($this->getEnvelopeKey());
+
+ if (count($this->keys['private']) !== 1) {
+ throw new Exception\RuntimeException('Please give a private key for decryption with Openssl');
+ }
+
+ if (!$this->package && empty($envelope)) {
+ throw new Exception\RuntimeException('Please give an envelope key for decryption with Openssl');
+ }
+
+ foreach ($this->keys['private'] as $cert) {
+ $keys = openssl_pkey_get_private($cert, $this->getPassphrase());
+ }
+
+ if ($this->package) {
+ $details = openssl_pkey_get_details($keys);
+ if ($details !== false) {
+ $fingerprint = md5($details['key']);
+ } else {
+ $fingerprint = md5("ZendFramework");
+ }
+
+ $count = unpack('ncount', $value);
+ $count = $count['count'];
+ $length = 2;
+ for ($i = $count; $i > 0; --$i) {
+ $header = unpack('H32print/nsize', substr($value, $length, 18));
+ $length += 18;
+ if ($header['print'] == $fingerprint) {
+ $envelope = substr($value, $length, $header['size']);
+ }
+
+ $length += $header['size'];
+ }
+
+ // remainder of string is the value to decrypt
+ $value = substr($value, $length);
+ }
+
+ $crypt = openssl_open($value, $decrypted, $envelope, $keys);
+ openssl_free_key($keys);
+
+ if ($crypt === false) {
+ throw new Exception\RuntimeException('Openssl was not able to decrypt you content with the given options');
+ }
+
+ // decompress after decryption
+ if (!empty($this->compression)) {
+ $decompress = new Decompress($this->compression);
+ $decrypted = $decompress($decrypted);
+ }
+
+ return $decrypted;
+ }
+
+ /**
+ * Returns the adapter name
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return 'Openssl';
+ }
+}
diff --git a/library/Zend/Filter/Exception/BadMethodCallException.php b/library/Zend/Filter/Exception/BadMethodCallException.php
new file mode 100755
index 0000000000..ae0d3f843c
--- /dev/null
+++ b/library/Zend/Filter/Exception/BadMethodCallException.php
@@ -0,0 +1,14 @@
+filename;
+ }
+
+ /**
+ * Sets the new filename where the content will be stored
+ *
+ * @param string $filename (Optional) New filename to set
+ * @return self
+ */
+ public function setFilename($filename = null)
+ {
+ $this->filename = $filename;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend\Filter\FilterInterface
+ *
+ * Decrypts the file $value with the defined settings
+ *
+ * @param string|array $value Full path of file to change or $_FILES data array
+ * @return string|array The filename which has been set
+ * @throws Exception\InvalidArgumentException
+ * @throws Exception\RuntimeException
+ */
+ public function filter($value)
+ {
+ if (!is_scalar($value) && !is_array($value)) {
+ return $value;
+ }
+
+ // An uploaded file? Retrieve the 'tmp_name'
+ $isFileUpload = false;
+ if (is_array($value)) {
+ if (!isset($value['tmp_name'])) {
+ return $value;
+ }
+
+ $isFileUpload = true;
+ $uploadData = $value;
+ $value = $value['tmp_name'];
+ }
+
+
+ if (!file_exists($value)) {
+ throw new Exception\InvalidArgumentException("File '$value' not found");
+ }
+
+ if (!isset($this->filename)) {
+ $this->filename = $value;
+ }
+
+ if (file_exists($this->filename) and !is_writable($this->filename)) {
+ throw new Exception\RuntimeException("File '{$this->filename}' is not writable");
+ }
+
+ $content = file_get_contents($value);
+ if (!$content) {
+ throw new Exception\RuntimeException("Problem while reading file '$value'");
+ }
+
+ $decrypted = parent::filter($content);
+ $result = file_put_contents($this->filename, $decrypted);
+
+ if (!$result) {
+ throw new Exception\RuntimeException("Problem while writing file '{$this->filename}'");
+ }
+
+ if ($isFileUpload) {
+ $uploadData['tmp_name'] = $this->filename;
+ return $uploadData;
+ }
+ return $this->filename;
+ }
+}
diff --git a/library/Zend/Filter/File/Encrypt.php b/library/Zend/Filter/File/Encrypt.php
new file mode 100755
index 0000000000..9e32b4ce74
--- /dev/null
+++ b/library/Zend/Filter/File/Encrypt.php
@@ -0,0 +1,107 @@
+filename;
+ }
+
+ /**
+ * Sets the new filename where the content will be stored
+ *
+ * @param string $filename (Optional) New filename to set
+ * @return self
+ */
+ public function setFilename($filename = null)
+ {
+ $this->filename = $filename;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend\Filter\Filter
+ *
+ * Encrypts the file $value with the defined settings
+ *
+ * @param string|array $value Full path of file to change or $_FILES data array
+ * @return string|array The filename which has been set, or false when there were errors
+ * @throws Exception\InvalidArgumentException
+ * @throws Exception\RuntimeException
+ */
+ public function filter($value)
+ {
+ if (!is_scalar($value) && !is_array($value)) {
+ return $value;
+ }
+
+ // An uploaded file? Retrieve the 'tmp_name'
+ $isFileUpload = false;
+ if (is_array($value)) {
+ if (!isset($value['tmp_name'])) {
+ return $value;
+ }
+
+ $isFileUpload = true;
+ $uploadData = $value;
+ $value = $value['tmp_name'];
+ }
+
+ if (!file_exists($value)) {
+ throw new Exception\InvalidArgumentException("File '$value' not found");
+ }
+
+ if (!isset($this->filename)) {
+ $this->filename = $value;
+ }
+
+ if (file_exists($this->filename) and !is_writable($this->filename)) {
+ throw new Exception\RuntimeException("File '{$this->filename}' is not writable");
+ }
+
+ $content = file_get_contents($value);
+ if (!$content) {
+ throw new Exception\RuntimeException("Problem while reading file '$value'");
+ }
+
+ $encrypted = parent::filter($content);
+ $result = file_put_contents($this->filename, $encrypted);
+
+ if (!$result) {
+ throw new Exception\RuntimeException("Problem while writing file '{$this->filename}'");
+ }
+
+ if ($isFileUpload) {
+ $uploadData['tmp_name'] = $this->filename;
+ return $uploadData;
+ }
+ return $this->filename;
+ }
+}
diff --git a/library/Zend/Filter/File/LowerCase.php b/library/Zend/Filter/File/LowerCase.php
new file mode 100755
index 0000000000..15d31482e7
--- /dev/null
+++ b/library/Zend/Filter/File/LowerCase.php
@@ -0,0 +1,70 @@
+ Source filename or directory which will be renamed
+ * 'target' => Target filename or directory, the new name of the source file
+ * 'overwrite' => Shall existing files be overwritten ?
+ * 'randomize' => Shall target files have a random postfix attached?
+ *
+ * @param string|array|Traversable $options Target file or directory to be renamed
+ * @throws Exception\InvalidArgumentException
+ */
+ public function __construct($options)
+ {
+ if ($options instanceof Traversable) {
+ $options = ArrayUtils::iteratorToArray($options);
+ } elseif (is_string($options)) {
+ $options = array('target' => $options);
+ } elseif (!is_array($options)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid options argument provided to filter'
+ );
+ }
+
+ $this->setFile($options);
+ }
+
+ /**
+ * Returns the files to rename and their new name and location
+ *
+ * @return array
+ */
+ public function getFile()
+ {
+ return $this->files;
+ }
+
+ /**
+ * Sets a new file or directory as target, deleting existing ones
+ *
+ * Array accepts the following keys:
+ * 'source' => Source filename or directory which will be renamed
+ * 'target' => Target filename or directory, the new name of the sourcefile
+ * 'overwrite' => Shall existing files be overwritten?
+ * 'randomize' => Shall target files have a random postfix attached?
+ *
+ * @param string|array $options Old file or directory to be rewritten
+ * @return self
+ */
+ public function setFile($options)
+ {
+ $this->files = array();
+ $this->addFile($options);
+
+ return $this;
+ }
+
+ /**
+ * Adds a new file or directory as target to the existing ones
+ *
+ * Array accepts the following keys:
+ * 'source' => Source filename or directory which will be renamed
+ * 'target' => Target filename or directory, the new name of the sourcefile
+ * 'overwrite' => Shall existing files be overwritten?
+ * 'randomize' => Shall target files have a random postfix attached?
+ *
+ * @param string|array $options Old file or directory to be rewritten
+ * @return Rename
+ * @throws Exception\InvalidArgumentException
+ */
+ public function addFile($options)
+ {
+ if (is_string($options)) {
+ $options = array('target' => $options);
+ } elseif (!is_array($options)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid options to rename filter provided'
+ );
+ }
+
+ $this->_convertOptions($options);
+
+ return $this;
+ }
+
+ /**
+ * Returns only the new filename without moving it
+ * But existing files will be erased when the overwrite option is true
+ *
+ * @param string $value Full path of file to change
+ * @param bool $source Return internal informations
+ * @return string The new filename which has been set
+ * @throws Exception\InvalidArgumentException If the target file already exists.
+ */
+ public function getNewName($value, $source = false)
+ {
+ $file = $this->_getFileName($value);
+ if (!is_array($file)) {
+ return $file;
+ }
+
+ if ($file['source'] == $file['target']) {
+ return $value;
+ }
+
+ if (!file_exists($file['source'])) {
+ return $value;
+ }
+
+ if ($file['overwrite'] && file_exists($file['target'])) {
+ unlink($file['target']);
+ }
+
+ if (file_exists($file['target'])) {
+ throw new Exception\InvalidArgumentException(
+ sprintf("File '%s' could not be renamed. It already exists.", $value)
+ );
+ }
+
+ if ($source) {
+ return $file;
+ }
+
+ return $file['target'];
+ }
+
+ /**
+ * Defined by Zend\Filter\Filter
+ *
+ * Renames the file $value to the new name set before
+ * Returns the file $value, removing all but digit characters
+ *
+ * @param string|array $value Full path of file to change or $_FILES data array
+ * @throws Exception\RuntimeException
+ * @return string|array The new filename which has been set
+ */
+ public function filter($value)
+ {
+ if (!is_scalar($value) && !is_array($value)) {
+ return $value;
+ }
+
+ // An uploaded file? Retrieve the 'tmp_name'
+ $isFileUpload = false;
+ if (is_array($value)) {
+ if (!isset($value['tmp_name'])) {
+ return $value;
+ }
+
+ $isFileUpload = true;
+ $uploadData = $value;
+ $value = $value['tmp_name'];
+ }
+
+ $file = $this->getNewName($value, true);
+ if (is_string($file)) {
+ if ($isFileUpload) {
+ return $uploadData;
+ } else {
+ return $file;
+ }
+ }
+
+ $result = rename($file['source'], $file['target']);
+
+ if ($result !== true) {
+ throw new Exception\RuntimeException(
+ sprintf(
+ "File '%s' could not be renamed. " .
+ "An error occurred while processing the file.",
+ $value
+ )
+ );
+ }
+
+ if ($isFileUpload) {
+ $uploadData['tmp_name'] = $file['target'];
+ return $uploadData;
+ }
+ return $file['target'];
+ }
+
+ /**
+ * Internal method for creating the file array
+ * Supports single and nested arrays
+ *
+ * @param array $options
+ * @return array
+ */
+ protected function _convertOptions($options)
+ {
+ $files = array();
+ foreach ($options as $key => $value) {
+ if (is_array($value)) {
+ $this->_convertOptions($value);
+ continue;
+ }
+
+ switch ($key) {
+ case "source":
+ $files['source'] = (string) $value;
+ break;
+
+ case 'target' :
+ $files['target'] = (string) $value;
+ break;
+
+ case 'overwrite' :
+ $files['overwrite'] = (bool) $value;
+ break;
+
+ case 'randomize' :
+ $files['randomize'] = (bool) $value;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (empty($files)) {
+ return $this;
+ }
+
+ if (empty($files['source'])) {
+ $files['source'] = '*';
+ }
+
+ if (empty($files['target'])) {
+ $files['target'] = '*';
+ }
+
+ if (empty($files['overwrite'])) {
+ $files['overwrite'] = false;
+ }
+
+ if (empty($files['randomize'])) {
+ $files['randomize'] = false;
+ }
+
+ $found = false;
+ foreach ($this->files as $key => $value) {
+ if ($value['source'] == $files['source']) {
+ $this->files[$key] = $files;
+ $found = true;
+ }
+ }
+
+ if (!$found) {
+ $count = count($this->files);
+ $this->files[$count] = $files;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Internal method to resolve the requested source
+ * and return all other related parameters
+ *
+ * @param string $file Filename to get the informations for
+ * @return array|string
+ */
+ protected function _getFileName($file)
+ {
+ $rename = array();
+ foreach ($this->files as $value) {
+ if ($value['source'] == '*') {
+ if (!isset($rename['source'])) {
+ $rename = $value;
+ $rename['source'] = $file;
+ }
+ }
+
+ if ($value['source'] == $file) {
+ $rename = $value;
+ break;
+ }
+ }
+
+ if (!isset($rename['source'])) {
+ return $file;
+ }
+
+ if (!isset($rename['target']) || $rename['target'] == '*') {
+ $rename['target'] = $rename['source'];
+ }
+
+ if (is_dir($rename['target'])) {
+ $name = basename($rename['source']);
+ $last = $rename['target'][strlen($rename['target']) - 1];
+ if (($last != '/') && ($last != '\\')) {
+ $rename['target'] .= DIRECTORY_SEPARATOR;
+ }
+
+ $rename['target'] .= $name;
+ }
+
+ if ($rename['randomize']) {
+ $info = pathinfo($rename['target']);
+ $newTarget = $info['dirname'] . DIRECTORY_SEPARATOR .
+ $info['filename'] . uniqid('_');
+ if (isset($info['extension'])) {
+ $newTarget .= '.' . $info['extension'];
+ }
+ $rename['target'] = $newTarget;
+ }
+
+ return $rename;
+ }
+}
diff --git a/library/Zend/Filter/File/RenameUpload.php b/library/Zend/Filter/File/RenameUpload.php
new file mode 100755
index 0000000000..e0b5d0b5e9
--- /dev/null
+++ b/library/Zend/Filter/File/RenameUpload.php
@@ -0,0 +1,313 @@
+ null,
+ 'use_upload_name' => false,
+ 'use_upload_extension' => false,
+ 'overwrite' => false,
+ 'randomize' => false,
+ );
+
+ /**
+ * Store already filtered values, so we can filter multiple
+ * times the same file without being block by move_uploaded_file
+ * internal checks
+ *
+ * @var array
+ */
+ protected $alreadyFiltered = array();
+
+ /**
+ * Constructor
+ *
+ * @param array|string $targetOrOptions The target file path or an options array
+ */
+ public function __construct($targetOrOptions)
+ {
+ if (is_array($targetOrOptions)) {
+ $this->setOptions($targetOrOptions);
+ } else {
+ $this->setTarget($targetOrOptions);
+ }
+ }
+
+ /**
+ * @param string $target Target file path or directory
+ * @return self
+ */
+ public function setTarget($target)
+ {
+ if (!is_string($target)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid target, must be a string'
+ );
+ }
+ $this->options['target'] = $target;
+ return $this;
+ }
+
+ /**
+ * @return string Target file path or directory
+ */
+ public function getTarget()
+ {
+ return $this->options['target'];
+ }
+
+ /**
+ * @param bool $flag When true, this filter will use the $_FILES['name']
+ * as the target filename.
+ * Otherwise, it uses the default 'target' rules.
+ * @return self
+ */
+ public function setUseUploadName($flag = true)
+ {
+ $this->options['use_upload_name'] = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getUseUploadName()
+ {
+ return $this->options['use_upload_name'];
+ }
+
+ /**
+ * @param bool $flag When true, this filter will use the original file
+ * extension for the target filename
+ * @return self
+ */
+ public function setUseUploadExtension($flag = true)
+ {
+ $this->options['use_upload_extension'] = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getUseUploadExtension()
+ {
+ return $this->options['use_upload_extension'];
+ }
+
+ /**
+ * @param bool $flag Shall existing files be overwritten?
+ * @return self
+ */
+ public function setOverwrite($flag = true)
+ {
+ $this->options['overwrite'] = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getOverwrite()
+ {
+ return $this->options['overwrite'];
+ }
+
+ /**
+ * @param bool $flag Shall target files have a random postfix attached?
+ * @return self
+ */
+ public function setRandomize($flag = true)
+ {
+ $this->options['randomize'] = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getRandomize()
+ {
+ return $this->options['randomize'];
+ }
+
+ /**
+ * Defined by Zend\Filter\Filter
+ *
+ * Renames the file $value to the new name set before
+ * Returns the file $value, removing all but digit characters
+ *
+ * @param string|array $value Full path of file to change or $_FILES data array
+ * @throws Exception\RuntimeException
+ * @return string|array The new filename which has been set, or false when there were errors
+ */
+ public function filter($value)
+ {
+ if (!is_scalar($value) && !is_array($value)) {
+ return $value;
+ }
+
+ // An uploaded file? Retrieve the 'tmp_name'
+ $isFileUpload = false;
+ if (is_array($value)) {
+ if (!isset($value['tmp_name'])) {
+ return $value;
+ }
+
+ $isFileUpload = true;
+ $uploadData = $value;
+ $sourceFile = $value['tmp_name'];
+ } else {
+ $uploadData = array(
+ 'tmp_name' => $value,
+ 'name' => $value,
+ );
+ $sourceFile = $value;
+ }
+
+ if (isset($this->alreadyFiltered[$sourceFile])) {
+ return $this->alreadyFiltered[$sourceFile];
+ }
+
+ $targetFile = $this->getFinalTarget($uploadData);
+ if (!file_exists($sourceFile) || $sourceFile == $targetFile) {
+ return $value;
+ }
+
+ $this->checkFileExists($targetFile);
+ $this->moveUploadedFile($sourceFile, $targetFile);
+
+ $return = $targetFile;
+ if ($isFileUpload) {
+ $return = $uploadData;
+ $return['tmp_name'] = $targetFile;
+ }
+
+ $this->alreadyFiltered[$sourceFile] = $return;
+
+ return $return;
+ }
+
+ /**
+ * @param string $sourceFile Source file path
+ * @param string $targetFile Target file path
+ * @throws Exception\RuntimeException
+ * @return bool
+ */
+ protected function moveUploadedFile($sourceFile, $targetFile)
+ {
+ ErrorHandler::start();
+ $result = move_uploaded_file($sourceFile, $targetFile);
+ $warningException = ErrorHandler::stop();
+ if (!$result || null !== $warningException) {
+ throw new Exception\RuntimeException(
+ sprintf("File '%s' could not be renamed. An error occurred while processing the file.", $sourceFile),
+ 0, $warningException
+ );
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param string $targetFile Target file path
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function checkFileExists($targetFile)
+ {
+ if (file_exists($targetFile)) {
+ if ($this->getOverwrite()) {
+ unlink($targetFile);
+ } else {
+ throw new Exception\InvalidArgumentException(
+ sprintf("File '%s' could not be renamed. It already exists.", $targetFile)
+ );
+ }
+ }
+ }
+
+ /**
+ * @param array $uploadData $_FILES array
+ * @return string
+ */
+ protected function getFinalTarget($uploadData)
+ {
+ $source = $uploadData['tmp_name'];
+ $target = $this->getTarget();
+ if (!isset($target) || $target == '*') {
+ $target = $source;
+ }
+
+ // Get the target directory
+ if (is_dir($target)) {
+ $targetDir = $target;
+ $last = $target[strlen($target) - 1];
+ if (($last != '/') && ($last != '\\')) {
+ $targetDir .= DIRECTORY_SEPARATOR;
+ }
+ } else {
+ $info = pathinfo($target);
+ $targetDir = $info['dirname'] . DIRECTORY_SEPARATOR;
+ }
+
+ // Get the target filename
+ if ($this->getUseUploadName()) {
+ $targetFile = basename($uploadData['name']);
+ } elseif (!is_dir($target)) {
+ $targetFile = basename($target);
+ if ($this->getUseUploadExtension() && !$this->getRandomize()) {
+ $targetInfo = pathinfo($targetFile);
+ $sourceinfo = pathinfo($uploadData['name']);
+ if (isset($sourceinfo['extension'])) {
+ $targetFile = $targetInfo['filename'] . '.' . $sourceinfo['extension'];
+ }
+ }
+ } else {
+ $targetFile = basename($source);
+ }
+
+ if ($this->getRandomize()) {
+ $targetFile = $this->applyRandomToFilename($uploadData['name'], $targetFile);
+ }
+
+ return $targetDir . $targetFile;
+ }
+
+ /**
+ * @param string $source
+ * @param string $filename
+ * @return string
+ */
+ protected function applyRandomToFilename($source, $filename)
+ {
+ $info = pathinfo($filename);
+ $filename = $info['filename'] . uniqid('_');
+
+ $sourceinfo = pathinfo($source);
+
+ $extension = '';
+ if ($this->getUseUploadExtension() === true && isset($sourceinfo['extension'])) {
+ $extension .= '.' . $sourceinfo['extension'];
+ } elseif (isset($info['extension'])) {
+ $extension .= '.' . $info['extension'];
+ }
+
+ return $filename . $extension;
+ }
+}
diff --git a/library/Zend/Filter/File/UpperCase.php b/library/Zend/Filter/File/UpperCase.php
new file mode 100755
index 0000000000..22bf09279e
--- /dev/null
+++ b/library/Zend/Filter/File/UpperCase.php
@@ -0,0 +1,70 @@
+filters = new PriorityQueue();
+
+ if (null !== $options) {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * @param array|Traversable $options
+ * @return self
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setOptions($options)
+ {
+ if (!is_array($options) && !$options instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Expected array or Traversable; received "%s"',
+ (is_object($options) ? get_class($options) : gettype($options))
+ ));
+ }
+
+ foreach ($options as $key => $value) {
+ switch (strtolower($key)) {
+ case 'callbacks':
+ foreach ($value as $spec) {
+ $callback = isset($spec['callback']) ? $spec['callback'] : false;
+ $priority = isset($spec['priority']) ? $spec['priority'] : static::DEFAULT_PRIORITY;
+ if ($callback) {
+ $this->attach($callback, $priority);
+ }
+ }
+ break;
+ case 'filters':
+ foreach ($value as $spec) {
+ $name = isset($spec['name']) ? $spec['name'] : false;
+ $options = isset($spec['options']) ? $spec['options'] : array();
+ $priority = isset($spec['priority']) ? $spec['priority'] : static::DEFAULT_PRIORITY;
+ if ($name) {
+ $this->attachByName($name, $options, $priority);
+ }
+ }
+ break;
+ default:
+ // ignore other options
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the count of attached filters
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->filters);
+ }
+
+ /**
+ * Get plugin manager instance
+ *
+ * @return FilterPluginManager
+ */
+ public function getPluginManager()
+ {
+ if (!$this->plugins) {
+ $this->setPluginManager(new FilterPluginManager());
+ }
+ return $this->plugins;
+ }
+
+ /**
+ * Set plugin manager instance
+ *
+ * @param FilterPluginManager $plugins
+ * @return self
+ */
+ public function setPluginManager(FilterPluginManager $plugins)
+ {
+ $this->plugins = $plugins;
+ return $this;
+ }
+
+ /**
+ * Retrieve a filter plugin by name
+ *
+ * @param mixed $name
+ * @param array $options
+ * @return FilterInterface
+ */
+ public function plugin($name, array $options = array())
+ {
+ $plugins = $this->getPluginManager();
+ return $plugins->get($name, $options);
+ }
+
+ /**
+ * Attach a filter to the chain
+ *
+ * @param callable|FilterInterface $callback A Filter implementation or valid PHP callback
+ * @param int $priority Priority at which to enqueue filter; defaults to 1000 (higher executes earlier)
+ * @throws Exception\InvalidArgumentException
+ * @return self
+ */
+ public function attach($callback, $priority = self::DEFAULT_PRIORITY)
+ {
+ if (!is_callable($callback)) {
+ if (!$callback instanceof FilterInterface) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Expected a valid PHP callback; received "%s"',
+ (is_object($callback) ? get_class($callback) : gettype($callback))
+ ));
+ }
+ $callback = array($callback, 'filter');
+ }
+ $this->filters->insert($callback, $priority);
+ return $this;
+ }
+
+ /**
+ * Attach a filter to the chain using a short name
+ *
+ * Retrieves the filter from the attached plugin manager, and then calls attach()
+ * with the retrieved instance.
+ *
+ * @param string $name
+ * @param mixed $options
+ * @param int $priority Priority at which to enqueue filter; defaults to 1000 (higher executes earlier)
+ * @return self
+ */
+ public function attachByName($name, $options = array(), $priority = self::DEFAULT_PRIORITY)
+ {
+ if (!is_array($options)) {
+ $options = (array) $options;
+ } elseif (empty($options)) {
+ $options = null;
+ }
+ $filter = $this->getPluginManager()->get($name, $options);
+ return $this->attach($filter, $priority);
+ }
+
+ /**
+ * Merge the filter chain with the one given in parameter
+ *
+ * @param FilterChain $filterChain
+ * @return self
+ */
+ public function merge(FilterChain $filterChain)
+ {
+ foreach ($filterChain->filters->toArray(PriorityQueue::EXTR_BOTH) as $item) {
+ $this->attach($item['data'], $item['priority']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get all the filters
+ *
+ * @return PriorityQueue
+ */
+ public function getFilters()
+ {
+ return $this->filters;
+ }
+
+ /**
+ * Returns $value filtered through each filter in the chain
+ *
+ * Filters are run in the order in which they were added to the chain (FIFO)
+ *
+ * @param mixed $value
+ * @return mixed
+ */
+ public function filter($value)
+ {
+ $chain = clone $this->filters;
+
+ $valueFiltered = $value;
+ foreach ($chain as $filter) {
+ $valueFiltered = call_user_func($filter, $valueFiltered);
+ }
+
+ return $valueFiltered;
+ }
+
+ /**
+ * Clone filters
+ */
+ public function __clone()
+ {
+ $this->filters = clone $this->filters;
+ }
+
+ /**
+ * Prepare filter chain for serialization
+ *
+ * Plugin manager (property 'plugins') cannot
+ * be serialized. On wakeup the property remains unset
+ * and next invocation to getPluginManager() sets
+ * the default plugin manager instance (FilterPluginManager).
+ */
+ public function __sleep()
+ {
+ return array('filters');
+ }
+}
diff --git a/library/Zend/Filter/FilterInterface.php b/library/Zend/Filter/FilterInterface.php
new file mode 100755
index 0000000000..2b3c163538
--- /dev/null
+++ b/library/Zend/Filter/FilterInterface.php
@@ -0,0 +1,22 @@
+ 'Zend\I18n\Filter\Alnum',
+ 'alpha' => 'Zend\I18n\Filter\Alpha',
+ 'basename' => 'Zend\Filter\BaseName',
+ 'boolean' => 'Zend\Filter\Boolean',
+ 'callback' => 'Zend\Filter\Callback',
+ 'compress' => 'Zend\Filter\Compress',
+ 'compressbz2' => 'Zend\Filter\Compress\Bz2',
+ 'compressgz' => 'Zend\Filter\Compress\Gz',
+ 'compresslzf' => 'Zend\Filter\Compress\Lzf',
+ 'compressrar' => 'Zend\Filter\Compress\Rar',
+ 'compresssnappy' => 'Zend\Filter\Compress\Snappy',
+ 'compresstar' => 'Zend\Filter\Compress\Tar',
+ 'compresszip' => 'Zend\Filter\Compress\Zip',
+ 'datetimeformatter' => 'Zend\Filter\DateTimeFormatter',
+ 'decompress' => 'Zend\Filter\Decompress',
+ 'decrypt' => 'Zend\Filter\Decrypt',
+ 'digits' => 'Zend\Filter\Digits',
+ 'dir' => 'Zend\Filter\Dir',
+ 'encrypt' => 'Zend\Filter\Encrypt',
+ 'encryptblockcipher' => 'Zend\Filter\Encrypt\BlockCipher',
+ 'encryptopenssl' => 'Zend\Filter\Encrypt\Openssl',
+ 'filedecrypt' => 'Zend\Filter\File\Decrypt',
+ 'fileencrypt' => 'Zend\Filter\File\Encrypt',
+ 'filelowercase' => 'Zend\Filter\File\LowerCase',
+ 'filerename' => 'Zend\Filter\File\Rename',
+ 'filerenameupload' => 'Zend\Filter\File\RenameUpload',
+ 'fileuppercase' => 'Zend\Filter\File\UpperCase',
+ 'htmlentities' => 'Zend\Filter\HtmlEntities',
+ 'inflector' => 'Zend\Filter\Inflector',
+ 'int' => 'Zend\Filter\Int',
+ 'null' => 'Zend\Filter\Null',
+ 'numberformat' => 'Zend\I18n\Filter\NumberFormat',
+ 'numberparse' => 'Zend\I18n\Filter\NumberParse',
+ 'pregreplace' => 'Zend\Filter\PregReplace',
+ 'realpath' => 'Zend\Filter\RealPath',
+ 'stringtolower' => 'Zend\Filter\StringToLower',
+ 'stringtoupper' => 'Zend\Filter\StringToUpper',
+ 'stringtrim' => 'Zend\Filter\StringTrim',
+ 'stripnewlines' => 'Zend\Filter\StripNewlines',
+ 'striptags' => 'Zend\Filter\StripTags',
+ 'urinormalize' => 'Zend\Filter\UriNormalize',
+ 'wordcamelcasetodash' => 'Zend\Filter\Word\CamelCaseToDash',
+ 'wordcamelcasetoseparator' => 'Zend\Filter\Word\CamelCaseToSeparator',
+ 'wordcamelcasetounderscore' => 'Zend\Filter\Word\CamelCaseToUnderscore',
+ 'worddashtocamelcase' => 'Zend\Filter\Word\DashToCamelCase',
+ 'worddashtoseparator' => 'Zend\Filter\Word\DashToSeparator',
+ 'worddashtounderscore' => 'Zend\Filter\Word\DashToUnderscore',
+ 'wordseparatortocamelcase' => 'Zend\Filter\Word\SeparatorToCamelCase',
+ 'wordseparatortodash' => 'Zend\Filter\Word\SeparatorToDash',
+ 'wordseparatortoseparator' => 'Zend\Filter\Word\SeparatorToSeparator',
+ 'wordunderscoretocamelcase' => 'Zend\Filter\Word\UnderscoreToCamelCase',
+ 'wordunderscoretodash' => 'Zend\Filter\Word\UnderscoreToDash',
+ 'wordunderscoretoseparator' => 'Zend\Filter\Word\UnderscoreToSeparator',
+ );
+
+ /**
+ * Whether or not to share by default; default to false
+ *
+ * @var bool
+ */
+ protected $shareByDefault = false;
+
+ /**
+ * Validate the plugin
+ *
+ * Checks that the filter loaded is either a valid callback or an instance
+ * of FilterInterface.
+ *
+ * @param mixed $plugin
+ * @return void
+ * @throws Exception\RuntimeException if invalid
+ */
+ public function validatePlugin($plugin)
+ {
+ if ($plugin instanceof FilterInterface) {
+ // we're okay
+ return;
+ }
+ if (is_callable($plugin)) {
+ // also okay
+ return;
+ }
+
+ throw new Exception\RuntimeException(sprintf(
+ 'Plugin of type %s is invalid; must implement %s\FilterInterface or be callable',
+ (is_object($plugin) ? get_class($plugin) : gettype($plugin)),
+ __NAMESPACE__
+ ));
+ }
+}
diff --git a/library/Zend/Filter/HtmlEntities.php b/library/Zend/Filter/HtmlEntities.php
new file mode 100755
index 0000000000..2abff010b1
--- /dev/null
+++ b/library/Zend/Filter/HtmlEntities.php
@@ -0,0 +1,203 @@
+setQuoteStyle($options['quotestyle']);
+ $this->setEncoding($options['encoding']);
+ $this->setDoubleQuote($options['doublequote']);
+ }
+
+ /**
+ * Returns the quoteStyle option
+ *
+ * @return int
+ */
+ public function getQuoteStyle()
+ {
+ return $this->quoteStyle;
+ }
+
+ /**
+ * Sets the quoteStyle option
+ *
+ * @param int $quoteStyle
+ * @return self Provides a fluent interface
+ */
+ public function setQuoteStyle($quoteStyle)
+ {
+ $this->quoteStyle = $quoteStyle;
+ return $this;
+ }
+
+
+ /**
+ * Get encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->encoding;
+ }
+
+ /**
+ * Set encoding
+ *
+ * @param string $value
+ * @return self
+ */
+ public function setEncoding($value)
+ {
+ $this->encoding = (string) $value;
+ return $this;
+ }
+
+ /**
+ * Returns the charSet option
+ *
+ * Proxies to {@link getEncoding()}
+ *
+ * @return string
+ */
+ public function getCharSet()
+ {
+ return $this->getEncoding();
+ }
+
+ /**
+ * Sets the charSet option
+ *
+ * Proxies to {@link setEncoding()}
+ *
+ * @param string $charSet
+ * @return self Provides a fluent interface
+ */
+ public function setCharSet($charSet)
+ {
+ return $this->setEncoding($charSet);
+ }
+
+ /**
+ * Returns the doubleQuote option
+ *
+ * @return bool
+ */
+ public function getDoubleQuote()
+ {
+ return $this->doubleQuote;
+ }
+
+ /**
+ * Sets the doubleQuote option
+ *
+ * @param bool $doubleQuote
+ * @return self Provides a fluent interface
+ */
+ public function setDoubleQuote($doubleQuote)
+ {
+ $this->doubleQuote = (bool) $doubleQuote;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend\Filter\FilterInterface
+ *
+ * Returns the string $value, converting characters to their corresponding HTML entity
+ * equivalents where they exist
+ *
+ * If the value provided is non-scalar, the value will remain unfiltered
+ *
+ * @param string $value
+ * @return string|mixed
+ * @throws Exception\DomainException on encoding mismatches
+ */
+ public function filter($value)
+ {
+ if (!is_scalar($value)) {
+ return $value;
+ }
+ $value = (string) $value;
+
+ $filtered = htmlentities($value, $this->getQuoteStyle(), $this->getEncoding(), $this->getDoubleQuote());
+ if (strlen($value) && !strlen($filtered)) {
+ if (!function_exists('iconv')) {
+ throw new Exception\DomainException('Encoding mismatch has resulted in htmlentities errors');
+ }
+ $enc = $this->getEncoding();
+ $value = iconv('', $this->getEncoding() . '//IGNORE', $value);
+ $filtered = htmlentities($value, $this->getQuoteStyle(), $enc, $this->getDoubleQuote());
+ if (!strlen($filtered)) {
+ throw new Exception\DomainException('Encoding mismatch has resulted in htmlentities errors');
+ }
+ }
+ return $filtered;
+ }
+}
diff --git a/library/Zend/Filter/Inflector.php b/library/Zend/Filter/Inflector.php
new file mode 100755
index 0000000000..4120a68f2e
--- /dev/null
+++ b/library/Zend/Filter/Inflector.php
@@ -0,0 +1,472 @@
+setOptions($options);
+ }
+
+ /**
+ * Retrieve plugin manager
+ *
+ * @return FilterPluginManager
+ */
+ public function getPluginManager()
+ {
+ if (!$this->pluginManager instanceof FilterPluginManager) {
+ $this->setPluginManager(new FilterPluginManager());
+ }
+
+ return $this->pluginManager;
+ }
+
+ /**
+ * Set plugin manager
+ *
+ * @param FilterPluginManager $manager
+ * @return self
+ */
+ public function setPluginManager(FilterPluginManager $manager)
+ {
+ $this->pluginManager = $manager;
+ return $this;
+ }
+
+ /**
+ * Set options
+ *
+ * @param array|Traversable $options
+ * @return self
+ */
+ public function setOptions($options)
+ {
+ if ($options instanceof Traversable) {
+ $options = ArrayUtils::iteratorToArray($options);
+ }
+
+ // Set plugin manager
+ if (array_key_exists('pluginManager', $options)) {
+ if (is_scalar($options['pluginManager']) && class_exists($options['pluginManager'])) {
+ $options['pluginManager'] = new $options['pluginManager'];
+ }
+ $this->setPluginManager($options['pluginManager']);
+ }
+
+ if (array_key_exists('throwTargetExceptionsOn', $options)) {
+ $this->setThrowTargetExceptionsOn($options['throwTargetExceptionsOn']);
+ }
+
+ if (array_key_exists('targetReplacementIdentifier', $options)) {
+ $this->setTargetReplacementIdentifier($options['targetReplacementIdentifier']);
+ }
+
+ if (array_key_exists('target', $options)) {
+ $this->setTarget($options['target']);
+ }
+
+ if (array_key_exists('rules', $options)) {
+ $this->addRules($options['rules']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set Whether or not the inflector should throw an exception when a replacement
+ * identifier is still found within an inflected target.
+ *
+ * @param bool $throwTargetExceptionsOn
+ * @return self
+ */
+ public function setThrowTargetExceptionsOn($throwTargetExceptionsOn)
+ {
+ $this->throwTargetExceptionsOn = ($throwTargetExceptionsOn == true) ? true : false;
+ return $this;
+ }
+
+ /**
+ * Will exceptions be thrown?
+ *
+ * @return bool
+ */
+ public function isThrowTargetExceptionsOn()
+ {
+ return $this->throwTargetExceptionsOn;
+ }
+
+ /**
+ * Set the Target Replacement Identifier, by default ':'
+ *
+ * @param string $targetReplacementIdentifier
+ * @return self
+ */
+ public function setTargetReplacementIdentifier($targetReplacementIdentifier)
+ {
+ if ($targetReplacementIdentifier) {
+ $this->targetReplacementIdentifier = (string) $targetReplacementIdentifier;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Target Replacement Identifier
+ *
+ * @return string
+ */
+ public function getTargetReplacementIdentifier()
+ {
+ return $this->targetReplacementIdentifier;
+ }
+
+ /**
+ * Set a Target
+ * ex: 'scripts/:controller/:action.:suffix'
+ *
+ * @param string $target
+ * @return self
+ */
+ public function setTarget($target)
+ {
+ $this->target = (string) $target;
+ return $this;
+ }
+
+ /**
+ * Retrieve target
+ *
+ * @return string
+ */
+ public function getTarget()
+ {
+ return $this->target;
+ }
+
+ /**
+ * Set Target Reference
+ *
+ * @param string $target
+ * @return self
+ */
+ public function setTargetReference(&$target)
+ {
+ $this->target =& $target;
+ return $this;
+ }
+
+ /**
+ * Is the same as calling addRules() with the exception that it
+ * clears the rules before adding them.
+ *
+ * @param array $rules
+ * @return self
+ */
+ public function setRules(array $rules)
+ {
+ $this->clearRules();
+ $this->addRules($rules);
+ return $this;
+ }
+
+ /**
+ * Multi-call to setting filter rules.
+ *
+ * If prefixed with a ":" (colon), a filter rule will be added. If not
+ * prefixed, a static replacement will be added.
+ *
+ * ex:
+ * array(
+ * ':controller' => array('CamelCaseToUnderscore', 'StringToLower'),
+ * ':action' => array('CamelCaseToUnderscore', 'StringToLower'),
+ * 'suffix' => 'phtml'
+ * );
+ *
+ * @param array $rules
+ * @return self
+ */
+ public function addRules(array $rules)
+ {
+ $keys = array_keys($rules);
+ foreach ($keys as $spec) {
+ if ($spec[0] == ':') {
+ $this->addFilterRule($spec, $rules[$spec]);
+ } else {
+ $this->setStaticRule($spec, $rules[$spec]);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get rules
+ *
+ * By default, returns all rules. If a $spec is provided, will return those
+ * rules if found, false otherwise.
+ *
+ * @param string $spec
+ * @return array|false
+ */
+ public function getRules($spec = null)
+ {
+ if (null !== $spec) {
+ $spec = $this->_normalizeSpec($spec);
+ if (isset($this->rules[$spec])) {
+ return $this->rules[$spec];
+ }
+ return false;
+ }
+
+ return $this->rules;
+ }
+
+ /**
+ * Returns a rule set by setFilterRule(), a numeric index must be provided
+ *
+ * @param string $spec
+ * @param int $index
+ * @return FilterInterface|false
+ */
+ public function getRule($spec, $index)
+ {
+ $spec = $this->_normalizeSpec($spec);
+ if (isset($this->rules[$spec]) && is_array($this->rules[$spec])) {
+ if (isset($this->rules[$spec][$index])) {
+ return $this->rules[$spec][$index];
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Clears the rules currently in the inflector
+ *
+ * @return self
+ */
+ public function clearRules()
+ {
+ $this->rules = array();
+ return $this;
+ }
+
+ /**
+ * Set a filtering rule for a spec. $ruleSet can be a string, Filter object
+ * or an array of strings or filter objects.
+ *
+ * @param string $spec
+ * @param array|string|\Zend\Filter\FilterInterface $ruleSet
+ * @return self
+ */
+ public function setFilterRule($spec, $ruleSet)
+ {
+ $spec = $this->_normalizeSpec($spec);
+ $this->rules[$spec] = array();
+ return $this->addFilterRule($spec, $ruleSet);
+ }
+
+ /**
+ * Add a filter rule for a spec
+ *
+ * @param mixed $spec
+ * @param mixed $ruleSet
+ * @return self
+ */
+ public function addFilterRule($spec, $ruleSet)
+ {
+ $spec = $this->_normalizeSpec($spec);
+ if (!isset($this->rules[$spec])) {
+ $this->rules[$spec] = array();
+ }
+
+ if (!is_array($ruleSet)) {
+ $ruleSet = array($ruleSet);
+ }
+
+ if (is_string($this->rules[$spec])) {
+ $temp = $this->rules[$spec];
+ $this->rules[$spec] = array();
+ $this->rules[$spec][] = $temp;
+ }
+
+ foreach ($ruleSet as $rule) {
+ $this->rules[$spec][] = $this->_getRule($rule);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set a static rule for a spec. This is a single string value
+ *
+ * @param string $name
+ * @param string $value
+ * @return self
+ */
+ public function setStaticRule($name, $value)
+ {
+ $name = $this->_normalizeSpec($name);
+ $this->rules[$name] = (string) $value;
+ return $this;
+ }
+
+ /**
+ * Set Static Rule Reference.
+ *
+ * This allows a consuming class to pass a property or variable
+ * in to be referenced when its time to build the output string from the
+ * target.
+ *
+ * @param string $name
+ * @param mixed $reference
+ * @return self
+ */
+ public function setStaticRuleReference($name, &$reference)
+ {
+ $name = $this->_normalizeSpec($name);
+ $this->rules[$name] =& $reference;
+ return $this;
+ }
+
+ /**
+ * Inflect
+ *
+ * @param string|array $source
+ * @throws Exception\RuntimeException
+ * @return string
+ */
+ public function filter($source)
+ {
+ // clean source
+ foreach ((array) $source as $sourceName => $sourceValue) {
+ $source[ltrim($sourceName, ':')] = $sourceValue;
+ }
+
+ $pregQuotedTargetReplacementIdentifier = preg_quote($this->targetReplacementIdentifier, '#');
+ $processedParts = array();
+
+ foreach ($this->rules as $ruleName => $ruleValue) {
+ if (isset($source[$ruleName])) {
+ if (is_string($ruleValue)) {
+ // overriding the set rule
+ $processedParts['#' . $pregQuotedTargetReplacementIdentifier . $ruleName . '#'] = str_replace('\\', '\\\\', $source[$ruleName]);
+ } elseif (is_array($ruleValue)) {
+ $processedPart = $source[$ruleName];
+ foreach ($ruleValue as $ruleFilter) {
+ $processedPart = $ruleFilter($processedPart);
+ }
+ $processedParts['#' . $pregQuotedTargetReplacementIdentifier . $ruleName . '#'] = str_replace('\\', '\\\\', $processedPart);
+ }
+ } elseif (is_string($ruleValue)) {
+ $processedParts['#' . $pregQuotedTargetReplacementIdentifier . $ruleName . '#'] = str_replace('\\', '\\\\', $ruleValue);
+ }
+ }
+
+ // all of the values of processedParts would have been str_replace('\\', '\\\\', ..)'d to disable preg_replace backreferences
+ $inflectedTarget = preg_replace(array_keys($processedParts), array_values($processedParts), $this->target);
+
+ if ($this->throwTargetExceptionsOn && (preg_match('#(?=' . $pregQuotedTargetReplacementIdentifier.'[A-Za-z]{1})#', $inflectedTarget) == true)) {
+ throw new Exception\RuntimeException('A replacement identifier ' . $this->targetReplacementIdentifier . ' was found inside the inflected target, perhaps a rule was not satisfied with a target source? Unsatisfied inflected target: ' . $inflectedTarget);
+ }
+
+ return $inflectedTarget;
+ }
+
+ /**
+ * Normalize spec string
+ *
+ * @param string $spec
+ * @return string
+ */
+ protected function _normalizeSpec($spec)
+ {
+ return ltrim((string) $spec, ':&');
+ }
+
+ /**
+ * Resolve named filters and convert them to filter objects.
+ *
+ * @param string $rule
+ * @return FilterInterface
+ */
+ protected function _getRule($rule)
+ {
+ if ($rule instanceof FilterInterface) {
+ return $rule;
+ }
+
+ $rule = (string) $rule;
+ return $this->getPluginManager()->get($rule);
+ }
+}
diff --git a/library/Zend/Filter/Int.php b/library/Zend/Filter/Int.php
new file mode 100755
index 0000000000..0f2b80dba4
--- /dev/null
+++ b/library/Zend/Filter/Int.php
@@ -0,0 +1,33 @@
+ 'boolean',
+ self::TYPE_INTEGER => 'integer',
+ self::TYPE_EMPTY_ARRAY => 'array',
+ self::TYPE_STRING => 'string',
+ self::TYPE_ZERO_STRING => 'zero',
+ self::TYPE_FLOAT => 'float',
+ self::TYPE_ALL => 'all',
+ );
+
+ /**
+ * @var array
+ */
+ protected $options = array(
+ 'type' => self::TYPE_ALL,
+ );
+
+ /**
+ * Constructor
+ *
+ * @param string|array|Traversable $typeOrOptions OPTIONAL
+ */
+ public function __construct($typeOrOptions = null)
+ {
+ if ($typeOrOptions !== null) {
+ if ($typeOrOptions instanceof Traversable) {
+ $typeOrOptions = iterator_to_array($typeOrOptions);
+ }
+
+ if (is_array($typeOrOptions)) {
+ if (isset($typeOrOptions['type'])) {
+ $this->setOptions($typeOrOptions);
+ } else {
+ $this->setType($typeOrOptions);
+ }
+ } else {
+ $this->setType($typeOrOptions);
+ }
+ }
+ }
+
+ /**
+ * Set boolean types
+ *
+ * @param int|array $type
+ * @throws Exception\InvalidArgumentException
+ * @return self
+ */
+ public function setType($type = null)
+ {
+ if (is_array($type)) {
+ $detected = 0;
+ foreach ($type as $value) {
+ if (is_int($value)) {
+ $detected += $value;
+ } elseif (in_array($value, $this->constants)) {
+ $detected += array_search($value, $this->constants);
+ }
+ }
+
+ $type = $detected;
+ } elseif (is_string($type) && in_array($type, $this->constants)) {
+ $type = array_search($type, $this->constants);
+ }
+
+ if (!is_int($type) || ($type < 0) || ($type > self::TYPE_ALL)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Unknown type value "%s" (%s)',
+ $type,
+ gettype($type)
+ ));
+ }
+
+ $this->options['type'] = $type;
+ return $this;
+ }
+
+ /**
+ * Returns defined boolean types
+ *
+ * @return int
+ */
+ public function getType()
+ {
+ return $this->options['type'];
+ }
+
+ /**
+ * Defined by Zend\Filter\FilterInterface
+ *
+ * Returns null representation of $value, if value is empty and matches
+ * types that should be considered null.
+ *
+ * @param string $value
+ * @return string
+ */
+ public function filter($value)
+ {
+ $type = $this->getType();
+
+ // FLOAT (0.0)
+ if ($type >= self::TYPE_FLOAT) {
+ $type -= self::TYPE_FLOAT;
+ if (is_float($value) && ($value == 0.0)) {
+ return null;
+ }
+ }
+
+ // STRING ZERO ('0')
+ if ($type >= self::TYPE_ZERO_STRING) {
+ $type -= self::TYPE_ZERO_STRING;
+ if (is_string($value) && ($value == '0')) {
+ return null;
+ }
+ }
+
+ // STRING ('')
+ if ($type >= self::TYPE_STRING) {
+ $type -= self::TYPE_STRING;
+ if (is_string($value) && ($value == '')) {
+ return null;
+ }
+ }
+
+ // EMPTY_ARRAY (array())
+ if ($type >= self::TYPE_EMPTY_ARRAY) {
+ $type -= self::TYPE_EMPTY_ARRAY;
+ if (is_array($value) && ($value == array())) {
+ return null;
+ }
+ }
+
+ // INTEGER (0)
+ if ($type >= self::TYPE_INTEGER) {
+ $type -= self::TYPE_INTEGER;
+ if (is_int($value) && ($value == 0)) {
+ return null;
+ }
+ }
+
+ // BOOLEAN (false)
+ if ($type >= self::TYPE_BOOLEAN) {
+ $type -= self::TYPE_BOOLEAN;
+ if (is_bool($value) && ($value == false)) {
+ return null;
+ }
+ }
+
+ return $value;
+ }
+}
diff --git a/library/Zend/Filter/PregReplace.php b/library/Zend/Filter/PregReplace.php
new file mode 100755
index 0000000000..9a7e3f3adc
--- /dev/null
+++ b/library/Zend/Filter/PregReplace.php
@@ -0,0 +1,166 @@
+ null,
+ 'replacement' => '',
+ );
+
+ /**
+ * Constructor
+ * Supported options are
+ * 'pattern' => matching pattern
+ * 'replacement' => replace with this
+ *
+ * @param array|Traversable|string|null $options
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Traversable) {
+ $options = iterator_to_array($options);
+ }
+
+ if (!is_array($options)
+ || (!isset($options['pattern']) && !isset($options['replacement']))
+ ) {
+ $args = func_get_args();
+ if (isset($args[0])) {
+ $this->setPattern($args[0]);
+ }
+ if (isset($args[1])) {
+ $this->setReplacement($args[1]);
+ }
+ } else {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * Set the regex pattern to search for
+ * @see preg_replace()
+ *
+ * @param string|array $pattern - same as the first argument of preg_replace
+ * @return self
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setPattern($pattern)
+ {
+ if (!is_array($pattern) && !is_string($pattern)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects pattern to be array or string; received "%s"',
+ __METHOD__,
+ (is_object($pattern) ? get_class($pattern) : gettype($pattern))
+ ));
+ }
+
+ if (is_array($pattern)) {
+ foreach ($pattern as $p) {
+ $this->validatePattern($p);
+ }
+ }
+
+ if (is_string($pattern)) {
+ $this->validatePattern($pattern);
+ }
+
+ $this->options['pattern'] = $pattern;
+ return $this;
+ }
+
+ /**
+ * Get currently set match pattern
+ *
+ * @return string|array
+ */
+ public function getPattern()
+ {
+ return $this->options['pattern'];
+ }
+
+ /**
+ * Set the replacement array/string
+ * @see preg_replace()
+ *
+ * @param array|string $replacement - same as the second argument of preg_replace
+ * @return self
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setReplacement($replacement)
+ {
+ if (!is_array($replacement) && !is_string($replacement)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects replacement to be array or string; received "%s"',
+ __METHOD__,
+ (is_object($replacement) ? get_class($replacement) : gettype($replacement))
+ ));
+ }
+ $this->options['replacement'] = $replacement;
+ return $this;
+ }
+
+ /**
+ * Get currently set replacement value
+ *
+ * @return string|array
+ */
+ public function getReplacement()
+ {
+ return $this->options['replacement'];
+ }
+
+ /**
+ * Perform regexp replacement as filter
+ *
+ * @param mixed $value
+ * @return mixed
+ * @throws Exception\RuntimeException
+ */
+ public function filter($value)
+ {
+ if (!is_scalar($value) && !is_array($value)) {
+ return $value;
+ }
+
+ if ($this->options['pattern'] === null) {
+ throw new Exception\RuntimeException(sprintf(
+ 'Filter %s does not have a valid pattern set',
+ get_class($this)
+ ));
+ }
+
+ return preg_replace($this->options['pattern'], $this->options['replacement'], $value);
+ }
+
+ /**
+ * Validate a pattern and ensure it does not contain the "e" modifier
+ *
+ * @param string $pattern
+ * @return bool
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function validatePattern($pattern)
+ {
+ if (!preg_match('/(?[imsxeADSUXJu]+)$/', $pattern, $matches)) {
+ return true;
+ }
+
+ if (false !== strstr($matches['modifier'], 'e')) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Pattern for a PregReplace filter may not contain the "e" pattern modifier; received "%s"',
+ $pattern
+ ));
+ }
+ }
+}
diff --git a/library/Zend/Filter/README.md b/library/Zend/Filter/README.md
new file mode 100755
index 0000000000..2aeb60bf21
--- /dev/null
+++ b/library/Zend/Filter/README.md
@@ -0,0 +1,15 @@
+Filter Component from ZF2
+=========================
+
+This is the Filter component for ZF2.
+
+- File issues at https://github.com/zendframework/zf2/issues
+- Create pull requests against https://github.com/zendframework/zf2
+- Documentation is at http://framework.zend.com/docs
+
+LICENSE
+-------
+
+The files in this archive are released under the [Zend Framework
+license](http://framework.zend.com/license), which is a 3-clause BSD license.
+
diff --git a/library/Zend/Filter/RealPath.php b/library/Zend/Filter/RealPath.php
new file mode 100755
index 0000000000..00633d5c9c
--- /dev/null
+++ b/library/Zend/Filter/RealPath.php
@@ -0,0 +1,122 @@
+ true
+ );
+
+ /**
+ * Class constructor
+ *
+ * @param bool|Traversable $existsOrOptions Options to set
+ */
+ public function __construct($existsOrOptions = true)
+ {
+ if ($existsOrOptions !== null) {
+ if (!static::isOptions($existsOrOptions)) {
+ $this->setExists($existsOrOptions);
+ } else {
+ $this->setOptions($existsOrOptions);
+ }
+ }
+ }
+
+ /**
+ * Sets if the path has to exist
+ * TRUE when the path must exist
+ * FALSE when not existing paths can be given
+ *
+ * @param bool $flag Path must exist
+ * @return self
+ */
+ public function setExists($flag = true)
+ {
+ $this->options['exists'] = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Returns true if the filtered path must exist
+ *
+ * @return bool
+ */
+ public function getExists()
+ {
+ return $this->options['exists'];
+ }
+
+ /**
+ * Defined by Zend\Filter\FilterInterface
+ *
+ * Returns realpath($value)
+ *
+ * If the value provided is non-scalar, the value will remain unfiltered
+ *
+ * @param string $value
+ * @return string|mixed
+ */
+ public function filter($value)
+ {
+ if (!is_string($value)) {
+ return $value;
+ }
+ $path = (string) $value;
+
+ if ($this->options['exists']) {
+ return realpath($path);
+ }
+
+ ErrorHandler::start();
+ $realpath = realpath($path);
+ ErrorHandler::stop();
+ if ($realpath) {
+ return $realpath;
+ }
+
+ $drive = '';
+ if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+ $path = preg_replace('/[\\\\\/]/', DIRECTORY_SEPARATOR, $path);
+ if (preg_match('/([a-zA-Z]\:)(.*)/', $path, $matches)) {
+ list(, $drive, $path) = $matches;
+ } else {
+ $cwd = getcwd();
+ $drive = substr($cwd, 0, 2);
+ if (substr($path, 0, 1) != DIRECTORY_SEPARATOR) {
+ $path = substr($cwd, 3) . DIRECTORY_SEPARATOR . $path;
+ }
+ }
+ } elseif (substr($path, 0, 1) != DIRECTORY_SEPARATOR) {
+ $path = getcwd() . DIRECTORY_SEPARATOR . $path;
+ }
+
+ $stack = array();
+ $parts = explode(DIRECTORY_SEPARATOR, $path);
+ foreach ($parts as $dir) {
+ if (strlen($dir) && $dir !== '.') {
+ if ($dir == '..') {
+ array_pop($stack);
+ } else {
+ array_push($stack, $dir);
+ }
+ }
+ }
+
+ return $drive . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $stack);
+ }
+}
diff --git a/library/Zend/Filter/StaticFilter.php b/library/Zend/Filter/StaticFilter.php
new file mode 100755
index 0000000000..2847137b64
--- /dev/null
+++ b/library/Zend/Filter/StaticFilter.php
@@ -0,0 +1,70 @@
+setShareByDefault(false);
+ }
+ static::$plugins = $manager;
+ }
+
+ /**
+ * Get plugin manager for loading filter classes
+ *
+ * @return FilterPluginManager
+ */
+ public static function getPluginManager()
+ {
+ if (null === static::$plugins) {
+ static::setPluginManager(new FilterPluginManager());
+ }
+ return static::$plugins;
+ }
+
+ /**
+ * Returns a value filtered through a specified filter class, without requiring separate
+ * instantiation of the filter object.
+ *
+ * The first argument of this method is a data input value, that you would have filtered.
+ * The second argument is a string, which corresponds to the basename of the filter class,
+ * relative to the Zend\Filter namespace. This method automatically loads the class,
+ * creates an instance, and applies the filter() method to the data input. You can also pass
+ * an array of constructor arguments, if they are needed for the filter class.
+ *
+ * @param mixed $value
+ * @param string $classBaseName
+ * @param array $args OPTIONAL
+ * @return mixed
+ * @throws Exception\ExceptionInterface
+ */
+ public static function execute($value, $classBaseName, array $args = array())
+ {
+ $plugins = static::getPluginManager();
+
+ $filter = $plugins->get($classBaseName, $args);
+ return $filter->filter($value);
+ }
+}
diff --git a/library/Zend/Filter/StringToLower.php b/library/Zend/Filter/StringToLower.php
new file mode 100755
index 0000000000..3d6f66a462
--- /dev/null
+++ b/library/Zend/Filter/StringToLower.php
@@ -0,0 +1,62 @@
+ null,
+ );
+
+ /**
+ * Constructor
+ *
+ * @param string|array|Traversable $encodingOrOptions OPTIONAL
+ */
+ public function __construct($encodingOrOptions = null)
+ {
+ if ($encodingOrOptions !== null) {
+ if (!static::isOptions($encodingOrOptions)) {
+ $this->setEncoding($encodingOrOptions);
+ } else {
+ $this->setOptions($encodingOrOptions);
+ }
+ }
+ }
+
+ /**
+ * Defined by Zend\Filter\FilterInterface
+ *
+ * Returns the string $value, converting characters to lowercase as necessary
+ *
+ * If the value provided is non-scalar, the value will remain unfiltered
+ *
+ * @param string $value
+ * @return string|mixed
+ */
+ public function filter($value)
+ {
+ if (!is_scalar($value)) {
+ return $value;
+ }
+ $value = (string) $value;
+
+ if ($this->options['encoding'] !== null) {
+ return mb_strtolower($value, $this->options['encoding']);
+ }
+
+ return strtolower($value);
+ }
+}
diff --git a/library/Zend/Filter/StringToUpper.php b/library/Zend/Filter/StringToUpper.php
new file mode 100755
index 0000000000..7f4c78e129
--- /dev/null
+++ b/library/Zend/Filter/StringToUpper.php
@@ -0,0 +1,62 @@
+ null,
+ );
+
+ /**
+ * Constructor
+ *
+ * @param string|array|Traversable $encodingOrOptions OPTIONAL
+ */
+ public function __construct($encodingOrOptions = null)
+ {
+ if ($encodingOrOptions !== null) {
+ if (!static::isOptions($encodingOrOptions)) {
+ $this->setEncoding($encodingOrOptions);
+ } else {
+ $this->setOptions($encodingOrOptions);
+ }
+ }
+ }
+
+ /**
+ * Defined by Zend\Filter\FilterInterface
+ *
+ * Returns the string $value, converting characters to uppercase as necessary
+ *
+ * If the value provided is non-scalar, the value will remain unfiltered
+ *
+ * @param string $value
+ * @return string|mixed
+ */
+ public function filter($value)
+ {
+ if (!is_scalar($value)) {
+ return $value;
+ }
+ $value = (string) $value;
+
+ if ($this->options['encoding'] !== null) {
+ return mb_strtoupper($value, $this->options['encoding']);
+ }
+
+ return strtoupper($value);
+ }
+}
diff --git a/library/Zend/Filter/StringTrim.php b/library/Zend/Filter/StringTrim.php
new file mode 100755
index 0000000000..2eae7fa36c
--- /dev/null
+++ b/library/Zend/Filter/StringTrim.php
@@ -0,0 +1,110 @@
+ null,
+ );
+
+ /**
+ * Sets filter options
+ *
+ * @param string|array|Traversable $charlistOrOptions
+ */
+ public function __construct($charlistOrOptions = null)
+ {
+ if ($charlistOrOptions !== null) {
+ if (!is_array($charlistOrOptions)
+ && !$charlistOrOptions instanceof Traversable
+ ) {
+ $this->setCharList($charlistOrOptions);
+ } else {
+ $this->setOptions($charlistOrOptions);
+ }
+ }
+ }
+
+ /**
+ * Sets the charList option
+ *
+ * @param string $charList
+ * @return self Provides a fluent interface
+ */
+ public function setCharList($charList)
+ {
+ if (! strlen($charList)) {
+ $charList = null;
+ }
+
+ $this->options['charlist'] = $charList;
+
+ return $this;
+ }
+
+ /**
+ * Returns the charList option
+ *
+ * @return string|null
+ */
+ public function getCharList()
+ {
+ return $this->options['charlist'];
+ }
+
+ /**
+ * Defined by Zend\Filter\FilterInterface
+ *
+ * Returns the string $value with characters stripped from the beginning and end
+ *
+ * @param string $value
+ * @return string
+ */
+ public function filter($value)
+ {
+ if (!is_string($value)) {
+ return $value;
+ }
+ $value = (string) $value;
+
+ if (null === $this->options['charlist']) {
+ return $this->unicodeTrim($value);
+ }
+
+ return $this->unicodeTrim($value, $this->options['charlist']);
+ }
+
+ /**
+ * Unicode aware trim method
+ * Fixes a PHP problem
+ *
+ * @param string $value
+ * @param string $charlist
+ * @return string
+ */
+ protected function unicodeTrim($value, $charlist = '\\\\s')
+ {
+ $chars = preg_replace(
+ array('/[\^\-\]\\\]/S', '/\\\{4}/S', '/\//'),
+ array('\\\\\\0', '\\', '\/'),
+ $charlist
+ );
+
+ $pattern = '/^[' . $chars . ']+|[' . $chars . ']+$/usSD';
+
+ return preg_replace($pattern, '', $value);
+ }
+}
diff --git a/library/Zend/Filter/StripNewlines.php b/library/Zend/Filter/StripNewlines.php
new file mode 100755
index 0000000000..481facaa88
--- /dev/null
+++ b/library/Zend/Filter/StripNewlines.php
@@ -0,0 +1,29 @@
+ Tags which are allowed
+ * 'allowAttribs' => Attributes which are allowed
+ * 'allowComments' => Are comments allowed ?
+ *
+ * @param string|array|Traversable $options
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Traversable) {
+ $options = ArrayUtils::iteratorToArray($options);
+ }
+ if ((!is_array($options)) || (is_array($options) && !array_key_exists('allowTags', $options) &&
+ !array_key_exists('allowAttribs', $options) && !array_key_exists('allowComments', $options))) {
+ $options = func_get_args();
+ $temp['allowTags'] = array_shift($options);
+ if (!empty($options)) {
+ $temp['allowAttribs'] = array_shift($options);
+ }
+
+ if (!empty($options)) {
+ $temp['allowComments'] = array_shift($options);
+ }
+
+ $options = $temp;
+ }
+
+ if (array_key_exists('allowTags', $options)) {
+ $this->setTagsAllowed($options['allowTags']);
+ }
+
+ if (array_key_exists('allowAttribs', $options)) {
+ $this->setAttributesAllowed($options['allowAttribs']);
+ }
+ }
+
+ /**
+ * Returns the tagsAllowed option
+ *
+ * @return array
+ */
+ public function getTagsAllowed()
+ {
+ return $this->tagsAllowed;
+ }
+
+ /**
+ * Sets the tagsAllowed option
+ *
+ * @param array|string $tagsAllowed
+ * @return self Provides a fluent interface
+ */
+ public function setTagsAllowed($tagsAllowed)
+ {
+ if (!is_array($tagsAllowed)) {
+ $tagsAllowed = array($tagsAllowed);
+ }
+
+ foreach ($tagsAllowed as $index => $element) {
+ // If the tag was provided without attributes
+ if (is_int($index) && is_string($element)) {
+ // Canonicalize the tag name
+ $tagName = strtolower($element);
+ // Store the tag as allowed with no attributes
+ $this->tagsAllowed[$tagName] = array();
+ }
+ // Otherwise, if a tag was provided with attributes
+ elseif (is_string($index) && (is_array($element) || is_string($element))) {
+ // Canonicalize the tag name
+ $tagName = strtolower($index);
+ // Canonicalize the attributes
+ if (is_string($element)) {
+ $element = array($element);
+ }
+ // Store the tag as allowed with the provided attributes
+ $this->tagsAllowed[$tagName] = array();
+ foreach ($element as $attribute) {
+ if (is_string($attribute)) {
+ // Canonicalize the attribute name
+ $attributeName = strtolower($attribute);
+ $this->tagsAllowed[$tagName][$attributeName] = null;
+ }
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the attributesAllowed option
+ *
+ * @return array
+ */
+ public function getAttributesAllowed()
+ {
+ return $this->attributesAllowed;
+ }
+
+ /**
+ * Sets the attributesAllowed option
+ *
+ * @param array|string $attributesAllowed
+ * @return self Provides a fluent interface
+ */
+ public function setAttributesAllowed($attributesAllowed)
+ {
+ if (!is_array($attributesAllowed)) {
+ $attributesAllowed = array($attributesAllowed);
+ }
+
+ // Store each attribute as allowed
+ foreach ($attributesAllowed as $attribute) {
+ if (is_string($attribute)) {
+ // Canonicalize the attribute name
+ $attributeName = strtolower($attribute);
+ $this->attributesAllowed[$attributeName] = null;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Defined by Zend\Filter\FilterInterface
+ *
+ * If the value provided is non-scalar, the value will remain unfiltered
+ *
+ * @todo improve docblock descriptions
+ * @param string $value
+ * @return string|mixed
+ */
+ public function filter($value)
+ {
+ if (!is_scalar($value)) {
+ return $value;
+ }
+ $value = (string) $value;
+
+ // Strip HTML comments first
+ while (strpos($value, '' . $link . '';
+ }
+
+ return $link;
+ }
+
+ /**
+ * Render link elements as string
+ *
+ * @param string|int $indent
+ * @return string
+ */
+ public function toString($indent = null)
+ {
+ $indent = (null !== $indent)
+ ? $this->getWhitespace($indent)
+ : $this->getIndent();
+
+ $items = array();
+ $this->getContainer()->ksort();
+ foreach ($this as $item) {
+ $items[] = $this->itemToString($item);
+ }
+
+ return $indent . implode($this->escape($this->getSeparator()) . $indent, $items);
+ }
+
+ /**
+ * Create data item for stack
+ *
+ * @param array $attributes
+ * @return stdClass
+ */
+ public function createData(array $attributes)
+ {
+ return (object) $attributes;
+ }
+
+ /**
+ * Create item for stylesheet link item
+ *
+ * @param array $args
+ * @return stdClass|false Returns false if stylesheet is a duplicate
+ */
+ public function createDataStylesheet(array $args)
+ {
+ $rel = 'stylesheet';
+ $type = 'text/css';
+ $media = 'screen';
+ $conditionalStylesheet = false;
+ $href = array_shift($args);
+
+ if ($this->isDuplicateStylesheet($href)) {
+ return false;
+ }
+
+ if (0 < count($args)) {
+ $media = array_shift($args);
+ if (is_array($media)) {
+ $media = implode(',', $media);
+ } else {
+ $media = (string) $media;
+ }
+ }
+ if (0 < count($args)) {
+ $conditionalStylesheet = array_shift($args);
+ if (!empty($conditionalStylesheet) && is_string($conditionalStylesheet)) {
+ $conditionalStylesheet = (string) $conditionalStylesheet;
+ } else {
+ $conditionalStylesheet = null;
+ }
+ }
+
+ if (0 < count($args) && is_array($args[0])) {
+ $extras = array_shift($args);
+ $extras = (array) $extras;
+ }
+
+ $attributes = compact('rel', 'type', 'href', 'media', 'conditionalStylesheet', 'extras');
+
+ return $this->createData($attributes);
+ }
+
+ /**
+ * Is the linked stylesheet a duplicate?
+ *
+ * @param string $uri
+ * @return bool
+ */
+ protected function isDuplicateStylesheet($uri)
+ {
+ foreach ($this->getContainer() as $item) {
+ if (($item->rel == 'stylesheet') && ($item->href == $uri)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Create item for alternate link item
+ *
+ * @param array $args
+ * @throws Exception\InvalidArgumentException
+ * @return stdClass
+ */
+ public function createDataAlternate(array $args)
+ {
+ if (3 > count($args)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Alternate tags require 3 arguments; %s provided',
+ count($args)
+ ));
+ }
+
+ $rel = 'alternate';
+ $href = array_shift($args);
+ $type = array_shift($args);
+ $title = array_shift($args);
+
+ if (0 < count($args) && is_array($args[0])) {
+ $extras = array_shift($args);
+ $extras = (array) $extras;
+
+ if (isset($extras['media']) && is_array($extras['media'])) {
+ $extras['media'] = implode(',', $extras['media']);
+ }
+ }
+
+ $href = (string) $href;
+ $type = (string) $type;
+ $title = (string) $title;
+
+ $attributes = compact('rel', 'href', 'type', 'title', 'extras');
+
+ return $this->createData($attributes);
+ }
+
+ /**
+ * Create item for a prev relationship (mainly used for pagination)
+ *
+ * @param array $args
+ * @return stdClass
+ */
+ public function createDataPrev(array $args)
+ {
+ $rel = 'prev';
+ $href = (string) array_shift($args);
+
+ $attributes = compact('rel', 'href');
+
+ return $this->createData($attributes);
+ }
+
+ /**
+ * Create item for a prev relationship (mainly used for pagination)
+ *
+ * @param array $args
+ * @return stdClass
+ */
+ public function createDataNext(array $args)
+ {
+ $rel = 'next';
+ $href = (string) array_shift($args);
+
+ $attributes = compact('rel', 'href');
+
+ return $this->createData($attributes);
+ }
+}
diff --git a/library/Zend/View/Helper/HeadMeta.php b/library/Zend/View/Helper/HeadMeta.php
new file mode 100755
index 0000000000..66a0dff59a
--- /dev/null
+++ b/library/Zend/View/Helper/HeadMeta.php
@@ -0,0 +1,453 @@
+setSeparator(PHP_EOL);
+ }
+
+ /**
+ * Retrieve object instance; optionally add meta tag
+ *
+ * @param string $content
+ * @param string $keyValue
+ * @param string $keyType
+ * @param array $modifiers
+ * @param string $placement
+ * @return HeadMeta
+ */
+ public function __invoke($content = null, $keyValue = null, $keyType = 'name', $modifiers = array(), $placement = Placeholder\Container\AbstractContainer::APPEND)
+ {
+ if ((null !== $content) && (null !== $keyValue)) {
+ $item = $this->createData($keyType, $keyValue, $content, $modifiers);
+ $action = strtolower($placement);
+ switch ($action) {
+ case 'append':
+ case 'prepend':
+ case 'set':
+ $this->$action($item);
+ break;
+ default:
+ $this->append($item);
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Overload method access
+ *
+ * @param string $method
+ * @param array $args
+ * @throws Exception\BadMethodCallException
+ * @return HeadMeta
+ */
+ public function __call($method, $args)
+ {
+ if (preg_match('/^(?Pset|(pre|ap)pend|offsetSet)(?PName|HttpEquiv|Property|Itemprop)$/', $method, $matches)) {
+ $action = $matches['action'];
+ $type = $this->normalizeType($matches['type']);
+ $argc = count($args);
+ $index = null;
+
+ if ('offsetSet' == $action) {
+ if (0 < $argc) {
+ $index = array_shift($args);
+ --$argc;
+ }
+ }
+
+ if (2 > $argc) {
+ throw new Exception\BadMethodCallException(
+ 'Too few arguments provided; requires key value, and content'
+ );
+ }
+
+ if (3 > $argc) {
+ $args[] = array();
+ }
+
+ $item = $this->createData($type, $args[0], $args[1], $args[2]);
+
+ if ('offsetSet' == $action) {
+ return $this->offsetSet($index, $item);
+ }
+
+ $this->$action($item);
+
+ return $this;
+ }
+
+ return parent::__call($method, $args);
+ }
+
+ /**
+ * Render placeholder as string
+ *
+ * @param string|int $indent
+ * @return string
+ */
+ public function toString($indent = null)
+ {
+ $indent = (null !== $indent)
+ ? $this->getWhitespace($indent)
+ : $this->getIndent();
+
+ $items = array();
+ $this->getContainer()->ksort();
+
+ try {
+ foreach ($this as $item) {
+ $items[] = $this->itemToString($item);
+ }
+ } catch (Exception\InvalidArgumentException $e) {
+ trigger_error($e->getMessage(), E_USER_WARNING);
+ return '';
+ }
+
+ return $indent . implode($this->escape($this->getSeparator()) . $indent, $items);
+ }
+
+ /**
+ * Create data item for inserting into stack
+ *
+ * @param string $type
+ * @param string $typeValue
+ * @param string $content
+ * @param array $modifiers
+ * @return stdClass
+ */
+ public function createData($type, $typeValue, $content, array $modifiers)
+ {
+ $data = new stdClass;
+ $data->type = $type;
+ $data->$type = $typeValue;
+ $data->content = $content;
+ $data->modifiers = $modifiers;
+
+ return $data;
+ }
+
+ /**
+ * Build meta HTML string
+ *
+ * @param stdClass $item
+ * @throws Exception\InvalidArgumentException
+ * @return string
+ */
+ public function itemToString(stdClass $item)
+ {
+ if (!in_array($item->type, $this->typeKeys)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Invalid type "%s" provided for meta',
+ $item->type
+ ));
+ }
+ $type = $item->type;
+
+ $modifiersString = '';
+ foreach ($item->modifiers as $key => $value) {
+ if ($this->view->plugin('doctype')->isHtml5()
+ && $key == 'scheme'
+ ) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid modifier "scheme" provided; not supported by HTML5'
+ );
+ }
+ if (!in_array($key, $this->modifierKeys)) {
+ continue;
+ }
+ $modifiersString .= $key . '="' . $this->escape($value) . '" ';
+ }
+
+ $modifiersString = rtrim($modifiersString);
+
+ if ('' != $modifiersString) {
+ $modifiersString = ' ' . $modifiersString;
+ }
+
+ if (method_exists($this->view, 'plugin')) {
+ if ($this->view->plugin('doctype')->isHtml5()
+ && $type == 'charset'
+ ) {
+ $tpl = ($this->view->plugin('doctype')->isXhtml())
+ ? ''
+ : '';
+ } elseif ($this->view->plugin('doctype')->isXhtml()) {
+ $tpl = '';
+ } else {
+ $tpl = '';
+ }
+ } else {
+ $tpl = '';
+ }
+
+ $meta = sprintf(
+ $tpl,
+ $type,
+ $this->escape($item->$type),
+ $this->escape($item->content),
+ $modifiersString
+ );
+
+ if (isset($item->modifiers['conditional']) && !empty($item->modifiers['conditional']) && is_string($item->modifiers['conditional'])) {
+ // inner wrap with comment end and start if !IE
+ if (str_replace(' ', '', $item->modifiers['conditional']) === '!IE') {
+ $meta = '' . $meta . '';
+ }
+
+ return $meta;
+ }
+
+ /**
+ * Normalize type attribute of meta
+ *
+ * @param string $type type in CamelCase
+ * @throws Exception\DomainException
+ * @return string
+ */
+ protected function normalizeType($type)
+ {
+ switch ($type) {
+ case 'Name':
+ return 'name';
+ case 'HttpEquiv':
+ return 'http-equiv';
+ case 'Property':
+ return 'property';
+ case 'Itemprop':
+ return 'itemprop';
+ default:
+ throw new Exception\DomainException(sprintf(
+ 'Invalid type "%s" passed to normalizeType',
+ $type
+ ));
+ }
+ }
+
+ /**
+ * Determine if item is valid
+ *
+ * @param mixed $item
+ * @return bool
+ */
+ protected function isValid($item)
+ {
+ if ((!$item instanceof stdClass)
+ || !isset($item->type)
+ || !isset($item->modifiers)
+ ) {
+ return false;
+ }
+
+ if (!isset($item->content)
+ && (! $this->view->plugin('doctype')->isHtml5()
+ || (! $this->view->plugin('doctype')->isHtml5() && $item->type !== 'charset'))
+ ) {
+ return false;
+ }
+
+ // is only supported with doctype html
+ if (! $this->view->plugin('doctype')->isHtml5()
+ && $item->type === 'itemprop'
+ ) {
+ return false;
+ }
+
+ // is only supported with doctype RDFa
+ if (!$this->view->plugin('doctype')->isRdfa()
+ && $item->type === 'property'
+ ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Append
+ *
+ * @param string $value
+ * @return void
+ * @throws Exception\InvalidArgumentException
+ */
+ public function append($value)
+ {
+ if (!$this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid value passed to append; please use appendMeta()'
+ );
+ }
+
+ return $this->getContainer()->append($value);
+ }
+
+ /**
+ * OffsetSet
+ *
+ * @param string|int $index
+ * @param string $value
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function offsetSet($index, $value)
+ {
+ if (!$this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid value passed to offsetSet; please use offsetSetName() or offsetSetHttpEquiv()'
+ );
+ }
+
+ return $this->getContainer()->offsetSet($index, $value);
+ }
+
+ /**
+ * OffsetUnset
+ *
+ * @param string|int $index
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function offsetUnset($index)
+ {
+ if (!in_array($index, $this->getContainer()->getKeys())) {
+ throw new Exception\InvalidArgumentException('Invalid index passed to offsetUnset()');
+ }
+
+ return $this->getContainer()->offsetUnset($index);
+ }
+
+ /**
+ * Prepend
+ *
+ * @param string $value
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function prepend($value)
+ {
+ if (!$this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid value passed to prepend; please use prependMeta()'
+ );
+ }
+
+ return $this->getContainer()->prepend($value);
+ }
+
+ /**
+ * Set
+ *
+ * @param string $value
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function set($value)
+ {
+ if (!$this->isValid($value)) {
+ throw new Exception\InvalidArgumentException('Invalid value passed to set; please use setMeta()');
+ }
+
+ $container = $this->getContainer();
+ foreach ($container->getArrayCopy() as $index => $item) {
+ if ($item->type == $value->type && $item->{$item->type} == $value->{$value->type}) {
+ $this->offsetUnset($index);
+ }
+ }
+
+ return $this->append($value);
+ }
+
+ /**
+ * Create an HTML5-style meta charset tag. Something like
+ *
+ * Not valid in a non-HTML5 doctype
+ *
+ * @param string $charset
+ * @return HeadMeta Provides a fluent interface
+ */
+ public function setCharset($charset)
+ {
+ $item = new stdClass;
+ $item->type = 'charset';
+ $item->charset = $charset;
+ $item->content = null;
+ $item->modifiers = array();
+ $this->set($item);
+
+ return $this;
+ }
+}
diff --git a/library/Zend/View/Helper/HeadScript.php b/library/Zend/View/Helper/HeadScript.php
new file mode 100755
index 0000000000..eca0475d30
--- /dev/null
+++ b/library/Zend/View/Helper/HeadScript.php
@@ -0,0 +1,507 @@
+setSeparator(PHP_EOL);
+ }
+
+ /**
+ * Return headScript object
+ *
+ * Returns headScript helper object; optionally, allows specifying a script
+ * or script file to include.
+ *
+ * @param string $mode Script or file
+ * @param string $spec Script/url
+ * @param string $placement Append, prepend, or set
+ * @param array $attrs Array of script attributes
+ * @param string $type Script type and/or array of script attributes
+ * @return HeadScript
+ */
+ public function __invoke($mode = self::FILE, $spec = null, $placement = 'APPEND', array $attrs = array(), $type = 'text/javascript')
+ {
+ if ((null !== $spec) && is_string($spec)) {
+ $action = ucfirst(strtolower($mode));
+ $placement = strtolower($placement);
+ switch ($placement) {
+ case 'set':
+ case 'prepend':
+ case 'append':
+ $action = $placement . $action;
+ break;
+ default:
+ $action = 'append' . $action;
+ break;
+ }
+ $this->$action($spec, $type, $attrs);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Overload method access
+ *
+ * @param string $method Method to call
+ * @param array $args Arguments of method
+ * @throws Exception\BadMethodCallException if too few arguments or invalid method
+ * @return HeadScript
+ */
+ public function __call($method, $args)
+ {
+ if (preg_match('/^(?Pset|(ap|pre)pend|offsetSet)(?PFile|Script)$/', $method, $matches)) {
+ if (1 > count($args)) {
+ throw new Exception\BadMethodCallException(sprintf(
+ 'Method "%s" requires at least one argument',
+ $method
+ ));
+ }
+
+ $action = $matches['action'];
+ $mode = strtolower($matches['mode']);
+ $type = 'text/javascript';
+ $attrs = array();
+
+ if ('offsetSet' == $action) {
+ $index = array_shift($args);
+ if (1 > count($args)) {
+ throw new Exception\BadMethodCallException(sprintf(
+ 'Method "%s" requires at least two arguments, an index and source',
+ $method
+ ));
+ }
+ }
+
+ $content = $args[0];
+
+ if (isset($args[1])) {
+ $type = (string) $args[1];
+ }
+ if (isset($args[2])) {
+ $attrs = (array) $args[2];
+ }
+
+ switch ($mode) {
+ case 'script':
+ $item = $this->createData($type, $attrs, $content);
+ if ('offsetSet' == $action) {
+ $this->offsetSet($index, $item);
+ } else {
+ $this->$action($item);
+ }
+ break;
+ case 'file':
+ default:
+ if (!$this->isDuplicate($content)) {
+ $attrs['src'] = $content;
+ $item = $this->createData($type, $attrs);
+ if ('offsetSet' == $action) {
+ $this->offsetSet($index, $item);
+ } else {
+ $this->$action($item);
+ }
+ }
+ break;
+ }
+
+ return $this;
+ }
+
+ return parent::__call($method, $args);
+ }
+
+ /**
+ * Retrieve string representation
+ *
+ * @param string|int $indent Amount of whitespaces or string to use for indention
+ * @return string
+ */
+ public function toString($indent = null)
+ {
+ $indent = (null !== $indent)
+ ? $this->getWhitespace($indent)
+ : $this->getIndent();
+
+ if ($this->view) {
+ $useCdata = $this->view->plugin('doctype')->isXhtml() ? true : false;
+ } else {
+ $useCdata = $this->useCdata ? true : false;
+ }
+
+ $escapeStart = ($useCdata) ? '//' : '//-->';
+
+ $items = array();
+ $this->getContainer()->ksort();
+ foreach ($this as $item) {
+ if (!$this->isValid($item)) {
+ continue;
+ }
+
+ $items[] = $this->itemToString($item, $indent, $escapeStart, $escapeEnd);
+ }
+
+ return implode($this->getSeparator(), $items);
+ }
+
+ /**
+ * Start capture action
+ *
+ * @param mixed $captureType Type of capture
+ * @param string $type Type of script
+ * @param array $attrs Attributes of capture
+ * @throws Exception\RuntimeException
+ * @return void
+ */
+ public function captureStart($captureType = Placeholder\Container\AbstractContainer::APPEND, $type = 'text/javascript', $attrs = array())
+ {
+ if ($this->captureLock) {
+ throw new Exception\RuntimeException('Cannot nest headScript captures');
+ }
+
+ $this->captureLock = true;
+ $this->captureType = $captureType;
+ $this->captureScriptType = $type;
+ $this->captureScriptAttrs = $attrs;
+ ob_start();
+ }
+
+ /**
+ * End capture action and store
+ *
+ * @return void
+ */
+ public function captureEnd()
+ {
+ $content = ob_get_clean();
+ $type = $this->captureScriptType;
+ $attrs = $this->captureScriptAttrs;
+ $this->captureScriptType = null;
+ $this->captureScriptAttrs = null;
+ $this->captureLock = false;
+
+ switch ($this->captureType) {
+ case Placeholder\Container\AbstractContainer::SET:
+ case Placeholder\Container\AbstractContainer::PREPEND:
+ case Placeholder\Container\AbstractContainer::APPEND:
+ $action = strtolower($this->captureType) . 'Script';
+ break;
+ default:
+ $action = 'appendScript';
+ break;
+ }
+
+ $this->$action($content, $type, $attrs);
+ }
+
+ /**
+ * Create data item containing all necessary components of script
+ *
+ * @param string $type Type of data
+ * @param array $attributes Attributes of data
+ * @param string $content Content of data
+ * @return stdClass
+ */
+ public function createData($type, array $attributes, $content = null)
+ {
+ $data = new stdClass();
+ $data->type = $type;
+ $data->attributes = $attributes;
+ $data->source = $content;
+
+ return $data;
+ }
+
+ /**
+ * Is the file specified a duplicate?
+ *
+ * @param string $file Name of file to check
+ * @return bool
+ */
+ protected function isDuplicate($file)
+ {
+ foreach ($this->getContainer() as $item) {
+ if (($item->source === null) && array_key_exists('src', $item->attributes) && ($file == $item->attributes['src'])) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Is the script provided valid?
+ *
+ * @param mixed $value Is the given script valid?
+ * @return bool
+ */
+ protected function isValid($value)
+ {
+ if ((!$value instanceof stdClass) || !isset($value->type) || (!isset($value->source) && !isset($value->attributes))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Create script HTML
+ *
+ * @param mixed $item Item to convert
+ * @param string $indent String to add before the item
+ * @param string $escapeStart Starting sequence
+ * @param string $escapeEnd Ending sequence
+ * @return string
+ */
+ public function itemToString($item, $indent, $escapeStart, $escapeEnd)
+ {
+ $attrString = '';
+ if (!empty($item->attributes)) {
+ foreach ($item->attributes as $key => $value) {
+ if ((!$this->arbitraryAttributesAllowed() && !in_array($key, $this->optionalAttributes))
+ || in_array($key, array('conditional', 'noescape'))) {
+ continue;
+ }
+ if ('defer' == $key) {
+ $value = 'defer';
+ }
+ $attrString .= sprintf(' %s="%s"', $key, ($this->autoEscape) ? $this->escape($value) : $value);
+ }
+ }
+
+ $addScriptEscape = !(isset($item->attributes['noescape']) && filter_var($item->attributes['noescape'], FILTER_VALIDATE_BOOLEAN));
+
+ $type = ($this->autoEscape) ? $this->escape($item->type) : $item->type;
+ $html = '';
+
+ if (isset($item->attributes['conditional']) && !empty($item->attributes['conditional']) && is_string($item->attributes['conditional'])) {
+ // inner wrap with comment end and start if !IE
+ if (str_replace(' ', '', $item->attributes['conditional']) === '!IE') {
+ $html = '' . $html . '';
+ } else {
+ $html = $indent . $html;
+ }
+
+ return $html;
+ }
+
+ /**
+ * Override append
+ *
+ * @param string $value Append script or file
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function append($value)
+ {
+ if (!$this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid argument passed to append(); please use one of the helper methods, appendScript() or appendFile()'
+ );
+ }
+
+ return $this->getContainer()->append($value);
+ }
+
+ /**
+ * Override prepend
+ *
+ * @param string $value Prepend script or file
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function prepend($value)
+ {
+ if (!$this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid argument passed to prepend(); please use one of the helper methods, prependScript() or prependFile()'
+ );
+ }
+
+ return $this->getContainer()->prepend($value);
+ }
+
+ /**
+ * Override set
+ *
+ * @param string $value Set script or file
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function set($value)
+ {
+ if (!$this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid argument passed to set(); please use one of the helper methods, setScript() or setFile()'
+ );
+ }
+
+ return $this->getContainer()->set($value);
+ }
+
+ /**
+ * Override offsetSet
+ *
+ * @param string|int $index Set script of file offset
+ * @param mixed $value
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function offsetSet($index, $value)
+ {
+ if (!$this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid argument passed to offsetSet(); please use one of the helper methods, offsetSetScript() or offsetSetFile()'
+ );
+ }
+
+ return $this->getContainer()->offsetSet($index, $value);
+ }
+
+ /**
+ * Set flag indicating if arbitrary attributes are allowed
+ *
+ * @param bool $flag Set flag
+ * @return HeadScript
+ */
+ public function setAllowArbitraryAttributes($flag)
+ {
+ $this->arbitraryAttributes = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Are arbitrary attributes allowed?
+ *
+ * @return bool
+ */
+ public function arbitraryAttributesAllowed()
+ {
+ return $this->arbitraryAttributes;
+ }
+}
diff --git a/library/Zend/View/Helper/HeadStyle.php b/library/Zend/View/Helper/HeadStyle.php
new file mode 100755
index 0000000000..c6cce3a708
--- /dev/null
+++ b/library/Zend/View/Helper/HeadStyle.php
@@ -0,0 +1,413 @@
+setSeparator(PHP_EOL);
+ }
+
+ /**
+ * Return headStyle object
+ *
+ * Returns headStyle helper object; optionally, allows specifying
+ *
+ * @param string $content Stylesheet contents
+ * @param string $placement Append, prepend, or set
+ * @param string|array $attributes Optional attributes to utilize
+ * @return HeadStyle
+ */
+ public function __invoke($content = null, $placement = 'APPEND', $attributes = array())
+ {
+ if ((null !== $content) && is_string($content)) {
+ switch (strtoupper($placement)) {
+ case 'SET':
+ $action = 'setStyle';
+ break;
+ case 'PREPEND':
+ $action = 'prependStyle';
+ break;
+ case 'APPEND':
+ default:
+ $action = 'appendStyle';
+ break;
+ }
+ $this->$action($content, $attributes);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Overload method calls
+ *
+ * @param string $method
+ * @param array $args
+ * @throws Exception\BadMethodCallException When no $content provided or invalid method
+ * @return void
+ */
+ public function __call($method, $args)
+ {
+ if (preg_match('/^(?Pset|(ap|pre)pend|offsetSet)(Style)$/', $method, $matches)) {
+ $index = null;
+ $argc = count($args);
+ $action = $matches['action'];
+
+ if ('offsetSet' == $action) {
+ if (0 < $argc) {
+ $index = array_shift($args);
+ --$argc;
+ }
+ }
+
+ if (1 > $argc) {
+ throw new Exception\BadMethodCallException(sprintf(
+ 'Method "%s" requires minimally content for the stylesheet',
+ $method
+ ));
+ }
+
+ $content = $args[0];
+ $attrs = array();
+ if (isset($args[1])) {
+ $attrs = (array) $args[1];
+ }
+
+ $item = $this->createData($content, $attrs);
+
+ if ('offsetSet' == $action) {
+ $this->offsetSet($index, $item);
+ } else {
+ $this->$action($item);
+ }
+
+ return $this;
+ }
+
+ return parent::__call($method, $args);
+ }
+
+ /**
+ * Create string representation of placeholder
+ *
+ * @param string|int $indent
+ * @return string
+ */
+ public function toString($indent = null)
+ {
+ $indent = (null !== $indent)
+ ? $this->getWhitespace($indent)
+ : $this->getIndent();
+
+ $items = array();
+ $this->getContainer()->ksort();
+ foreach ($this as $item) {
+ if (!$this->isValid($item)) {
+ continue;
+ }
+ $items[] = $this->itemToString($item, $indent);
+ }
+
+ $return = $indent . implode($this->getSeparator() . $indent, $items);
+ $return = preg_replace("/(\r\n?|\n)/", '$1' . $indent, $return);
+
+ return $return;
+ }
+
+ /**
+ * Start capture action
+ *
+ * @param string $type
+ * @param string $attrs
+ * @throws Exception\RuntimeException
+ * @return void
+ */
+ public function captureStart($type = Placeholder\Container\AbstractContainer::APPEND, $attrs = null)
+ {
+ if ($this->captureLock) {
+ throw new Exception\RuntimeException('Cannot nest headStyle captures');
+ }
+
+ $this->captureLock = true;
+ $this->captureAttrs = $attrs;
+ $this->captureType = $type;
+ ob_start();
+ }
+
+ /**
+ * End capture action and store
+ *
+ * @return void
+ */
+ public function captureEnd()
+ {
+ $content = ob_get_clean();
+ $attrs = $this->captureAttrs;
+ $this->captureAttrs = null;
+ $this->captureLock = false;
+
+ switch ($this->captureType) {
+ case Placeholder\Container\AbstractContainer::SET:
+ $this->setStyle($content, $attrs);
+ break;
+ case Placeholder\Container\AbstractContainer::PREPEND:
+ $this->prependStyle($content, $attrs);
+ break;
+ case Placeholder\Container\AbstractContainer::APPEND:
+ default:
+ $this->appendStyle($content, $attrs);
+ break;
+ }
+ }
+
+ /**
+ * Create data item for use in stack
+ *
+ * @param string $content
+ * @param array $attributes
+ * @return stdClass
+ */
+ public function createData($content, array $attributes)
+ {
+ if (!isset($attributes['media'])) {
+ $attributes['media'] = 'screen';
+ } elseif (is_array($attributes['media'])) {
+ $attributes['media'] = implode(',', $attributes['media']);
+ }
+
+ $data = new stdClass();
+ $data->content = $content;
+ $data->attributes = $attributes;
+
+ return $data;
+ }
+
+ /**
+ * Determine if a value is a valid style tag
+ *
+ * @param mixed $value
+ * @return bool
+ */
+ protected function isValid($value)
+ {
+ if ((!$value instanceof stdClass) || !isset($value->content) || !isset($value->attributes)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Convert content and attributes into valid style tag
+ *
+ * @param stdClass $item Item to render
+ * @param string $indent Indentation to use
+ * @return string
+ */
+ public function itemToString(stdClass $item, $indent)
+ {
+ $attrString = '';
+ if (!empty($item->attributes)) {
+ $enc = 'UTF-8';
+ if ($this->view instanceof View\Renderer\RendererInterface
+ && method_exists($this->view, 'getEncoding')
+ ) {
+ $enc = $this->view->getEncoding();
+ }
+ $escaper = $this->getEscaper($enc);
+ foreach ($item->attributes as $key => $value) {
+ if (!in_array($key, $this->optionalAttributes)) {
+ continue;
+ }
+ if ('media' == $key) {
+ if (false === strpos($value, ',')) {
+ if (!in_array($value, $this->mediaTypes)) {
+ continue;
+ }
+ } else {
+ $mediaTypes = explode(',', $value);
+ $value = '';
+ foreach ($mediaTypes as $type) {
+ $type = trim($type);
+ if (!in_array($type, $this->mediaTypes)) {
+ continue;
+ }
+ $value .= $type .',';
+ }
+ $value = substr($value, 0, -1);
+ }
+ }
+ $attrString .= sprintf(' %s="%s"', $key, $escaper->escapeHtmlAttr($value));
+ }
+ }
+
+ $escapeStart = $indent . '' . PHP_EOL;
+ if (isset($item->attributes['conditional'])
+ && !empty($item->attributes['conditional'])
+ && is_string($item->attributes['conditional'])
+ ) {
+ $escapeStart = null;
+ $escapeEnd = null;
+ }
+
+ $html = '';
+
+ if (null == $escapeStart && null == $escapeEnd) {
+ // inner wrap with comment end and start if !IE
+ if (str_replace(' ', '', $item->attributes['conditional']) === '!IE') {
+ $html = '' . $html . '';
+ }
+
+ return $html;
+ }
+
+ /**
+ * Override append to enforce style creation
+ *
+ * @param mixed $value
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function append($value)
+ {
+ if (!$this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid value passed to append; please use appendStyle()'
+ );
+ }
+
+ return $this->getContainer()->append($value);
+ }
+
+ /**
+ * Override offsetSet to enforce style creation
+ *
+ * @param string|int $index
+ * @param mixed $value
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function offsetSet($index, $value)
+ {
+ if (!$this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid value passed to offsetSet; please use offsetSetStyle()'
+ );
+ }
+
+ return $this->getContainer()->offsetSet($index, $value);
+ }
+
+ /**
+ * Override prepend to enforce style creation
+ *
+ * @param mixed $value
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function prepend($value)
+ {
+ if (!$this->isValid($value)) {
+ throw new Exception\InvalidArgumentException(
+ 'Invalid value passed to prepend; please use prependStyle()'
+ );
+ }
+
+ return $this->getContainer()->prepend($value);
+ }
+
+ /**
+ * Override set to enforce style creation
+ *
+ * @param mixed $value
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function set($value)
+ {
+ if (!$this->isValid($value)) {
+ throw new Exception\InvalidArgumentException('Invalid value passed to set; please use setStyle()');
+ }
+
+ return $this->getContainer()->set($value);
+ }
+}
diff --git a/library/Zend/View/Helper/HeadTitle.php b/library/Zend/View/Helper/HeadTitle.php
new file mode 100755
index 0000000000..2ea8d78dd0
--- /dev/null
+++ b/library/Zend/View/Helper/HeadTitle.php
@@ -0,0 +1,263 @@
+getDefaultAttachOrder())
+ ? Placeholder\Container\AbstractContainer::APPEND
+ : $this->getDefaultAttachOrder();
+ }
+
+ $title = (string) $title;
+ if ($title !== '') {
+ if ($setType == Placeholder\Container\AbstractContainer::SET) {
+ $this->set($title);
+ } elseif ($setType == Placeholder\Container\AbstractContainer::PREPEND) {
+ $this->prepend($title);
+ } else {
+ $this->append($title);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Render title (wrapped by title tag)
+ *
+ * @param string|null $indent
+ * @return string
+ */
+ public function toString($indent = null)
+ {
+ $indent = (null !== $indent)
+ ? $this->getWhitespace($indent)
+ : $this->getIndent();
+
+ $output = $this->renderTitle();
+
+ return $indent . '' . $output . '';
+ }
+
+ /**
+ * Render title string
+ *
+ * @return string
+ */
+ public function renderTitle()
+ {
+ $items = array();
+
+ if (null !== ($translator = $this->getTranslator())) {
+ foreach ($this as $item) {
+ $items[] = $translator->translate($item, $this->getTranslatorTextDomain());
+ }
+ } else {
+ foreach ($this as $item) {
+ $items[] = $item;
+ }
+ }
+
+ $separator = $this->getSeparator();
+ $output = '';
+
+ $prefix = $this->getPrefix();
+ if ($prefix) {
+ $output .= $prefix;
+ }
+
+ $output .= implode($separator, $items);
+
+ $postfix = $this->getPostfix();
+ if ($postfix) {
+ $output .= $postfix;
+ }
+
+ $output = ($this->autoEscape) ? $this->escape($output) : $output;
+
+ return $output;
+ }
+
+ /**
+ * Set a default order to add titles
+ *
+ * @param string $setType
+ * @throws Exception\DomainException
+ * @return HeadTitle
+ */
+ public function setDefaultAttachOrder($setType)
+ {
+ if (!in_array($setType, array(
+ Placeholder\Container\AbstractContainer::APPEND,
+ Placeholder\Container\AbstractContainer::SET,
+ Placeholder\Container\AbstractContainer::PREPEND
+ ))) {
+ throw new Exception\DomainException(
+ "You must use a valid attach order: 'PREPEND', 'APPEND' or 'SET'"
+ );
+ }
+ $this->defaultAttachOrder = $setType;
+
+ return $this;
+ }
+
+ /**
+ * Get the default attach order, if any.
+ *
+ * @return mixed
+ */
+ public function getDefaultAttachOrder()
+ {
+ return $this->defaultAttachOrder;
+ }
+
+ // Translator methods - Good candidate to refactor as a trait with PHP 5.4
+
+ /**
+ * Sets translator to use in helper
+ *
+ * @param Translator $translator [optional] translator.
+ * Default is null, which sets no translator.
+ * @param string $textDomain [optional] text domain
+ * Default is null, which skips setTranslatorTextDomain
+ * @return HeadTitle
+ */
+ public function setTranslator(Translator $translator = null, $textDomain = null)
+ {
+ $this->translator = $translator;
+ if (null !== $textDomain) {
+ $this->setTranslatorTextDomain($textDomain);
+ }
+ return $this;
+ }
+
+ /**
+ * Returns translator used in helper
+ *
+ * @return Translator|null
+ */
+ public function getTranslator()
+ {
+ if (! $this->isTranslatorEnabled()) {
+ return null;
+ }
+
+ return $this->translator;
+ }
+
+ /**
+ * Checks if the helper has a translator
+ *
+ * @return bool
+ */
+ public function hasTranslator()
+ {
+ return (bool) $this->getTranslator();
+ }
+
+ /**
+ * Sets whether translator is enabled and should be used
+ *
+ * @param bool $enabled [optional] whether translator should be used.
+ * Default is true.
+ * @return HeadTitle
+ */
+ public function setTranslatorEnabled($enabled = true)
+ {
+ $this->translatorEnabled = (bool) $enabled;
+ return $this;
+ }
+
+ /**
+ * Returns whether translator is enabled and should be used
+ *
+ * @return bool
+ */
+ public function isTranslatorEnabled()
+ {
+ return $this->translatorEnabled;
+ }
+
+ /**
+ * Set translation text domain
+ *
+ * @param string $textDomain
+ * @return HeadTitle
+ */
+ public function setTranslatorTextDomain($textDomain = 'default')
+ {
+ $this->translatorTextDomain = $textDomain;
+ return $this;
+ }
+
+ /**
+ * Return the translation text domain
+ *
+ * @return string
+ */
+ public function getTranslatorTextDomain()
+ {
+ return $this->translatorTextDomain;
+ }
+}
diff --git a/library/Zend/View/Helper/HelperInterface.php b/library/Zend/View/Helper/HelperInterface.php
new file mode 100755
index 0000000000..2d58c01f90
--- /dev/null
+++ b/library/Zend/View/Helper/HelperInterface.php
@@ -0,0 +1,30 @@
+ $data, 'quality' => 'high'), $params);
+
+ $htmlObject = $this->getView()->plugin('htmlObject');
+ return $htmlObject($data, self::TYPE, $attribs, $params, $content);
+ }
+}
diff --git a/library/Zend/View/Helper/HtmlList.php b/library/Zend/View/Helper/HtmlList.php
new file mode 100755
index 0000000000..29c79c73c1
--- /dev/null
+++ b/library/Zend/View/Helper/HtmlList.php
@@ -0,0 +1,58 @@
+getView()->plugin('escapeHtml');
+ $item = $escaper($item);
+ }
+ $list .= '' . $item . '' . self::EOL;
+ } else {
+ $itemLength = 5 + strlen(self::EOL);
+ if ($itemLength < strlen($list)) {
+ $list = substr($list, 0, strlen($list) - $itemLength)
+ . $this($item, $ordered, $attribs, $escape) . '' . self::EOL;
+ } else {
+ $list .= '' . $this($item, $ordered, $attribs, $escape) . '' . self::EOL;
+ }
+ }
+ }
+
+ if ($attribs) {
+ $attribs = $this->htmlAttribs($attribs);
+ } else {
+ $attribs = '';
+ }
+
+ $tag = ($ordered) ? 'ol' : 'ul';
+
+ return '<' . $tag . $attribs . '>' . self::EOL . $list . '' . $tag . '>' . self::EOL;
+ }
+}
diff --git a/library/Zend/View/Helper/HtmlObject.php b/library/Zend/View/Helper/HtmlObject.php
new file mode 100755
index 0000000000..17c2822f35
--- /dev/null
+++ b/library/Zend/View/Helper/HtmlObject.php
@@ -0,0 +1,63 @@
+ $data, 'type' => $type), $attribs);
+
+ // Params
+ $paramHtml = array();
+ $closingBracket = $this->getClosingBracket();
+
+ foreach ($params as $param => $options) {
+ if (is_string($options)) {
+ $options = array('value' => $options);
+ }
+
+ $options = array_merge(array('name' => $param), $options);
+
+ $paramHtml[] = 'htmlAttribs($options) . $closingBracket;
+ }
+
+ // Content
+ if (is_array($content)) {
+ $content = implode(self::EOL, $content);
+ }
+
+ // Object header
+ $xhtml = '';
+
+ return $xhtml;
+ }
+}
diff --git a/library/Zend/View/Helper/HtmlPage.php b/library/Zend/View/Helper/HtmlPage.php
new file mode 100755
index 0000000000..ee170c1b0c
--- /dev/null
+++ b/library/Zend/View/Helper/HtmlPage.php
@@ -0,0 +1,51 @@
+ self::ATTRIB_CLASSID);
+
+ /**
+ * Output a html object tag
+ *
+ * @param string $data The html url
+ * @param array $attribs Attribs for the object tag
+ * @param array $params Params for in the object tag
+ * @param string $content Alternative content
+ * @return string
+ */
+ public function __invoke($data, array $attribs = array(), array $params = array(), $content = null)
+ {
+ // Attribs
+ $attribs = array_merge($this->attribs, $attribs);
+
+ // Params
+ $params = array_merge(array('data' => $data), $params);
+
+ $htmlObject = $this->getView()->plugin('htmlObject');
+ return $htmlObject($data, self::TYPE, $attribs, $params, $content);
+ }
+}
diff --git a/library/Zend/View/Helper/HtmlQuicktime.php b/library/Zend/View/Helper/HtmlQuicktime.php
new file mode 100755
index 0000000000..629d6efa32
--- /dev/null
+++ b/library/Zend/View/Helper/HtmlQuicktime.php
@@ -0,0 +1,56 @@
+ self::ATTRIB_CLASSID, 'codebase' => self::ATTRIB_CODEBASE);
+
+ /**
+ * Output a quicktime movie object tag
+ *
+ * @param string $data The quicktime file
+ * @param array $attribs Attribs for the object tag
+ * @param array $params Params for in the object tag
+ * @param string $content Alternative content
+ * @return string
+ */
+ public function __invoke($data, array $attribs = array(), array $params = array(), $content = null)
+ {
+ // Attrs
+ $attribs = array_merge($this->attribs, $attribs);
+
+ // Params
+ $params = array_merge(array('src' => $data), $params);
+
+ $htmlObject = $this->getView()->plugin('htmlObject');
+ return $htmlObject($data, self::TYPE, $attribs, $params, $content);
+ }
+}
diff --git a/library/Zend/View/Helper/Identity.php b/library/Zend/View/Helper/Identity.php
new file mode 100755
index 0000000000..4fe1453736
--- /dev/null
+++ b/library/Zend/View/Helper/Identity.php
@@ -0,0 +1,69 @@
+authenticationService instanceof AuthenticationService) {
+ throw new Exception\RuntimeException('No AuthenticationService instance provided');
+ }
+
+ if (!$this->authenticationService->hasIdentity()) {
+ return null;
+ }
+
+ return $this->authenticationService->getIdentity();
+ }
+
+ /**
+ * Set AuthenticationService instance
+ *
+ * @param AuthenticationService $authenticationService
+ * @return Identity
+ */
+ public function setAuthenticationService(AuthenticationService $authenticationService)
+ {
+ $this->authenticationService = $authenticationService;
+ return $this;
+ }
+
+ /**
+ * Get AuthenticationService instance
+ *
+ * @return AuthenticationService
+ */
+ public function getAuthenticationService()
+ {
+ return $this->authenticationService;
+ }
+}
diff --git a/library/Zend/View/Helper/InlineScript.php b/library/Zend/View/Helper/InlineScript.php
new file mode 100755
index 0000000000..57dace7d6f
--- /dev/null
+++ b/library/Zend/View/Helper/InlineScript.php
@@ -0,0 +1,42 @@
+response instanceof Response) {
+ $headers = $this->response->getHeaders();
+ $headers->addHeaderLine('Content-Type', 'application/json');
+ }
+
+ return $data;
+ }
+
+ /**
+ * Set the response object
+ *
+ * @param Response $response
+ * @return Json
+ */
+ public function setResponse(Response $response)
+ {
+ $this->response = $response;
+ return $this;
+ }
+}
diff --git a/library/Zend/View/Helper/Layout.php b/library/Zend/View/Helper/Layout.php
new file mode 100755
index 0000000000..65b630fb24
--- /dev/null
+++ b/library/Zend/View/Helper/Layout.php
@@ -0,0 +1,98 @@
+getRoot();
+ }
+
+ return $this->setTemplate($template);
+ }
+
+ /**
+ * Get layout template
+ *
+ * @return string
+ */
+ public function getLayout()
+ {
+ return $this->getRoot()->getTemplate();
+ }
+
+ /**
+ * Get the root view model
+ *
+ * @throws Exception\RuntimeException
+ * @return null|Model
+ */
+ protected function getRoot()
+ {
+ $helper = $this->getViewModelHelper();
+
+ if (!$helper->hasRoot()) {
+ throw new Exception\RuntimeException(sprintf(
+ '%s: no view model currently registered as root in renderer',
+ __METHOD__
+ ));
+ }
+
+ return $helper->getRoot();
+ }
+
+ /**
+ * Set layout template
+ *
+ * @param string $template
+ * @return Layout
+ */
+ public function setTemplate($template)
+ {
+ $this->getRoot()->setTemplate((string) $template);
+ return $this;
+ }
+
+ /**
+ * Retrieve the view model helper
+ *
+ * @return ViewModel
+ */
+ protected function getViewModelHelper()
+ {
+ if (null === $this->viewModelHelper) {
+ $this->viewModelHelper = $this->getView()->plugin('view_model');
+ }
+
+ return $this->viewModelHelper;
+ }
+}
diff --git a/library/Zend/View/Helper/Navigation.php b/library/Zend/View/Helper/Navigation.php
new file mode 100755
index 0000000000..015a5ec020
--- /dev/null
+++ b/library/Zend/View/Helper/Navigation.php
@@ -0,0 +1,346 @@
+setContainer($container);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Magic overload: Proxy to other navigation helpers or the container
+ *
+ * Examples of usage from a view script or layout:
+ *
+ * // proxy to Menu helper and render container:
+ * echo $this->navigation()->menu();
+ *
+ * // proxy to Breadcrumbs helper and set indentation:
+ * $this->navigation()->breadcrumbs()->setIndent(8);
+ *
+ * // proxy to container and find all pages with 'blog' route:
+ * $blogPages = $this->navigation()->findAllByRoute('blog');
+ *
+ *
+ * @param string $method helper name or method name in container
+ * @param array $arguments [optional] arguments to pass
+ * @throws \Zend\View\Exception\ExceptionInterface if proxying to a helper, and the
+ * helper is not an instance of the
+ * interface specified in
+ * {@link findHelper()}
+ * @throws \Zend\Navigation\Exception\ExceptionInterface if method does not exist in container
+ * @return mixed returns what the proxied call returns
+ */
+ public function __call($method, array $arguments = array())
+ {
+ // check if call should proxy to another helper
+ $helper = $this->findHelper($method, false);
+ if ($helper) {
+ if ($helper instanceof ServiceLocatorAwareInterface && $this->getServiceLocator()) {
+ $helper->setServiceLocator($this->getServiceLocator());
+ }
+ return call_user_func_array($helper, $arguments);
+ }
+
+ // default behaviour: proxy call to container
+ return parent::__call($method, $arguments);
+ }
+
+ /**
+ * Renders helper
+ *
+ * @param AbstractContainer $container
+ * @return string
+ * @throws Exception\RuntimeException
+ */
+ public function render($container = null)
+ {
+ return $this->findHelper($this->getDefaultProxy())->render($container);
+ }
+
+ /**
+ * Returns the helper matching $proxy
+ *
+ * The helper must implement the interface
+ * {@link Zend\View\Helper\Navigation\Helper}.
+ *
+ * @param string $proxy helper name
+ * @param bool $strict [optional] whether exceptions should be
+ * thrown if something goes
+ * wrong. Default is true.
+ * @throws Exception\RuntimeException if $strict is true and helper cannot be found
+ * @return \Zend\View\Helper\Navigation\HelperInterface helper instance
+ */
+ public function findHelper($proxy, $strict = true)
+ {
+ $plugins = $this->getPluginManager();
+ if (!$plugins->has($proxy)) {
+ if ($strict) {
+ throw new Exception\RuntimeException(sprintf(
+ 'Failed to find plugin for %s',
+ $proxy
+ ));
+ }
+ return false;
+ }
+
+ $helper = $plugins->get($proxy);
+ $container = $this->getContainer();
+ $hash = spl_object_hash($container) . spl_object_hash($helper);
+
+ if (!isset($this->injected[$hash])) {
+ $helper->setContainer();
+ $this->inject($helper);
+ $this->injected[$hash] = true;
+ } else {
+ if ($this->getInjectContainer()) {
+ $helper->setContainer($container);
+ }
+ }
+
+ return $helper;
+ }
+
+ /**
+ * Injects container, ACL, and translator to the given $helper if this
+ * helper is configured to do so
+ *
+ * @param NavigationHelper $helper helper instance
+ * @return void
+ */
+ protected function inject(NavigationHelper $helper)
+ {
+ if ($this->getInjectContainer() && !$helper->hasContainer()) {
+ $helper->setContainer($this->getContainer());
+ }
+
+ if ($this->getInjectAcl()) {
+ if (!$helper->hasAcl()) {
+ $helper->setAcl($this->getAcl());
+ }
+ if (!$helper->hasRole()) {
+ $helper->setRole($this->getRole());
+ }
+ }
+
+ if ($this->getInjectTranslator() && !$helper->hasTranslator()) {
+ $helper->setTranslator(
+ $this->getTranslator(),
+ $this->getTranslatorTextDomain()
+ );
+ }
+ }
+
+ /**
+ * Sets the default proxy to use in {@link render()}
+ *
+ * @param string $proxy default proxy
+ * @return Navigation
+ */
+ public function setDefaultProxy($proxy)
+ {
+ $this->defaultProxy = (string) $proxy;
+ return $this;
+ }
+
+ /**
+ * Returns the default proxy to use in {@link render()}
+ *
+ * @return string
+ */
+ public function getDefaultProxy()
+ {
+ return $this->defaultProxy;
+ }
+
+ /**
+ * Sets whether container should be injected when proxying
+ *
+ * @param bool $injectContainer
+ * @return Navigation
+ */
+ public function setInjectContainer($injectContainer = true)
+ {
+ $this->injectContainer = (bool) $injectContainer;
+ return $this;
+ }
+
+ /**
+ * Returns whether container should be injected when proxying
+ *
+ * @return bool
+ */
+ public function getInjectContainer()
+ {
+ return $this->injectContainer;
+ }
+
+ /**
+ * Sets whether ACL should be injected when proxying
+ *
+ * @param bool $injectAcl
+ * @return Navigation
+ */
+ public function setInjectAcl($injectAcl = true)
+ {
+ $this->injectAcl = (bool) $injectAcl;
+ return $this;
+ }
+
+ /**
+ * Returns whether ACL should be injected when proxying
+ *
+ * @return bool
+ */
+ public function getInjectAcl()
+ {
+ return $this->injectAcl;
+ }
+
+ /**
+ * Sets whether translator should be injected when proxying
+ *
+ * @param bool $injectTranslator
+ * @return Navigation
+ */
+ public function setInjectTranslator($injectTranslator = true)
+ {
+ $this->injectTranslator = (bool) $injectTranslator;
+ return $this;
+ }
+
+ /**
+ * Returns whether translator should be injected when proxying
+ *
+ * @return bool
+ */
+ public function getInjectTranslator()
+ {
+ return $this->injectTranslator;
+ }
+
+ /**
+ * Set manager for retrieving navigation helpers
+ *
+ * @param Navigation\PluginManager $plugins
+ * @return Navigation
+ */
+ public function setPluginManager(Navigation\PluginManager $plugins)
+ {
+ $renderer = $this->getView();
+ if ($renderer) {
+ $plugins->setRenderer($renderer);
+ }
+ $this->plugins = $plugins;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve plugin loader for navigation helpers
+ *
+ * Lazy-loads an instance of Navigation\HelperLoader if none currently
+ * registered.
+ *
+ * @return Navigation\PluginManager
+ */
+ public function getPluginManager()
+ {
+ if (null === $this->plugins) {
+ $this->setPluginManager(new Navigation\PluginManager());
+ }
+
+ return $this->plugins;
+ }
+
+ /**
+ * Set the View object
+ *
+ * @param Renderer $view
+ * @return self
+ */
+ public function setView(Renderer $view)
+ {
+ parent::setView($view);
+ if ($view && $this->plugins) {
+ $this->plugins->setRenderer($view);
+ }
+ return $this;
+ }
+}
diff --git a/library/Zend/View/Helper/Navigation/AbstractHelper.php b/library/Zend/View/Helper/Navigation/AbstractHelper.php
new file mode 100755
index 0000000000..1b997683ff
--- /dev/null
+++ b/library/Zend/View/Helper/Navigation/AbstractHelper.php
@@ -0,0 +1,945 @@
+getContainer(), $method),
+ $arguments);
+ }
+
+ /**
+ * Magic overload: Proxy to {@link render()}.
+ *
+ * This method will trigger an E_USER_ERROR if rendering the helper causes
+ * an exception to be thrown.
+ *
+ * Implements {@link HelperInterface::__toString()}.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ try {
+ return $this->render();
+ } catch (\Exception $e) {
+ $msg = get_class($e) . ': ' . $e->getMessage();
+ trigger_error($msg, E_USER_ERROR);
+ return '';
+ }
+ }
+
+ /**
+ * Finds the deepest active page in the given container
+ *
+ * @param Navigation\AbstractContainer $container container to search
+ * @param int|null $minDepth [optional] minimum depth
+ * required for page to be
+ * valid. Default is to use
+ * {@link getMinDepth()}. A
+ * null value means no minimum
+ * depth required.
+ * @param int|null $maxDepth [optional] maximum depth
+ * a page can have to be
+ * valid. Default is to use
+ * {@link getMaxDepth()}. A
+ * null value means no maximum
+ * depth required.
+ * @return array an associative array with
+ * the values 'depth' and
+ * 'page', or an empty array
+ * if not found
+ */
+ public function findActive($container, $minDepth = null, $maxDepth = -1)
+ {
+ $this->parseContainer($container);
+ if (!is_int($minDepth)) {
+ $minDepth = $this->getMinDepth();
+ }
+ if ((!is_int($maxDepth) || $maxDepth < 0) && null !== $maxDepth) {
+ $maxDepth = $this->getMaxDepth();
+ }
+
+ $found = null;
+ $foundDepth = -1;
+ $iterator = new RecursiveIteratorIterator(
+ $container,
+ RecursiveIteratorIterator::CHILD_FIRST
+ );
+
+ /** @var \Zend\Navigation\Page\AbstractPage $page */
+ foreach ($iterator as $page) {
+ $currDepth = $iterator->getDepth();
+ if ($currDepth < $minDepth || !$this->accept($page)) {
+ // page is not accepted
+ continue;
+ }
+
+ if ($page->isActive(false) && $currDepth > $foundDepth) {
+ // found an active page at a deeper level than before
+ $found = $page;
+ $foundDepth = $currDepth;
+ }
+ }
+
+ if (is_int($maxDepth) && $foundDepth > $maxDepth) {
+ while ($foundDepth > $maxDepth) {
+ if (--$foundDepth < $minDepth) {
+ $found = null;
+ break;
+ }
+
+ $found = $found->getParent();
+ if (!$found instanceof AbstractPage) {
+ $found = null;
+ break;
+ }
+ }
+ }
+
+ if ($found) {
+ return array('page' => $found, 'depth' => $foundDepth);
+ }
+
+ return array();
+ }
+
+ /**
+ * Verifies container and eventually fetches it from service locator if it is a string
+ *
+ * @param Navigation\AbstractContainer|string|null $container
+ * @throws Exception\InvalidArgumentException
+ */
+ protected function parseContainer(&$container = null)
+ {
+ if (null === $container) {
+ return;
+ }
+
+ if (is_string($container)) {
+ if (!$this->getServiceLocator()) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Attempted to set container with alias "%s" but no ServiceLocator was set',
+ $container
+ ));
+ }
+
+ /**
+ * Load the navigation container from the root service locator
+ *
+ * The navigation container is probably located in Zend\ServiceManager\ServiceManager
+ * and not in the View\HelperPluginManager. If the set service locator is a
+ * HelperPluginManager, access the navigation container via the main service locator.
+ */
+ $sl = $this->getServiceLocator();
+ if ($sl instanceof View\HelperPluginManager) {
+ $sl = $sl->getServiceLocator();
+ }
+ $container = $sl->get($container);
+ return;
+ }
+
+ if (!$container instanceof Navigation\AbstractContainer) {
+ throw new Exception\InvalidArgumentException(
+ 'Container must be a string alias or an instance of ' .
+ 'Zend\Navigation\AbstractContainer'
+ );
+ }
+ }
+
+ // Iterator filter methods:
+
+ /**
+ * Determines whether a page should be accepted when iterating
+ *
+ * Default listener may be 'overridden' by attaching listener to 'isAllowed'
+ * method. Listener must be 'short circuited' if overriding default ACL
+ * listener.
+ *
+ * Rules:
+ * - If a page is not visible it is not accepted, unless RenderInvisible has
+ * been set to true
+ * - If $useAcl is true (default is true):
+ * - Page is accepted if listener returns true, otherwise false
+ * - If page is accepted and $recursive is true, the page
+ * will not be accepted if it is the descendant of a non-accepted page
+ *
+ * @param AbstractPage $page page to check
+ * @param bool $recursive [optional] if true, page will not be
+ * accepted if it is the descendant of
+ * a page that is not accepted. Default
+ * is true
+ *
+ * @return bool Whether page should be accepted
+ */
+ public function accept(AbstractPage $page, $recursive = true)
+ {
+ $accept = true;
+
+ if (!$page->isVisible(false) && !$this->getRenderInvisible()) {
+ $accept = false;
+ } elseif ($this->getUseAcl()) {
+ $acl = $this->getAcl();
+ $role = $this->getRole();
+ $params = array('acl' => $acl, 'page' => $page, 'role' => $role);
+ $accept = $this->isAllowed($params);
+ }
+
+ if ($accept && $recursive) {
+ $parent = $page->getParent();
+
+ if ($parent instanceof AbstractPage) {
+ $accept = $this->accept($parent, true);
+ }
+ }
+
+ return $accept;
+ }
+
+ /**
+ * Determines whether a page should be allowed given certain parameters
+ *
+ * @param array $params
+ * @return bool
+ */
+ protected function isAllowed($params)
+ {
+ $results = $this->getEventManager()->trigger(__FUNCTION__, $this, $params);
+ return $results->last();
+ }
+
+ // Util methods:
+
+ /**
+ * Retrieve whitespace representation of $indent
+ *
+ * @param int|string $indent
+ * @return string
+ */
+ protected function getWhitespace($indent)
+ {
+ if (is_int($indent)) {
+ $indent = str_repeat(' ', $indent);
+ }
+
+ return (string) $indent;
+ }
+
+ /**
+ * Converts an associative array to a string of tag attributes.
+ *
+ * Overloads {@link View\Helper\AbstractHtmlElement::htmlAttribs()}.
+ *
+ * @param array $attribs an array where each key-value pair is converted
+ * to an attribute name and value
+ * @return string
+ */
+ protected function htmlAttribs($attribs)
+ {
+ // filter out null values and empty string values
+ foreach ($attribs as $key => $value) {
+ if ($value === null || (is_string($value) && !strlen($value))) {
+ unset($attribs[$key]);
+ }
+ }
+
+ return parent::htmlAttribs($attribs);
+ }
+
+ /**
+ * Returns an HTML string containing an 'a' element for the given page
+ *
+ * @param AbstractPage $page page to generate HTML for
+ * @return string HTML string (Label)
+ */
+ public function htmlify(AbstractPage $page)
+ {
+ $label = $this->translate($page->getLabel(), $page->getTextDomain());
+ $title = $this->translate($page->getTitle(), $page->getTextDomain());
+
+ // get attribs for anchor element
+ $attribs = array(
+ 'id' => $page->getId(),
+ 'title' => $title,
+ 'class' => $page->getClass(),
+ 'href' => $page->getHref(),
+ 'target' => $page->getTarget()
+ );
+
+ /** @var \Zend\View\Helper\EscapeHtml $escaper */
+ $escaper = $this->view->plugin('escapeHtml');
+ $label = $escaper($label);
+
+ return 'htmlAttribs($attribs) . '>' . $label . '';
+ }
+
+ /**
+ * Translate a message (for label, title, …)
+ *
+ * @param string $message ID of the message to translate
+ * @param string $textDomain Text domain (category name for the translations)
+ * @return string Translated message
+ */
+ protected function translate($message, $textDomain = null)
+ {
+ if (is_string($message) && !empty($message)) {
+ if (null !== ($translator = $this->getTranslator())) {
+ if (null === $textDomain) {
+ $textDomain = $this->getTranslatorTextDomain();
+ }
+
+ return $translator->translate($message, $textDomain);
+ }
+ }
+
+ return $message;
+ }
+
+ /**
+ * Normalize an ID
+ *
+ * Overrides {@link View\Helper\AbstractHtmlElement::normalizeId()}.
+ *
+ * @param string $value
+ * @return string
+ */
+ protected function normalizeId($value)
+ {
+ $prefix = get_class($this);
+ $prefix = strtolower(trim(substr($prefix, strrpos($prefix, '\\')), '\\'));
+
+ return $prefix . '-' . $value;
+ }
+
+ /**
+ * Sets ACL to use when iterating pages
+ *
+ * Implements {@link HelperInterface::setAcl()}.
+ *
+ * @param Acl\AclInterface $acl ACL object.
+ * @return AbstractHelper
+ */
+ public function setAcl(Acl\AclInterface $acl = null)
+ {
+ $this->acl = $acl;
+ return $this;
+ }
+
+ /**
+ * Returns ACL or null if it isn't set using {@link setAcl()} or
+ * {@link setDefaultAcl()}
+ *
+ * Implements {@link HelperInterface::getAcl()}.
+ *
+ * @return Acl\AclInterface|null ACL object or null
+ */
+ public function getAcl()
+ {
+ if ($this->acl === null && static::$defaultAcl !== null) {
+ return static::$defaultAcl;
+ }
+
+ return $this->acl;
+ }
+
+ /**
+ * Checks if the helper has an ACL instance
+ *
+ * Implements {@link HelperInterface::hasAcl()}.
+ *
+ * @return bool
+ */
+ public function hasAcl()
+ {
+ if ($this->acl instanceof Acl\Acl
+ || static::$defaultAcl instanceof Acl\Acl
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Set the event manager.
+ *
+ * @param EventManagerInterface $events
+ * @return AbstractHelper
+ */
+ public function setEventManager(EventManagerInterface $events)
+ {
+ $events->setIdentifiers(array(
+ __CLASS__,
+ get_called_class(),
+ ));
+
+ $this->events = $events;
+
+ $this->setDefaultListeners();
+
+ return $this;
+ }
+
+ /**
+ * Get the event manager.
+ *
+ * @return EventManagerInterface
+ */
+ public function getEventManager()
+ {
+ if (null === $this->events) {
+ $this->setEventManager(new EventManager());
+ }
+
+ return $this->events;
+ }
+
+ /**
+ * Sets navigation container the helper operates on by default
+ *
+ * Implements {@link HelperInterface::setContainer()}.
+ *
+ * @param string|Navigation\AbstractContainer $container Default is null, meaning container will be reset.
+ * @return AbstractHelper
+ */
+ public function setContainer($container = null)
+ {
+ $this->parseContainer($container);
+ $this->container = $container;
+
+ return $this;
+ }
+
+ /**
+ * Returns the navigation container helper operates on by default
+ *
+ * Implements {@link HelperInterface::getContainer()}.
+ *
+ * If no container is set, a new container will be instantiated and
+ * stored in the helper.
+ *
+ * @return Navigation\AbstractContainer navigation container
+ */
+ public function getContainer()
+ {
+ if (null === $this->container) {
+ $this->container = new Navigation\Navigation();
+ }
+
+ return $this->container;
+ }
+
+ /**
+ * Checks if the helper has a container
+ *
+ * Implements {@link HelperInterface::hasContainer()}.
+ *
+ * @return bool
+ */
+ public function hasContainer()
+ {
+ return null !== $this->container;
+ }
+
+ /**
+ * Set the indentation string for using in {@link render()}, optionally a
+ * number of spaces to indent with
+ *
+ * @param string|int $indent
+ * @return AbstractHelper
+ */
+ public function setIndent($indent)
+ {
+ $this->indent = $this->getWhitespace($indent);
+ return $this;
+ }
+
+ /**
+ * Returns indentation
+ *
+ * @return string
+ */
+ public function getIndent()
+ {
+ return $this->indent;
+ }
+
+ /**
+ * Sets the maximum depth a page can have to be included when rendering
+ *
+ * @param int $maxDepth Default is null, which sets no maximum depth.
+ * @return AbstractHelper
+ */
+ public function setMaxDepth($maxDepth = null)
+ {
+ if (null === $maxDepth || is_int($maxDepth)) {
+ $this->maxDepth = $maxDepth;
+ } else {
+ $this->maxDepth = (int) $maxDepth;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns maximum depth a page can have to be included when rendering
+ *
+ * @return int|null
+ */
+ public function getMaxDepth()
+ {
+ return $this->maxDepth;
+ }
+
+ /**
+ * Sets the minimum depth a page must have to be included when rendering
+ *
+ * @param int $minDepth Default is null, which sets no minimum depth.
+ * @return AbstractHelper
+ */
+ public function setMinDepth($minDepth = null)
+ {
+ if (null === $minDepth || is_int($minDepth)) {
+ $this->minDepth = $minDepth;
+ } else {
+ $this->minDepth = (int) $minDepth;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns minimum depth a page must have to be included when rendering
+ *
+ * @return int|null
+ */
+ public function getMinDepth()
+ {
+ if (!is_int($this->minDepth) || $this->minDepth < 0) {
+ return 0;
+ }
+
+ return $this->minDepth;
+ }
+
+ /**
+ * Render invisible items?
+ *
+ * @param bool $renderInvisible
+ * @return AbstractHelper
+ */
+ public function setRenderInvisible($renderInvisible = true)
+ {
+ $this->renderInvisible = (bool) $renderInvisible;
+ return $this;
+ }
+
+ /**
+ * Return renderInvisible flag
+ *
+ * @return bool
+ */
+ public function getRenderInvisible()
+ {
+ return $this->renderInvisible;
+ }
+
+ /**
+ * Sets ACL role(s) to use when iterating pages
+ *
+ * Implements {@link HelperInterface::setRole()}.
+ *
+ * @param mixed $role [optional] role to set. Expects a string, an
+ * instance of type {@link Acl\Role\RoleInterface}, or null. Default
+ * is null, which will set no role.
+ * @return AbstractHelper
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setRole($role = null)
+ {
+ if (null === $role || is_string($role) ||
+ $role instanceof Acl\Role\RoleInterface
+ ) {
+ $this->role = $role;
+ } else {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '$role must be a string, null, or an instance of '
+ . 'Zend\Permissions\Role\RoleInterface; %s given',
+ (is_object($role) ? get_class($role) : gettype($role))
+ ));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns ACL role to use when iterating pages, or null if it isn't set
+ * using {@link setRole()} or {@link setDefaultRole()}
+ *
+ * Implements {@link HelperInterface::getRole()}.
+ *
+ * @return string|Acl\Role\RoleInterface|null
+ */
+ public function getRole()
+ {
+ if ($this->role === null && static::$defaultRole !== null) {
+ return static::$defaultRole;
+ }
+
+ return $this->role;
+ }
+
+ /**
+ * Checks if the helper has an ACL role
+ *
+ * Implements {@link HelperInterface::hasRole()}.
+ *
+ * @return bool
+ */
+ public function hasRole()
+ {
+ if ($this->role instanceof Acl\Role\RoleInterface
+ || is_string($this->role)
+ || static::$defaultRole instanceof Acl\Role\RoleInterface
+ || is_string(static::$defaultRole)
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Set the service locator.
+ *
+ * @param ServiceLocatorInterface $serviceLocator
+ * @return AbstractHelper
+ */
+ public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
+ {
+ $this->serviceLocator = $serviceLocator;
+ return $this;
+ }
+
+ /**
+ * Get the service locator.
+ *
+ * @return ServiceLocatorInterface
+ */
+ public function getServiceLocator()
+ {
+ return $this->serviceLocator;
+ }
+
+ // Translator methods - Good candidate to refactor as a trait with PHP 5.4
+
+ /**
+ * Sets translator to use in helper
+ *
+ * @param Translator $translator [optional] translator.
+ * Default is null, which sets no translator.
+ * @param string $textDomain [optional] text domain
+ * Default is null, which skips setTranslatorTextDomain
+ * @return AbstractHelper
+ */
+ public function setTranslator(Translator $translator = null, $textDomain = null)
+ {
+ $this->translator = $translator;
+ if (null !== $textDomain) {
+ $this->setTranslatorTextDomain($textDomain);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns translator used in helper
+ *
+ * @return Translator|null
+ */
+ public function getTranslator()
+ {
+ if (! $this->isTranslatorEnabled()) {
+ return null;
+ }
+
+ return $this->translator;
+ }
+
+ /**
+ * Checks if the helper has a translator
+ *
+ * @return bool
+ */
+ public function hasTranslator()
+ {
+ return (bool) $this->getTranslator();
+ }
+
+ /**
+ * Sets whether translator is enabled and should be used
+ *
+ * @param bool $enabled
+ * @return AbstractHelper
+ */
+ public function setTranslatorEnabled($enabled = true)
+ {
+ $this->translatorEnabled = (bool) $enabled;
+ return $this;
+ }
+
+ /**
+ * Returns whether translator is enabled and should be used
+ *
+ * @return bool
+ */
+ public function isTranslatorEnabled()
+ {
+ return $this->translatorEnabled;
+ }
+
+ /**
+ * Set translation text domain
+ *
+ * @param string $textDomain
+ * @return AbstractHelper
+ */
+ public function setTranslatorTextDomain($textDomain = 'default')
+ {
+ $this->translatorTextDomain = $textDomain;
+ return $this;
+ }
+
+ /**
+ * Return the translation text domain
+ *
+ * @return string
+ */
+ public function getTranslatorTextDomain()
+ {
+ return $this->translatorTextDomain;
+ }
+
+ /**
+ * Sets whether ACL should be used
+ *
+ * Implements {@link HelperInterface::setUseAcl()}.
+ *
+ * @param bool $useAcl
+ * @return AbstractHelper
+ */
+ public function setUseAcl($useAcl = true)
+ {
+ $this->useAcl = (bool) $useAcl;
+ return $this;
+ }
+
+ /**
+ * Returns whether ACL should be used
+ *
+ * Implements {@link HelperInterface::getUseAcl()}.
+ *
+ * @return bool
+ */
+ public function getUseAcl()
+ {
+ return $this->useAcl;
+ }
+
+ // Static methods:
+
+ /**
+ * Sets default ACL to use if another ACL is not explicitly set
+ *
+ * @param Acl\AclInterface $acl [optional] ACL object. Default is null, which
+ * sets no ACL object.
+ * @return void
+ */
+ public static function setDefaultAcl(Acl\AclInterface $acl = null)
+ {
+ static::$defaultAcl = $acl;
+ }
+
+ /**
+ * Sets default ACL role(s) to use when iterating pages if not explicitly
+ * set later with {@link setRole()}
+ *
+ * @param mixed $role [optional] role to set. Expects null, string, or an
+ * instance of {@link Acl\Role\RoleInterface}. Default is null, which
+ * sets no default role.
+ * @return void
+ * @throws Exception\InvalidArgumentException if role is invalid
+ */
+ public static function setDefaultRole($role = null)
+ {
+ if (null === $role
+ || is_string($role)
+ || $role instanceof Acl\Role\RoleInterface
+ ) {
+ static::$defaultRole = $role;
+ } else {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '$role must be null|string|Zend\Permissions\Role\RoleInterface; received "%s"',
+ (is_object($role) ? get_class($role) : gettype($role))
+ ));
+ }
+ }
+
+ /**
+ * Attaches default ACL listeners, if ACLs are in use
+ */
+ protected function setDefaultListeners()
+ {
+ if (!$this->getUseAcl()) {
+ return;
+ }
+
+ $this->getEventManager()->getSharedManager()->attach(
+ 'Zend\View\Helper\Navigation\AbstractHelper',
+ 'isAllowed',
+ array('Zend\View\Helper\Navigation\Listener\AclListener', 'accept')
+ );
+ }
+}
diff --git a/library/Zend/View/Helper/Navigation/Breadcrumbs.php b/library/Zend/View/Helper/Navigation/Breadcrumbs.php
new file mode 100755
index 0000000000..5337ca8e07
--- /dev/null
+++ b/library/Zend/View/Helper/Navigation/Breadcrumbs.php
@@ -0,0 +1,292 @@
+setContainer($container);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Renders helper
+ *
+ * Implements {@link HelperInterface::render()}.
+ *
+ * @param AbstractContainer $container [optional] container to render. Default is
+ * to render the container registered in the helper.
+ * @return string
+ */
+ public function render($container = null)
+ {
+ $partial = $this->getPartial();
+ if ($partial) {
+ return $this->renderPartial($container, $partial);
+ }
+
+ return $this->renderStraight($container);
+ }
+
+ /**
+ * Renders breadcrumbs by chaining 'a' elements with the separator
+ * registered in the helper
+ *
+ * @param AbstractContainer $container [optional] container to render. Default is
+ * to render the container registered in the helper.
+ * @return string
+ */
+ public function renderStraight($container = null)
+ {
+ $this->parseContainer($container);
+ if (null === $container) {
+ $container = $this->getContainer();
+ }
+
+ // find deepest active
+ if (!$active = $this->findActive($container)) {
+ return '';
+ }
+
+ $active = $active['page'];
+
+ // put the deepest active page last in breadcrumbs
+ if ($this->getLinkLast()) {
+ $html = $this->htmlify($active);
+ } else {
+ /** @var \Zend\View\Helper\EscapeHtml $escaper */
+ $escaper = $this->view->plugin('escapeHtml');
+ $html = $escaper(
+ $this->translate($active->getLabel(), $active->getTextDomain())
+ );
+ }
+
+ // walk back to root
+ while ($parent = $active->getParent()) {
+ if ($parent instanceof AbstractPage) {
+ // prepend crumb to html
+ $html = $this->htmlify($parent)
+ . $this->getSeparator()
+ . $html;
+ }
+
+ if ($parent === $container) {
+ // at the root of the given container
+ break;
+ }
+
+ $active = $parent;
+ }
+
+ return strlen($html) ? $this->getIndent() . $html : '';
+ }
+
+ /**
+ * Renders the given $container by invoking the partial view helper
+ *
+ * The container will simply be passed on as a model to the view script,
+ * so in the script it will be available in $this->container
.
+ *
+ * @param AbstractContainer $container [optional] container to pass to view script.
+ * Default is to use the container registered
+ * in the helper.
+ * @param string|array $partial [optional] partial view script to use.
+ * Default is to use the partial registered
+ * in the helper. If an array is given, it
+ * is expected to contain two values; the
+ * partial view script to use, and the module
+ * where the script can be found.
+ * @throws Exception\RuntimeException if no partial provided
+ * @throws Exception\InvalidArgumentException if partial is invalid array
+ * @return string helper output
+ */
+ public function renderPartial($container = null, $partial = null)
+ {
+ $this->parseContainer($container);
+ if (null === $container) {
+ $container = $this->getContainer();
+ }
+
+ if (null === $partial) {
+ $partial = $this->getPartial();
+ }
+
+ if (empty($partial)) {
+ throw new Exception\RuntimeException(
+ 'Unable to render menu: No partial view script provided'
+ );
+ }
+
+ // put breadcrumb pages in model
+ $model = array(
+ 'pages' => array(),
+ 'separator' => $this->getSeparator()
+ );
+ $active = $this->findActive($container);
+ if ($active) {
+ $active = $active['page'];
+ $model['pages'][] = $active;
+ while ($parent = $active->getParent()) {
+ if ($parent instanceof AbstractPage) {
+ $model['pages'][] = $parent;
+ } else {
+ break;
+ }
+
+ if ($parent === $container) {
+ // break if at the root of the given container
+ break;
+ }
+
+ $active = $parent;
+ }
+ $model['pages'] = array_reverse($model['pages']);
+ }
+
+ /** @var \Zend\View\Helper\Partial $partialHelper */
+ $partialHelper = $this->view->plugin('partial');
+
+ if (is_array($partial)) {
+ if (count($partial) != 2) {
+ throw new Exception\InvalidArgumentException(
+ 'Unable to render menu: A view partial supplied as '
+ . 'an array must contain two values: partial view '
+ . 'script and module where script can be found'
+ );
+ }
+
+ return $partialHelper($partial[0], $model);
+ }
+
+ return $partialHelper($partial, $model);
+ }
+
+ /**
+ * Sets whether last page in breadcrumbs should be hyperlinked
+ *
+ * @param bool $linkLast whether last page should be hyperlinked
+ * @return Breadcrumbs
+ */
+ public function setLinkLast($linkLast)
+ {
+ $this->linkLast = (bool) $linkLast;
+ return $this;
+ }
+
+ /**
+ * Returns whether last page in breadcrumbs should be hyperlinked
+ *
+ * @return bool
+ */
+ public function getLinkLast()
+ {
+ return $this->linkLast;
+ }
+
+ /**
+ * Sets which partial view script to use for rendering menu
+ *
+ * @param string|array $partial partial view script or null. If an array is
+ * given, it is expected to contain two
+ * values; the partial view script to use,
+ * and the module where the script can be
+ * found.
+ * @return Breadcrumbs
+ */
+ public function setPartial($partial)
+ {
+ if (null === $partial || is_string($partial) || is_array($partial)) {
+ $this->partial = $partial;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns partial view script to use for rendering menu
+ *
+ * @return string|array|null
+ */
+ public function getPartial()
+ {
+ return $this->partial;
+ }
+
+ /**
+ * Sets breadcrumb separator
+ *
+ * @param string $separator separator string
+ * @return Breadcrumbs
+ */
+ public function setSeparator($separator)
+ {
+ if (is_string($separator)) {
+ $this->separator = $separator;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns breadcrumb separator
+ *
+ * @return string breadcrumb separator
+ */
+ public function getSeparator()
+ {
+ return $this->separator;
+ }
+}
diff --git a/library/Zend/View/Helper/Navigation/HelperInterface.php b/library/Zend/View/Helper/Navigation/HelperInterface.php
new file mode 100755
index 0000000000..8b52c8c1da
--- /dev/null
+++ b/library/Zend/View/Helper/Navigation/HelperInterface.php
@@ -0,0 +1,143 @@
+ elements
+ */
+class Links extends AbstractHelper
+{
+ /**
+ * Constants used for specifying which link types to find and render
+ *
+ * @var int
+ */
+ const RENDER_ALTERNATE = 0x0001;
+ const RENDER_STYLESHEET = 0x0002;
+ const RENDER_START = 0x0004;
+ const RENDER_NEXT = 0x0008;
+ const RENDER_PREV = 0x0010;
+ const RENDER_CONTENTS = 0x0020;
+ const RENDER_INDEX = 0x0040;
+ const RENDER_GLOSSARY = 0x0080;
+ const RENDER_COPYRIGHT = 0x0100;
+ const RENDER_CHAPTER = 0x0200;
+ const RENDER_SECTION = 0x0400;
+ const RENDER_SUBSECTION = 0x0800;
+ const RENDER_APPENDIX = 0x1000;
+ const RENDER_HELP = 0x2000;
+ const RENDER_BOOKMARK = 0x4000;
+ const RENDER_CUSTOM = 0x8000;
+ const RENDER_ALL = 0xffff;
+
+ /**
+ * Maps render constants to W3C link types
+ *
+ * @var array
+ */
+ protected static $RELATIONS = array(
+ self::RENDER_ALTERNATE => 'alternate',
+ self::RENDER_STYLESHEET => 'stylesheet',
+ self::RENDER_START => 'start',
+ self::RENDER_NEXT => 'next',
+ self::RENDER_PREV => 'prev',
+ self::RENDER_CONTENTS => 'contents',
+ self::RENDER_INDEX => 'index',
+ self::RENDER_GLOSSARY => 'glossary',
+ self::RENDER_COPYRIGHT => 'copyright',
+ self::RENDER_CHAPTER => 'chapter',
+ self::RENDER_SECTION => 'section',
+ self::RENDER_SUBSECTION => 'subsection',
+ self::RENDER_APPENDIX => 'appendix',
+ self::RENDER_HELP => 'help',
+ self::RENDER_BOOKMARK => 'bookmark',
+ );
+
+ /**
+ * The helper's render flag
+ *
+ * @see render()
+ * @see setRenderFlag()
+ * @var int
+ */
+ protected $renderFlag = self::RENDER_ALL;
+
+ /**
+ * Root container
+ *
+ * Used for preventing methods to traverse above the container given to
+ * the {@link render()} method.
+ *
+ * @see _findRoot()
+ * @var AbstractContainer
+ */
+ protected $root;
+
+ /**
+ * Helper entry point
+ *
+ * @param string|AbstractContainer $container container to operate on
+ * @return Links
+ */
+ public function __invoke($container = null)
+ {
+ if (null !== $container) {
+ $this->setContainer($container);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Magic overload: Proxy calls to {@link findRelation()} or container
+ *
+ * Examples of finder calls:
+ *
+ * // METHOD // SAME AS
+ * $h->findRelNext($page); // $h->findRelation($page, 'rel', 'next')
+ * $h->findRevSection($page); // $h->findRelation($page, 'rev', 'section');
+ * $h->findRelFoo($page); // $h->findRelation($page, 'rel', 'foo');
+ *
+ *
+ * @param string $method
+ * @param array $arguments
+ * @return mixed
+ * @throws Exception\ExceptionInterface
+ */
+ public function __call($method, array $arguments = array())
+ {
+ ErrorHandler::start(E_WARNING);
+ $result = preg_match('/find(Rel|Rev)(.+)/', $method, $match);
+ ErrorHandler::stop();
+ if ($result) {
+ return $this->findRelation($arguments[0],
+ strtolower($match[1]),
+ strtolower($match[2]));
+ }
+
+ return parent::__call($method, $arguments);
+ }
+
+ /**
+ * Renders helper
+ *
+ * Implements {@link HelperInterface::render()}.
+ *
+ * @param AbstractContainer|string|null $container [optional] container to render.
+ * Default is to render the
+ * container registered in the
+ * helper.
+ * @return string
+ */
+ public function render($container = null)
+ {
+ $this->parseContainer($container);
+ if (null === $container) {
+ $container = $this->getContainer();
+ }
+
+ $active = $this->findActive($container);
+ if ($active) {
+ $active = $active['page'];
+ } else {
+ // no active page
+ return '';
+ }
+
+ $output = '';
+ $indent = $this->getIndent();
+ $this->root = $container;
+
+ $result = $this->findAllRelations($active, $this->getRenderFlag());
+ foreach ($result as $attrib => $types) {
+ foreach ($types as $relation => $pages) {
+ foreach ($pages as $page) {
+ $r = $this->renderLink($page, $attrib, $relation);
+ if ($r) {
+ $output .= $indent . $r . self::EOL;
+ }
+ }
+ }
+ }
+
+ $this->root = null;
+
+ // return output (trim last newline by spec)
+ return strlen($output) ? rtrim($output, self::EOL) : '';
+ }
+
+ /**
+ * Renders the given $page as a link element, with $attrib = $relation
+ *
+ * @param AbstractPage $page the page to render the link for
+ * @param string $attrib the attribute to use for $type,
+ * either 'rel' or 'rev'
+ * @param string $relation relation type, muse be one of;
+ * alternate, appendix, bookmark,
+ * chapter, contents, copyright,
+ * glossary, help, home, index, next,
+ * prev, section, start, stylesheet,
+ * subsection
+ * @return string
+ * @throws Exception\DomainException
+ */
+ public function renderLink(AbstractPage $page, $attrib, $relation)
+ {
+ if (!in_array($attrib, array('rel', 'rev'))) {
+ throw new Exception\DomainException(sprintf(
+ 'Invalid relation attribute "%s", must be "rel" or "rev"',
+ $attrib
+ ));
+ }
+
+ if (!$href = $page->getHref()) {
+ return '';
+ }
+
+ // TODO: add more attribs
+ // http://www.w3.org/TR/html401/struct/links.html#h-12.2
+ $attribs = array(
+ $attrib => $relation,
+ 'href' => $href,
+ 'title' => $page->getLabel()
+ );
+
+ return 'htmlAttribs($attribs) .
+ $this->getClosingBracket();
+ }
+
+ // Finder methods:
+
+ /**
+ * Finds all relations (forward and reverse) for the given $page
+ *
+ * The form of the returned array:
+ *
+ * // $page denotes an instance of Zend\Navigation\Page\AbstractPage
+ * $returned = array(
+ * 'rel' => array(
+ * 'alternate' => array($page, $page, $page),
+ * 'start' => array($page),
+ * 'next' => array($page),
+ * 'prev' => array($page),
+ * 'canonical' => array($page)
+ * ),
+ * 'rev' => array(
+ * 'section' => array($page)
+ * )
+ * );
+ *
+ *
+ * @param AbstractPage $page page to find links for
+ * @param null|int
+ * @return array
+ */
+ public function findAllRelations(AbstractPage $page, $flag = null)
+ {
+ if (!is_int($flag)) {
+ $flag = self::RENDER_ALL;
+ }
+
+ $result = array('rel' => array(), 'rev' => array());
+ $native = array_values(static::$RELATIONS);
+
+ foreach (array_keys($result) as $rel) {
+ $meth = 'getDefined' . ucfirst($rel);
+ $types = array_merge($native, array_diff($page->$meth(), $native));
+
+ foreach ($types as $type) {
+ if (!$relFlag = array_search($type, static::$RELATIONS)) {
+ $relFlag = self::RENDER_CUSTOM;
+ }
+ if (!($flag & $relFlag)) {
+ continue;
+ }
+
+ $found = $this->findRelation($page, $rel, $type);
+ if ($found) {
+ if (!is_array($found)) {
+ $found = array($found);
+ }
+ $result[$rel][$type] = $found;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Finds relations of the given $rel=$type from $page
+ *
+ * This method will first look for relations in the page instance, then
+ * by searching the root container if nothing was found in the page.
+ *
+ * @param AbstractPage $page page to find relations for
+ * @param string $rel relation, "rel" or "rev"
+ * @param string $type link type, e.g. 'start', 'next'
+ * @return AbstractPage|array|null
+ * @throws Exception\DomainException if $rel is not "rel" or "rev"
+ */
+ public function findRelation(AbstractPage $page, $rel, $type)
+ {
+ if (!in_array($rel, array('rel', 'rev'))) {
+ throw new Exception\DomainException(sprintf(
+ 'Invalid argument: $rel must be "rel" or "rev"; "%s" given',
+ $rel
+ ));
+ }
+
+ if (!$result = $this->findFromProperty($page, $rel, $type)) {
+ $result = $this->findFromSearch($page, $rel, $type);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Finds relations of given $type for $page by checking if the
+ * relation is specified as a property of $page
+ *
+ * @param AbstractPage $page page to find relations for
+ * @param string $rel relation, 'rel' or 'rev'
+ * @param string $type link type, e.g. 'start', 'next'
+ * @return AbstractPage|array|null
+ */
+ protected function findFromProperty(AbstractPage $page, $rel, $type)
+ {
+ $method = 'get' . ucfirst($rel);
+ $result = $page->$method($type);
+ if ($result) {
+ $result = $this->convertToPages($result);
+ if ($result) {
+ if (!is_array($result)) {
+ $result = array($result);
+ }
+
+ foreach ($result as $key => $page) {
+ if (!$this->accept($page)) {
+ unset($result[$key]);
+ }
+ }
+
+ return count($result) == 1 ? $result[0] : $result;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds relations of given $rel=$type for $page by using the helper to
+ * search for the relation in the root container
+ *
+ * @param AbstractPage $page page to find relations for
+ * @param string $rel relation, 'rel' or 'rev'
+ * @param string $type link type, e.g. 'start', 'next', etc
+ * @return array|null
+ */
+ protected function findFromSearch(AbstractPage $page, $rel, $type)
+ {
+ $found = null;
+
+ $method = 'search' . ucfirst($rel) . ucfirst($type);
+ if (method_exists($this, $method)) {
+ $found = $this->$method($page);
+ }
+
+ return $found;
+ }
+
+ // Search methods:
+
+ /**
+ * Searches the root container for the forward 'start' relation of the given
+ * $page
+ *
+ * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
+ * Refers to the first document in a collection of documents. This link type
+ * tells search engines which document is considered by the author to be the
+ * starting point of the collection.
+ *
+ * @param AbstractPage $page
+ * @return AbstractPage|null
+ */
+ public function searchRelStart(AbstractPage $page)
+ {
+ $found = $this->findRoot($page);
+ if (!$found instanceof AbstractPage) {
+ $found->rewind();
+ $found = $found->current();
+ }
+
+ if ($found === $page || !$this->accept($found)) {
+ $found = null;
+ }
+
+ return $found;
+ }
+
+ /**
+ * Searches the root container for the forward 'next' relation of the given
+ * $page
+ *
+ * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
+ * Refers to the next document in a linear sequence of documents. User
+ * agents may choose to preload the "next" document, to reduce the perceived
+ * load time.
+ *
+ * @param AbstractPage $page
+ * @return AbstractPage|null
+ */
+ public function searchRelNext(AbstractPage $page)
+ {
+ $found = null;
+ $break = false;
+ $iterator = new RecursiveIteratorIterator($this->findRoot($page),
+ RecursiveIteratorIterator::SELF_FIRST);
+ foreach ($iterator as $intermediate) {
+ if ($intermediate === $page) {
+ // current page; break at next accepted page
+ $break = true;
+ continue;
+ }
+
+ if ($break && $this->accept($intermediate)) {
+ $found = $intermediate;
+ break;
+ }
+ }
+
+ return $found;
+ }
+
+ /**
+ * Searches the root container for the forward 'prev' relation of the given
+ * $page
+ *
+ * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
+ * Refers to the previous document in an ordered series of documents. Some
+ * user agents also support the synonym "Previous".
+ *
+ * @param AbstractPage $page
+ * @return AbstractPage|null
+ */
+ public function searchRelPrev(AbstractPage $page)
+ {
+ $found = null;
+ $prev = null;
+ $iterator = new RecursiveIteratorIterator(
+ $this->findRoot($page),
+ RecursiveIteratorIterator::SELF_FIRST);
+ foreach ($iterator as $intermediate) {
+ if (!$this->accept($intermediate)) {
+ continue;
+ }
+ if ($intermediate === $page) {
+ $found = $prev;
+ break;
+ }
+
+ $prev = $intermediate;
+ }
+
+ return $found;
+ }
+
+ /**
+ * Searches the root container for forward 'chapter' relations of the given
+ * $page
+ *
+ * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
+ * Refers to a document serving as a chapter in a collection of documents.
+ *
+ * @param AbstractPage $page
+ * @return AbstractPage|array|null
+ */
+ public function searchRelChapter(AbstractPage $page)
+ {
+ $found = array();
+
+ // find first level of pages
+ $root = $this->findRoot($page);
+
+ // find start page(s)
+ $start = $this->findRelation($page, 'rel', 'start');
+ if (!is_array($start)) {
+ $start = array($start);
+ }
+
+ foreach ($root as $chapter) {
+ // exclude self and start page from chapters
+ if ($chapter !== $page &&
+ !in_array($chapter, $start) &&
+ $this->accept($chapter)) {
+ $found[] = $chapter;
+ }
+ }
+
+ switch (count($found)) {
+ case 0:
+ return null;
+ case 1:
+ return $found[0];
+ default:
+ return $found;
+ }
+ }
+
+ /**
+ * Searches the root container for forward 'section' relations of the given
+ * $page
+ *
+ * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
+ * Refers to a document serving as a section in a collection of documents.
+ *
+ * @param AbstractPage $page
+ * @return AbstractPage|array|null
+ */
+ public function searchRelSection(AbstractPage $page)
+ {
+ $found = array();
+
+ // check if given page has pages and is a chapter page
+ if ($page->hasPages() && $this->findRoot($page)->hasPage($page)) {
+ foreach ($page as $section) {
+ if ($this->accept($section)) {
+ $found[] = $section;
+ }
+ }
+ }
+
+ switch (count($found)) {
+ case 0:
+ return null;
+ case 1:
+ return $found[0];
+ default:
+ return $found;
+ }
+ }
+
+ /**
+ * Searches the root container for forward 'subsection' relations of the
+ * given $page
+ *
+ * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
+ * Refers to a document serving as a subsection in a collection of
+ * documents.
+ *
+ * @param AbstractPage $page
+ * @return AbstractPage|array|null
+ */
+ public function searchRelSubsection(AbstractPage $page)
+ {
+ $found = array();
+
+ if ($page->hasPages()) {
+ // given page has child pages, loop chapters
+ foreach ($this->findRoot($page) as $chapter) {
+ // is page a section?
+ if ($chapter->hasPage($page)) {
+ foreach ($page as $subsection) {
+ if ($this->accept($subsection)) {
+ $found[] = $subsection;
+ }
+ }
+ }
+ }
+ }
+
+ switch (count($found)) {
+ case 0:
+ return null;
+ case 1:
+ return $found[0];
+ default:
+ return $found;
+ }
+ }
+
+ /**
+ * Searches the root container for the reverse 'section' relation of the
+ * given $page
+ *
+ * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
+ * Refers to a document serving as a section in a collection of documents.
+ *
+ * @param AbstractPage $page
+ * @return AbstractPage|null
+ */
+ public function searchRevSection(AbstractPage $page)
+ {
+ $found = null;
+ $parent = $page->getParent();
+ if ($parent) {
+ if ($parent instanceof AbstractPage &&
+ $this->findRoot($page)->hasPage($parent)) {
+ $found = $parent;
+ }
+ }
+
+ return $found;
+ }
+
+ /**
+ * Searches the root container for the reverse 'section' relation of the
+ * given $page
+ *
+ * From {@link http://www.w3.org/TR/html4/types.html#type-links}:
+ * Refers to a document serving as a subsection in a collection of
+ * documents.
+ *
+ * @param AbstractPage $page
+ * @return AbstractPage|null
+ */
+ public function searchRevSubsection(AbstractPage $page)
+ {
+ $found = null;
+ $parent = $page->getParent();
+ if ($parent) {
+ if ($parent instanceof AbstractPage) {
+ $root = $this->findRoot($page);
+ foreach ($root as $chapter) {
+ if ($chapter->hasPage($parent)) {
+ $found = $parent;
+ break;
+ }
+ }
+ }
+ }
+
+ return $found;
+ }
+
+ // Util methods:
+
+ /**
+ * Returns the root container of the given page
+ *
+ * When rendering a container, the render method still store the given
+ * container as the root container, and unset it when done rendering. This
+ * makes sure finder methods will not traverse above the container given
+ * to the render method.
+ *
+ * @param AbstractPage $page
+ * @return AbstractContainer
+ */
+ protected function findRoot(AbstractPage $page)
+ {
+ if ($this->root) {
+ return $this->root;
+ }
+
+ $root = $page;
+
+ while ($parent = $page->getParent()) {
+ $root = $parent;
+ if ($parent instanceof AbstractPage) {
+ $page = $parent;
+ } else {
+ break;
+ }
+ }
+
+ return $root;
+ }
+
+ /**
+ * Converts a $mixed value to an array of pages
+ *
+ * @param mixed $mixed mixed value to get page(s) from
+ * @param bool $recursive whether $value should be looped
+ * if it is an array or a config
+ * @return AbstractPage|array|null
+ */
+ protected function convertToPages($mixed, $recursive = true)
+ {
+ if ($mixed instanceof AbstractPage) {
+ // value is a page instance; return directly
+ return $mixed;
+ } elseif ($mixed instanceof AbstractContainer) {
+ // value is a container; return pages in it
+ $pages = array();
+ foreach ($mixed as $page) {
+ $pages[] = $page;
+ }
+ return $pages;
+ } elseif ($mixed instanceof Traversable) {
+ $mixed = ArrayUtils::iteratorToArray($mixed);
+ } elseif (is_string($mixed)) {
+ // value is a string; make a URI page
+ return AbstractPage::factory(array(
+ 'type' => 'uri',
+ 'uri' => $mixed
+ ));
+ }
+
+ if (is_array($mixed) && !empty($mixed)) {
+ if ($recursive && is_numeric(key($mixed))) {
+ // first key is numeric; assume several pages
+ $pages = array();
+ foreach ($mixed as $value) {
+ $value = $this->convertToPages($value, false);
+ if ($value) {
+ $pages[] = $value;
+ }
+ }
+ return $pages;
+ } else {
+ // pass array to factory directly
+ try {
+ $page = AbstractPage::factory($mixed);
+ return $page;
+ } catch (\Exception $e) {
+ }
+ }
+ }
+
+ // nothing found
+ return null;
+ }
+
+ /**
+ * Sets the helper's render flag
+ *
+ * The helper uses the bitwise '&' operator against the hex values of the
+ * render constants. This means that the flag can is "bitwised" value of
+ * the render constants. Examples:
+ *
+ * // render all links except glossary
+ * $flag = Links:RENDER_ALL ^ Links:RENDER_GLOSSARY;
+ * $helper->setRenderFlag($flag);
+ *
+ * // render only chapters and sections
+ * $flag = Links:RENDER_CHAPTER | Links:RENDER_SECTION;
+ * $helper->setRenderFlag($flag);
+ *
+ * // render only relations that are not native W3C relations
+ * $helper->setRenderFlag(Links:RENDER_CUSTOM);
+ *
+ * // render all relations (default)
+ * $helper->setRenderFlag(Links:RENDER_ALL);
+ *
+ *
+ * Note that custom relations can also be rendered directly using the
+ * {@link renderLink()} method.
+ *
+ * @param int $renderFlag
+ * @return Links
+ */
+ public function setRenderFlag($renderFlag)
+ {
+ $this->renderFlag = (int) $renderFlag;
+
+ return $this;
+ }
+
+ /**
+ * Returns the helper's render flag
+ *
+ * @return int
+ */
+ public function getRenderFlag()
+ {
+ return $this->renderFlag;
+ }
+}
diff --git a/library/Zend/View/Helper/Navigation/Listener/AclListener.php b/library/Zend/View/Helper/Navigation/Listener/AclListener.php
new file mode 100755
index 0000000000..5c8656084e
--- /dev/null
+++ b/library/Zend/View/Helper/Navigation/Listener/AclListener.php
@@ -0,0 +1,55 @@
+getParams();
+ $acl = $params['acl'];
+ $page = $params['page'];
+ $role = $params['role'];
+
+ if (!$acl) {
+ return $accepted;
+ }
+
+ $resource = $page->getResource();
+ $privilege = $page->getPrivilege();
+
+ if ($resource || $privilege) {
+ $accepted = $acl->hasResource($resource)
+ && $acl->isAllowed($role, $resource, $privilege);
+ }
+
+ return $accepted;
+ }
+}
diff --git a/library/Zend/View/Helper/Navigation/Menu.php b/library/Zend/View/Helper/Navigation/Menu.php
new file mode 100755
index 0000000000..29d7199234
--- /dev/null
+++ b/library/Zend/View/Helper/Navigation/Menu.php
@@ -0,0 +1,765 @@
+ element
+ *
+ * @var bool
+ */
+ protected $addClassToListItem = false;
+
+ /**
+ * Whether labels should be escaped
+ *
+ * @var bool
+ */
+ protected $escapeLabels = true;
+
+ /**
+ * Whether only active branch should be rendered
+ *
+ * @var bool
+ */
+ protected $onlyActiveBranch = false;
+
+ /**
+ * Partial view script to use for rendering menu
+ *
+ * @var string|array
+ */
+ protected $partial = null;
+
+ /**
+ * Whether parents should be rendered when only rendering active branch
+ *
+ * @var bool
+ */
+ protected $renderParents = true;
+
+ /**
+ * CSS class to use for the ul element
+ *
+ * @var string
+ */
+ protected $ulClass = 'navigation';
+
+ /**
+ * CSS class to use for the active li element
+ *
+ * @var string
+ */
+ protected $liActiveClass = 'active';
+
+ /**
+ * View helper entry point:
+ * Retrieves helper and optionally sets container to operate on
+ *
+ * @param AbstractContainer $container [optional] container to operate on
+ * @return self
+ */
+ public function __invoke($container = null)
+ {
+ if (null !== $container) {
+ $this->setContainer($container);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Renders menu
+ *
+ * Implements {@link HelperInterface::render()}.
+ *
+ * If a partial view is registered in the helper, the menu will be rendered
+ * using the given partial script. If no partial is registered, the menu
+ * will be rendered as an 'ul' element by the helper's internal method.
+ *
+ * @see renderPartial()
+ * @see renderMenu()
+ *
+ * @param AbstractContainer $container [optional] container to render. Default is
+ * to render the container registered in the helper.
+ * @return string
+ */
+ public function render($container = null)
+ {
+ $partial = $this->getPartial();
+ if ($partial) {
+ return $this->renderPartial($container, $partial);
+ }
+
+ return $this->renderMenu($container);
+ }
+
+ /**
+ * Renders the deepest active menu within [$minDepth, $maxDepth], (called
+ * from {@link renderMenu()})
+ *
+ * @param AbstractContainer $container container to render
+ * @param string $ulClass CSS class for first UL
+ * @param string $indent initial indentation
+ * @param int|null $minDepth minimum depth
+ * @param int|null $maxDepth maximum depth
+ * @param bool $escapeLabels Whether or not to escape the labels
+ * @param bool $addClassToListItem Whether or not page class applied to element
+ * @param string $liActiveClass CSS class for active LI
+ * @return string
+ */
+ protected function renderDeepestMenu(
+ AbstractContainer $container,
+ $ulClass,
+ $indent,
+ $minDepth,
+ $maxDepth,
+ $escapeLabels,
+ $addClassToListItem,
+ $liActiveClass
+ ) {
+ if (!$active = $this->findActive($container, $minDepth - 1, $maxDepth)) {
+ return '';
+ }
+
+ // special case if active page is one below minDepth
+ if ($active['depth'] < $minDepth) {
+ if (!$active['page']->hasPages(!$this->renderInvisible)) {
+ return '';
+ }
+ } elseif (!$active['page']->hasPages(!$this->renderInvisible)) {
+ // found pages has no children; render siblings
+ $active['page'] = $active['page']->getParent();
+ } elseif (is_int($maxDepth) && $active['depth'] +1 > $maxDepth) {
+ // children are below max depth; render siblings
+ $active['page'] = $active['page']->getParent();
+ }
+
+ /* @var $escaper \Zend\View\Helper\EscapeHtmlAttr */
+ $escaper = $this->view->plugin('escapeHtmlAttr');
+ $ulClass = $ulClass ? ' class="' . $escaper($ulClass) . '"' : '';
+ $html = $indent . '' . PHP_EOL;
+
+ foreach ($active['page'] as $subPage) {
+ if (!$this->accept($subPage)) {
+ continue;
+ }
+
+ // render li tag and page
+ $liClasses = array();
+ // Is page active?
+ if ($subPage->isActive(true)) {
+ $liClasses[] = $liActiveClass;
+ }
+ // Add CSS class from page to -
+ if ($addClassToListItem && $subPage->getClass()) {
+ $liClasses[] = $subPage->getClass();
+ }
+ $liClass = empty($liClasses) ? '' : ' class="' . $escaper(implode(' ', $liClasses)) . '"';
+
+ $html .= $indent . '
- ' . PHP_EOL;
+ $html .= $indent . ' ' . $this->htmlify($subPage, $escapeLabels, $addClassToListItem) . PHP_EOL;
+ $html .= $indent . '
' . PHP_EOL;
+ }
+
+ $html .= $indent . '
';
+
+ return $html;
+ }
+
+ /**
+ * Renders helper
+ *
+ * Renders a HTML 'ul' for the given $container. If $container is not given,
+ * the container registered in the helper will be used.
+ *
+ * Available $options:
+ *
+ *
+ * @param AbstractContainer $container [optional] container to create menu from.
+ * Default is to use the container retrieved
+ * from {@link getContainer()}.
+ * @param array $options [optional] options for controlling rendering
+ * @return string
+ */
+ public function renderMenu($container = null, array $options = array())
+ {
+ $this->parseContainer($container);
+ if (null === $container) {
+ $container = $this->getContainer();
+ }
+
+
+ $options = $this->normalizeOptions($options);
+
+ if ($options['onlyActiveBranch'] && !$options['renderParents']) {
+ $html = $this->renderDeepestMenu($container,
+ $options['ulClass'],
+ $options['indent'],
+ $options['minDepth'],
+ $options['maxDepth'],
+ $options['escapeLabels'],
+ $options['addClassToListItem'],
+ $options['liActiveClass']
+ );
+ } else {
+ $html = $this->renderNormalMenu($container,
+ $options['ulClass'],
+ $options['indent'],
+ $options['minDepth'],
+ $options['maxDepth'],
+ $options['onlyActiveBranch'],
+ $options['escapeLabels'],
+ $options['addClassToListItem'],
+ $options['liActiveClass']
+ );
+ }
+
+ return $html;
+ }
+
+ /**
+ * Renders a normal menu (called from {@link renderMenu()})
+ *
+ * @param AbstractContainer $container container to render
+ * @param string $ulClass CSS class for first UL
+ * @param string $indent initial indentation
+ * @param int|null $minDepth minimum depth
+ * @param int|null $maxDepth maximum depth
+ * @param bool $onlyActive render only active branch?
+ * @param bool $escapeLabels Whether or not to escape the labels
+ * @param bool $addClassToListItem Whether or not page class applied to element
+ * @param string $liActiveClass CSS class for active LI
+ * @return string
+ */
+ protected function renderNormalMenu(
+ AbstractContainer $container,
+ $ulClass,
+ $indent,
+ $minDepth,
+ $maxDepth,
+ $onlyActive,
+ $escapeLabels,
+ $addClassToListItem,
+ $liActiveClass
+ ) {
+ $html = '';
+
+ // find deepest active
+ $found = $this->findActive($container, $minDepth, $maxDepth);
+ /* @var $escaper \Zend\View\Helper\EscapeHtmlAttr */
+ $escaper = $this->view->plugin('escapeHtmlAttr');
+
+ if ($found) {
+ $foundPage = $found['page'];
+ $foundDepth = $found['depth'];
+ } else {
+ $foundPage = null;
+ }
+
+ // create iterator
+ $iterator = new RecursiveIteratorIterator($container,
+ RecursiveIteratorIterator::SELF_FIRST);
+ if (is_int($maxDepth)) {
+ $iterator->setMaxDepth($maxDepth);
+ }
+
+ // iterate container
+ $prevDepth = -1;
+ foreach ($iterator as $page) {
+ $depth = $iterator->getDepth();
+ $isActive = $page->isActive(true);
+ if ($depth < $minDepth || !$this->accept($page)) {
+ // page is below minDepth or not accepted by acl/visibility
+ continue;
+ } elseif ($onlyActive && !$isActive) {
+ // page is not active itself, but might be in the active branch
+ $accept = false;
+ if ($foundPage) {
+ if ($foundPage->hasPage($page)) {
+ // accept if page is a direct child of the active page
+ $accept = true;
+ } elseif ($foundPage->getParent()->hasPage($page)) {
+ // page is a sibling of the active page...
+ if (!$foundPage->hasPages(!$this->renderInvisible) ||
+ is_int($maxDepth) && $foundDepth + 1 > $maxDepth) {
+ // accept if active page has no children, or the
+ // children are too deep to be rendered
+ $accept = true;
+ }
+ }
+ }
+
+ if (!$accept) {
+ continue;
+ }
+ }
+
+ // make sure indentation is correct
+ $depth -= $minDepth;
+ $myIndent = $indent . str_repeat(' ', $depth);
+
+ if ($depth > $prevDepth) {
+ // start new ul tag
+ if ($ulClass && $depth == 0) {
+ $ulClass = ' class="' . $escaper($ulClass) . '"';
+ } else {
+ $ulClass = '';
+ }
+ $html .= $myIndent . '' . PHP_EOL;
+ } elseif ($prevDepth > $depth) {
+ // close li/ul tags until we're at current depth
+ for ($i = $prevDepth; $i > $depth; $i--) {
+ $ind = $indent . str_repeat(' ', $i);
+ $html .= $ind . '
' . PHP_EOL;
+ $html .= $ind . '' . PHP_EOL;
+ }
+ // close previous li tag
+ $html .= $myIndent . ' ' . PHP_EOL;
+ } else {
+ // close previous li tag
+ $html .= $myIndent . ' ' . PHP_EOL;
+ }
+
+ // render li tag and page
+ $liClasses = array();
+ // Is page active?
+ if ($isActive) {
+ $liClasses[] = $liActiveClass;
+ }
+ // Add CSS class from page to
+ if ($addClassToListItem && $page->getClass()) {
+ $liClasses[] = $page->getClass();
+ }
+ $liClass = empty($liClasses) ? '' : ' class="' . $escaper(implode(' ', $liClasses)) . '"';
+
+ $html .= $myIndent . ' ' . PHP_EOL
+ . $myIndent . ' ' . $this->htmlify($page, $escapeLabels, $addClassToListItem) . PHP_EOL;
+
+ // store as previous depth for next iteration
+ $prevDepth = $depth;
+ }
+
+ if ($html) {
+ // done iterating container; close open ul/li tags
+ for ($i = $prevDepth+1; $i > 0; $i--) {
+ $myIndent = $indent . str_repeat(' ', $i-1);
+ $html .= $myIndent . ' ' . PHP_EOL
+ . $myIndent . '' . PHP_EOL;
+ }
+ $html = rtrim($html, PHP_EOL);
+ }
+
+ return $html;
+ }
+
+ /**
+ * Renders the given $container by invoking the partial view helper
+ *
+ * The container will simply be passed on as a model to the view script
+ * as-is, and will be available in the partial script as 'container', e.g.
+ * echo 'Number of pages: ', count($this->container);
.
+ *
+ * @param AbstractContainer $container [optional] container to pass to view
+ * script. Default is to use the container
+ * registered in the helper.
+ * @param string|array $partial [optional] partial view script to use.
+ * Default is to use the partial
+ * registered in the helper. If an array
+ * is given, it is expected to contain two
+ * values; the partial view script to use,
+ * and the module where the script can be
+ * found.
+ * @return string
+ * @throws Exception\RuntimeException if no partial provided
+ * @throws Exception\InvalidArgumentException if partial is invalid array
+ */
+ public function renderPartial($container = null, $partial = null)
+ {
+ $this->parseContainer($container);
+ if (null === $container) {
+ $container = $this->getContainer();
+ }
+
+ if (null === $partial) {
+ $partial = $this->getPartial();
+ }
+
+ if (empty($partial)) {
+ throw new Exception\RuntimeException(
+ 'Unable to render menu: No partial view script provided'
+ );
+ }
+
+ $model = array(
+ 'container' => $container
+ );
+
+ /** @var \Zend\View\Helper\Partial $partialHelper */
+ $partialHelper = $this->view->plugin('partial');
+
+ if (is_array($partial)) {
+ if (count($partial) != 2) {
+ throw new Exception\InvalidArgumentException(
+ 'Unable to render menu: A view partial supplied as '
+ . 'an array must contain two values: partial view '
+ . 'script and module where script can be found'
+ );
+ }
+
+ return $partialHelper($partial[0], $model);
+ }
+
+ return $partialHelper($partial, $model);
+ }
+
+ /**
+ * Renders the inner-most sub menu for the active page in the $container
+ *
+ * This is a convenience method which is equivalent to the following call:
+ *
+ * renderMenu($container, array(
+ * 'indent' => $indent,
+ * 'ulClass' => $ulClass,
+ * 'minDepth' => null,
+ * 'maxDepth' => null,
+ * 'onlyActiveBranch' => true,
+ * 'renderParents' => false,
+ * 'liActiveClass' => $liActiveClass
+ * ));
+ *
+ *
+ * @param AbstractContainer $container [optional] container to
+ * render. Default is to render
+ * the container registered in
+ * the helper.
+ * @param string $ulClass [optional] CSS class to
+ * use for UL element. Default
+ * is to use the value from
+ * {@link getUlClass()}.
+ * @param string|int $indent [optional] indentation as
+ * a string or number of
+ * spaces. Default is to use
+ * the value retrieved from
+ * {@link getIndent()}.
+ * @param string $liActiveClass [optional] CSS class to
+ * use for UL element. Default
+ * is to use the value from
+ * {@link getUlClass()}.
+ * @return string
+ */
+ public function renderSubMenu(
+ AbstractContainer $container = null,
+ $ulClass = null,
+ $indent = null,
+ $liActiveClass = null
+ ) {
+ return $this->renderMenu($container, array(
+ 'indent' => $indent,
+ 'ulClass' => $ulClass,
+ 'minDepth' => null,
+ 'maxDepth' => null,
+ 'onlyActiveBranch' => true,
+ 'renderParents' => false,
+ 'escapeLabels' => true,
+ 'addClassToListItem' => false,
+ 'liActiveClass' => $liActiveClass
+ ));
+ }
+
+ /**
+ * Returns an HTML string containing an 'a' element for the given page if
+ * the page's href is not empty, and a 'span' element if it is empty
+ *
+ * Overrides {@link AbstractHelper::htmlify()}.
+ *
+ * @param AbstractPage $page page to generate HTML for
+ * @param bool $escapeLabel Whether or not to escape the label
+ * @param bool $addClassToListItem Whether or not to add the page class to the list item
+ * @return string
+ */
+ public function htmlify(AbstractPage $page, $escapeLabel = true, $addClassToListItem = false)
+ {
+ // get attribs for element
+ $attribs = array(
+ 'id' => $page->getId(),
+ 'title' => $this->translate($page->getTitle(), $page->getTextDomain()),
+ );
+
+ if ($addClassToListItem === false) {
+ $attribs['class'] = $page->getClass();
+ }
+
+ // does page have a href?
+ $href = $page->getHref();
+ if ($href) {
+ $element = 'a';
+ $attribs['href'] = $href;
+ $attribs['target'] = $page->getTarget();
+ } else {
+ $element = 'span';
+ }
+
+ $html = '<' . $element . $this->htmlAttribs($attribs) . '>';
+ $label = $this->translate($page->getLabel(), $page->getTextDomain());
+ if ($escapeLabel === true) {
+ /** @var \Zend\View\Helper\EscapeHtml $escaper */
+ $escaper = $this->view->plugin('escapeHtml');
+ $html .= $escaper($label);
+ } else {
+ $html .= $label;
+ }
+ $html .= '' . $element . '>';
+
+ return $html;
+ }
+
+ /**
+ * Normalizes given render options
+ *
+ * @param array $options [optional] options to normalize
+ * @return array
+ */
+ protected function normalizeOptions(array $options = array())
+ {
+ if (isset($options['indent'])) {
+ $options['indent'] = $this->getWhitespace($options['indent']);
+ } else {
+ $options['indent'] = $this->getIndent();
+ }
+
+ if (isset($options['ulClass']) && $options['ulClass'] !== null) {
+ $options['ulClass'] = (string) $options['ulClass'];
+ } else {
+ $options['ulClass'] = $this->getUlClass();
+ }
+
+ if (array_key_exists('minDepth', $options)) {
+ if (null !== $options['minDepth']) {
+ $options['minDepth'] = (int) $options['minDepth'];
+ }
+ } else {
+ $options['minDepth'] = $this->getMinDepth();
+ }
+
+ if ($options['minDepth'] < 0 || $options['minDepth'] === null) {
+ $options['minDepth'] = 0;
+ }
+
+ if (array_key_exists('maxDepth', $options)) {
+ if (null !== $options['maxDepth']) {
+ $options['maxDepth'] = (int) $options['maxDepth'];
+ }
+ } else {
+ $options['maxDepth'] = $this->getMaxDepth();
+ }
+
+ if (!isset($options['onlyActiveBranch'])) {
+ $options['onlyActiveBranch'] = $this->getOnlyActiveBranch();
+ }
+
+ if (!isset($options['escapeLabels'])) {
+ $options['escapeLabels'] = $this->escapeLabels;
+ }
+
+ if (!isset($options['renderParents'])) {
+ $options['renderParents'] = $this->getRenderParents();
+ }
+
+ if (!isset($options['addClassToListItem'])) {
+ $options['addClassToListItem'] = $this->getAddClassToListItem();
+ }
+
+ if (isset($options['liActiveClass']) && $options['liActiveClass'] !== null) {
+ $options['liActiveClass'] = (string) $options['liActiveClass'];
+ } else {
+ $options['liActiveClass'] = $this->getLiActiveClass();
+ }
+
+ return $options;
+ }
+
+ /**
+ * Sets a flag indicating whether labels should be escaped
+ *
+ * @param bool $flag [optional] escape labels
+ * @return self
+ */
+ public function escapeLabels($flag = true)
+ {
+ $this->escapeLabels = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Enables/disables page class applied to element
+ *
+ * @param bool $flag [optional] page class applied to element
+ * Default is true.
+ * @return self fluent interface, returns self
+ */
+ public function setAddClassToListItem($flag = true)
+ {
+ $this->addClassToListItem = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Returns flag indicating whether page class should be applied to element
+ *
+ * By default, this value is false.
+ *
+ * @return bool whether parents should be rendered
+ */
+ public function getAddClassToListItem()
+ {
+ return $this->addClassToListItem;
+ }
+
+ /**
+ * Sets a flag indicating whether only active branch should be rendered
+ *
+ * @param bool $flag [optional] render only active branch.
+ * @return self
+ */
+ public function setOnlyActiveBranch($flag = true)
+ {
+ $this->onlyActiveBranch = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Returns a flag indicating whether only active branch should be rendered
+ *
+ * By default, this value is false, meaning the entire menu will be
+ * be rendered.
+ *
+ * @return bool
+ */
+ public function getOnlyActiveBranch()
+ {
+ return $this->onlyActiveBranch;
+ }
+
+ /**
+ * Sets which partial view script to use for rendering menu
+ *
+ * @param string|array $partial partial view script or null. If an array is
+ * given, it is expected to contain two
+ * values; the partial view script to use,
+ * and the module where the script can be
+ * found.
+ * @return self
+ */
+ public function setPartial($partial)
+ {
+ if (null === $partial || is_string($partial) || is_array($partial)) {
+ $this->partial = $partial;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns partial view script to use for rendering menu
+ *
+ * @return string|array|null
+ */
+ public function getPartial()
+ {
+ return $this->partial;
+ }
+
+ /**
+ * Enables/disables rendering of parents when only rendering active branch
+ *
+ * See {@link setOnlyActiveBranch()} for more information.
+ *
+ * @param bool $flag [optional] render parents when rendering active branch.
+ * @return self
+ */
+ public function setRenderParents($flag = true)
+ {
+ $this->renderParents = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Returns flag indicating whether parents should be rendered when rendering
+ * only the active branch
+ *
+ * By default, this value is true.
+ *
+ * @return bool
+ */
+ public function getRenderParents()
+ {
+ return $this->renderParents;
+ }
+
+ /**
+ * Sets CSS class to use for the first 'ul' element when rendering
+ *
+ * @param string $ulClass CSS class to set
+ * @return self
+ */
+ public function setUlClass($ulClass)
+ {
+ if (is_string($ulClass)) {
+ $this->ulClass = $ulClass;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns CSS class to use for the first 'ul' element when rendering
+ *
+ * @return string
+ */
+ public function getUlClass()
+ {
+ return $this->ulClass;
+ }
+
+ /**
+ * Sets CSS class to use for the active 'li' element when rendering
+ *
+ * @param string $liActiveClass CSS class to set
+ * @return self
+ */
+ public function setLiActiveClass($liActiveClass)
+ {
+ if (is_string($liActiveClass)) {
+ $this->liActiveClass = $liActiveClass;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns CSS class to use for the active 'li' element when rendering
+ *
+ * @return string
+ */
+ public function getLiActiveClass()
+ {
+ return $this->liActiveClass;
+ }
+}
diff --git a/library/Zend/View/Helper/Navigation/PluginManager.php b/library/Zend/View/Helper/Navigation/PluginManager.php
new file mode 100755
index 0000000000..8faf86eb3d
--- /dev/null
+++ b/library/Zend/View/Helper/Navigation/PluginManager.php
@@ -0,0 +1,58 @@
+ 'Zend\View\Helper\Navigation\Breadcrumbs',
+ 'links' => 'Zend\View\Helper\Navigation\Links',
+ 'menu' => 'Zend\View\Helper\Navigation\Menu',
+ 'sitemap' => 'Zend\View\Helper\Navigation\Sitemap',
+ );
+
+ /**
+ * Validate the plugin
+ *
+ * Checks that the helper loaded is an instance of AbstractHelper.
+ *
+ * @param mixed $plugin
+ * @return void
+ * @throws Exception\InvalidArgumentException if invalid
+ */
+ public function validatePlugin($plugin)
+ {
+ if ($plugin instanceof AbstractHelper) {
+ // we're okay
+ return;
+ }
+
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Plugin of type %s is invalid; must implement %s\AbstractHelper',
+ (is_object($plugin) ? get_class($plugin) : gettype($plugin)),
+ __NAMESPACE__
+ ));
+ }
+}
diff --git a/library/Zend/View/Helper/Navigation/Sitemap.php b/library/Zend/View/Helper/Navigation/Sitemap.php
new file mode 100755
index 0000000000..2ba2b4a4bd
--- /dev/null
+++ b/library/Zend/View/Helper/Navigation/Sitemap.php
@@ -0,0 +1,441 @@
+ tag
+ *
+ * @var string
+ */
+ const SITEMAP_NS = 'http://www.sitemaps.org/schemas/sitemap/0.9';
+
+ /**
+ * Schema URL
+ *
+ * @var string
+ */
+ const SITEMAP_XSD = 'http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd';
+
+ /**
+ * Whether XML output should be formatted
+ *
+ * @var bool
+ */
+ protected $formatOutput = false;
+
+ /**
+ * Server url
+ *
+ * @var string
+ */
+ protected $serverUrl;
+
+ /**
+ * List of urls in the sitemap
+ *
+ * @var array
+ */
+ protected $urls = array();
+
+ /**
+ * Whether sitemap should be validated using Zend\Validate\Sitemap\*
+ *
+ * @var bool
+ */
+ protected $useSitemapValidators = true;
+
+ /**
+ * Whether sitemap should be schema validated when generated
+ *
+ * @var bool
+ */
+ protected $useSchemaValidation = false;
+
+ /**
+ * Whether the XML declaration should be included in XML output
+ *
+ * @var bool
+ */
+ protected $useXmlDeclaration = true;
+
+ /**
+ * Helper entry point
+ *
+ * @param string|AbstractContainer $container container to operate on
+ * @return Sitemap
+ */
+ public function __invoke($container = null)
+ {
+ if (null !== $container) {
+ $this->setContainer($container);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Renders helper
+ *
+ * Implements {@link HelperInterface::render()}.
+ *
+ * @param AbstractContainer $container [optional] container to render. Default is
+ * to render the container registered in the helper.
+ * @return string
+ */
+ public function render($container = null)
+ {
+ $dom = $this->getDomSitemap($container);
+ $xml = $this->getUseXmlDeclaration() ?
+ $dom->saveXML() :
+ $dom->saveXML($dom->documentElement);
+
+ return rtrim($xml, PHP_EOL);
+ }
+
+ /**
+ * Returns a DOMDocument containing the Sitemap XML for the given container
+ *
+ * @param AbstractContainer $container [optional] container to get
+ * breadcrumbs from, defaults
+ * to what is registered in the
+ * helper
+ * @return DOMDocument DOM representation of the
+ * container
+ * @throws Exception\RuntimeException if schema validation is on
+ * and the sitemap is invalid
+ * according to the sitemap
+ * schema, or if sitemap
+ * validators are used and the
+ * loc element fails validation
+ */
+ public function getDomSitemap(AbstractContainer $container = null)
+ {
+ // Reset the urls
+ $this->urls = array();
+
+ if (null === $container) {
+ $container = $this->getContainer();
+ }
+
+ // check if we should validate using our own validators
+ if ($this->getUseSitemapValidators()) {
+ // create validators
+ $locValidator = new \Zend\Validator\Sitemap\Loc();
+ $lastmodValidator = new \Zend\Validator\Sitemap\Lastmod();
+ $changefreqValidator = new \Zend\Validator\Sitemap\Changefreq();
+ $priorityValidator = new \Zend\Validator\Sitemap\Priority();
+ }
+
+ // create document
+ $dom = new DOMDocument('1.0', 'UTF-8');
+ $dom->formatOutput = $this->getFormatOutput();
+
+ // ...and urlset (root) element
+ $urlSet = $dom->createElementNS(self::SITEMAP_NS, 'urlset');
+ $dom->appendChild($urlSet);
+
+ // create iterator
+ $iterator = new RecursiveIteratorIterator($container,
+ RecursiveIteratorIterator::SELF_FIRST);
+
+ $maxDepth = $this->getMaxDepth();
+ if (is_int($maxDepth)) {
+ $iterator->setMaxDepth($maxDepth);
+ }
+ $minDepth = $this->getMinDepth();
+ if (!is_int($minDepth) || $minDepth < 0) {
+ $minDepth = 0;
+ }
+
+ // iterate container
+ foreach ($iterator as $page) {
+ if ($iterator->getDepth() < $minDepth || !$this->accept($page)) {
+ // page should not be included
+ continue;
+ }
+
+ // get absolute url from page
+ if (!$url = $this->url($page)) {
+ // skip page if it has no url (rare case)
+ // or already is in the sitemap
+ continue;
+ }
+
+ // create url node for this page
+ $urlNode = $dom->createElementNS(self::SITEMAP_NS, 'url');
+ $urlSet->appendChild($urlNode);
+
+ if ($this->getUseSitemapValidators()
+ && !$locValidator->isValid($url)
+ ) {
+ throw new Exception\RuntimeException(sprintf(
+ 'Encountered an invalid URL for Sitemap XML: "%s"',
+ $url
+ ));
+ }
+
+ // put url in 'loc' element
+ $urlNode->appendChild($dom->createElementNS(self::SITEMAP_NS,
+ 'loc', $url));
+
+ // add 'lastmod' element if a valid lastmod is set in page
+ if (isset($page->lastmod)) {
+ $lastmod = strtotime((string) $page->lastmod);
+
+ // prevent 1970-01-01...
+ if ($lastmod !== false) {
+ $lastmod = date('c', $lastmod);
+ }
+
+ if (!$this->getUseSitemapValidators() ||
+ $lastmodValidator->isValid($lastmod)) {
+ $urlNode->appendChild(
+ $dom->createElementNS(self::SITEMAP_NS, 'lastmod',
+ $lastmod)
+ );
+ }
+ }
+
+ // add 'changefreq' element if a valid changefreq is set in page
+ if (isset($page->changefreq)) {
+ $changefreq = $page->changefreq;
+ if (!$this->getUseSitemapValidators() ||
+ $changefreqValidator->isValid($changefreq)) {
+ $urlNode->appendChild(
+ $dom->createElementNS(self::SITEMAP_NS, 'changefreq',
+ $changefreq)
+ );
+ }
+ }
+
+ // add 'priority' element if a valid priority is set in page
+ if (isset($page->priority)) {
+ $priority = $page->priority;
+ if (!$this->getUseSitemapValidators() ||
+ $priorityValidator->isValid($priority)) {
+ $urlNode->appendChild(
+ $dom->createElementNS(self::SITEMAP_NS, 'priority', $priority)
+ );
+ }
+ }
+ }
+
+ // validate using schema if specified
+ if ($this->getUseSchemaValidation()) {
+ ErrorHandler::start();
+ $test = $dom->schemaValidate(self::SITEMAP_XSD);
+ $error = ErrorHandler::stop();
+ if (!$test) {
+ throw new Exception\RuntimeException(sprintf(
+ 'Sitemap is invalid according to XML Schema at "%s"',
+ self::SITEMAP_XSD
+ ), 0, $error);
+ }
+ }
+
+ return $dom;
+ }
+
+ /**
+ * Returns an escaped absolute URL for the given page
+ *
+ * @param AbstractPage $page
+ * @return string
+ */
+ public function url(AbstractPage $page)
+ {
+ $href = $page->getHref();
+
+ if (!isset($href{0})) {
+ // no href
+ return '';
+ } elseif ($href{0} == '/') {
+ // href is relative to root; use serverUrl helper
+ $url = $this->getServerUrl() . $href;
+ } elseif (preg_match('/^[a-z]+:/im', (string) $href)) {
+ // scheme is given in href; assume absolute URL already
+ $url = (string) $href;
+ } else {
+ // href is relative to current document; use url helpers
+ $basePathHelper = $this->getView()->plugin('basepath');
+ $curDoc = $basePathHelper();
+ $curDoc = ('/' == $curDoc) ? '' : trim($curDoc, '/');
+ $url = rtrim($this->getServerUrl(), '/') . '/'
+ . $curDoc
+ . (empty($curDoc) ? '' : '/') . $href;
+ }
+
+ if (! in_array($url, $this->urls)) {
+ $this->urls[] = $url;
+ return $this->xmlEscape($url);
+ }
+
+ return null;
+ }
+
+ /**
+ * Escapes string for XML usage
+ *
+ * @param string $string
+ * @return string
+ */
+ protected function xmlEscape($string)
+ {
+ $escaper = $this->view->plugin('escapeHtml');
+ return $escaper($string);
+ }
+
+ /**
+ * Sets whether XML output should be formatted
+ *
+ * @param bool $formatOutput
+ * @return Sitemap
+ */
+ public function setFormatOutput($formatOutput = true)
+ {
+ $this->formatOutput = (bool) $formatOutput;
+ return $this;
+ }
+
+ /**
+ * Returns whether XML output should be formatted
+ *
+ * @return bool
+ */
+ public function getFormatOutput()
+ {
+ return $this->formatOutput;
+ }
+
+ /**
+ * Sets server url (scheme and host-related stuff without request URI)
+ *
+ * E.g. http://www.example.com
+ *
+ * @param string $serverUrl
+ * @return Sitemap
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setServerUrl($serverUrl)
+ {
+ $uri = Uri\UriFactory::factory($serverUrl);
+ $uri->setFragment('');
+ $uri->setPath('');
+ $uri->setQuery('');
+
+ if ($uri->isValid()) {
+ $this->serverUrl = $uri->toString();
+ } else {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Invalid server URL: "%s"',
+ $serverUrl
+ ));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns server URL
+ *
+ * @return string
+ */
+ public function getServerUrl()
+ {
+ if (!isset($this->serverUrl)) {
+ $serverUrlHelper = $this->getView()->plugin('serverUrl');
+ $this->serverUrl = $serverUrlHelper();
+ }
+
+ return $this->serverUrl;
+ }
+
+ /**
+ * Sets whether sitemap should be validated using Zend\Validate\Sitemap_*
+ *
+ * @param bool $useSitemapValidators
+ * @return Sitemap
+ */
+ public function setUseSitemapValidators($useSitemapValidators)
+ {
+ $this->useSitemapValidators = (bool) $useSitemapValidators;
+ return $this;
+ }
+
+ /**
+ * Returns whether sitemap should be validated using Zend\Validate\Sitemap_*
+ *
+ * @return bool
+ */
+ public function getUseSitemapValidators()
+ {
+ return $this->useSitemapValidators;
+ }
+
+ /**
+ * Sets whether sitemap should be schema validated when generated
+ *
+ * @param bool $schemaValidation
+ * @return Sitemap
+ */
+ public function setUseSchemaValidation($schemaValidation)
+ {
+ $this->useSchemaValidation = (bool) $schemaValidation;
+ return $this;
+ }
+
+ /**
+ * Returns true if sitemap should be schema validated when generated
+ *
+ * @return bool
+ */
+ public function getUseSchemaValidation()
+ {
+ return $this->useSchemaValidation;
+ }
+
+ /**
+ * Sets whether the XML declaration should be used in output
+ *
+ * @param bool $useXmlDecl
+ * @return Sitemap
+ */
+ public function setUseXmlDeclaration($useXmlDecl)
+ {
+ $this->useXmlDeclaration = (bool) $useXmlDecl;
+ return $this;
+ }
+
+ /**
+ * Returns whether the XML declaration should be used in output
+ *
+ * @return bool
+ */
+ public function getUseXmlDeclaration()
+ {
+ return $this->useXmlDeclaration;
+ }
+}
diff --git a/library/Zend/View/Helper/PaginationControl.php b/library/Zend/View/Helper/PaginationControl.php
new file mode 100755
index 0000000000..9963fdd35f
--- /dev/null
+++ b/library/Zend/View/Helper/PaginationControl.php
@@ -0,0 +1,131 @@
+paginator is set and,
+ * if so, uses that. Also, if no scrolling style or partial are specified,
+ * the defaults will be used (if set).
+ *
+ * @param Paginator\Paginator $paginator (Optional)
+ * @param string $scrollingStyle (Optional) Scrolling style
+ * @param string $partial (Optional) View partial
+ * @param array|string $params (Optional) params to pass to the partial
+ * @throws Exception\RuntimeException if no paginator or no view partial provided
+ * @throws Exception\InvalidArgumentException if partial is invalid array
+ * @return string
+ */
+ public function __invoke(Paginator\Paginator $paginator = null, $scrollingStyle = null, $partial = null, $params = null)
+ {
+ if ($paginator === null) {
+ if (isset($this->view->paginator) and $this->view->paginator !== null and $this->view->paginator instanceof Paginator\Paginator) {
+ $paginator = $this->view->paginator;
+ } else {
+ throw new Exception\RuntimeException('No paginator instance provided or incorrect type');
+ }
+ }
+
+ if ($partial === null) {
+ if (static::$defaultViewPartial === null) {
+ throw new Exception\RuntimeException('No view partial provided and no default set');
+ }
+
+ $partial = static::$defaultViewPartial;
+ }
+
+ if ($scrollingStyle === null) {
+ $scrollingStyle = static::$defaultScrollingStyle;
+ }
+
+ $pages = get_object_vars($paginator->getPages($scrollingStyle));
+
+ if ($params !== null) {
+ $pages = array_merge($pages, (array) $params);
+ }
+
+ if (is_array($partial)) {
+ if (count($partial) != 2) {
+ throw new Exception\InvalidArgumentException(
+ 'A view partial supplied as an array must contain two values: the filename and its module'
+ );
+ }
+
+ if ($partial[1] !== null) {
+ $partialHelper = $this->view->plugin('partial');
+ return $partialHelper($partial[0], $pages);
+ }
+
+ $partial = $partial[0];
+ }
+
+ $partialHelper = $this->view->plugin('partial');
+ return $partialHelper($partial, $pages);
+ }
+
+ /**
+ * Sets the default Scrolling Style
+ *
+ * @param string $style string 'all' | 'elastic' | 'sliding' | 'jumping'
+ */
+ public static function setDefaultScrollingStyle($style)
+ {
+ static::$defaultScrollingStyle = $style;
+ }
+
+ /**
+ * Gets the default scrolling style
+ *
+ * @return string
+ */
+ public static function getDefaultScrollingStyle()
+ {
+ return static::$defaultScrollingStyle;
+ }
+
+ /**
+ * Sets the default view partial.
+ *
+ * @param string|array $partial View partial
+ */
+ public static function setDefaultViewPartial($partial)
+ {
+ static::$defaultViewPartial = $partial;
+ }
+
+ /**
+ * Gets the default view partial
+ *
+ * @return string|array
+ */
+ public static function getDefaultViewPartial()
+ {
+ return static::$defaultViewPartial;
+ }
+}
diff --git a/library/Zend/View/Helper/Partial.php b/library/Zend/View/Helper/Partial.php
new file mode 100755
index 0000000000..444f161b98
--- /dev/null
+++ b/library/Zend/View/Helper/Partial.php
@@ -0,0 +1,94 @@
+getView()->render($name);
+ }
+
+ if (is_scalar($values)) {
+ $values = array();
+ } elseif ($values instanceof ModelInterface) {
+ $values = $values->getVariables();
+ } elseif (is_object($values)) {
+ if (null !== ($objectKey = $this->getObjectKey())) {
+ $values = array($objectKey => $values);
+ } elseif (method_exists($values, 'toArray')) {
+ $values = $values->toArray();
+ } else {
+ $values = get_object_vars($values);
+ }
+ }
+
+ return $this->getView()->render($name, $values);
+ }
+
+ /**
+ * Set object key
+ *
+ * @param string $key
+ * @return Partial
+ */
+ public function setObjectKey($key)
+ {
+ if (null === $key) {
+ $this->objectKey = null;
+ return $this;
+ }
+
+ $this->objectKey = (string) $key;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve object key
+ *
+ * The objectKey is the variable to which an object in the iterator will be
+ * assigned.
+ *
+ * @return null|string
+ */
+ public function getObjectKey()
+ {
+ return $this->objectKey;
+ }
+}
diff --git a/library/Zend/View/Helper/PartialLoop.php b/library/Zend/View/Helper/PartialLoop.php
new file mode 100755
index 0000000000..f66297e67b
--- /dev/null
+++ b/library/Zend/View/Helper/PartialLoop.php
@@ -0,0 +1,77 @@
+toArray();
+ } else {
+ throw new Exception\InvalidArgumentException('PartialLoop helper requires iterable data');
+ }
+ }
+
+ // reset the counter if it's called again
+ $this->partialCounter = 0;
+ $content = '';
+
+ foreach ($values as $item) {
+ $this->partialCounter++;
+ $content .= parent::__invoke($name, $item);
+ }
+
+ return $content;
+ }
+
+ /**
+ * Get the partial counter
+ *
+ * @return int
+ */
+ public function getPartialCounter()
+ {
+ return $this->partialCounter;
+ }
+}
diff --git a/library/Zend/View/Helper/Placeholder.php b/library/Zend/View/Helper/Placeholder.php
new file mode 100755
index 0000000000..297c17a554
--- /dev/null
+++ b/library/Zend/View/Helper/Placeholder.php
@@ -0,0 +1,98 @@
+getContainer($name);
+ }
+
+ /**
+ * createContainer
+ *
+ * @param string $key
+ * @param array $value
+ * @return Container\AbstractContainer
+ */
+ public function createContainer($key, array $value = array())
+ {
+ $key = (string) $key;
+
+ $this->items[$key] = new $this->containerClass($value);
+ return $this->items[$key];
+ }
+
+ /**
+ * Retrieve a placeholder container
+ *
+ * @param string $key
+ * @return Container\AbstractContainer
+ */
+ public function getContainer($key)
+ {
+ $key = (string) $key;
+ if (isset($this->items[$key])) {
+ return $this->items[$key];
+ }
+
+ $container = $this->createContainer($key);
+
+ return $container;
+ }
+
+ /**
+ * Does a particular container exist?
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function containerExists($key)
+ {
+ $key = (string) $key;
+ $return = array_key_exists($key, $this->items);
+ return $return;
+ }
+}
diff --git a/library/Zend/View/Helper/Placeholder/Container.php b/library/Zend/View/Helper/Placeholder/Container.php
new file mode 100755
index 0000000000..35220176c8
--- /dev/null
+++ b/library/Zend/View/Helper/Placeholder/Container.php
@@ -0,0 +1,17 @@
+toString();
+ }
+
+ /**
+ * Render the placeholder
+ *
+ * @param null|int|string $indent
+ * @return string
+ */
+ public function toString($indent = null)
+ {
+ $indent = ($indent !== null)
+ ? $this->getWhitespace($indent)
+ : $this->getIndent();
+
+ $items = $this->getArrayCopy();
+ $return = $indent
+ . $this->getPrefix()
+ . implode($this->getSeparator(), $items)
+ . $this->getPostfix();
+ $return = preg_replace("/(\r\n?|\n)/", '$1' . $indent, $return);
+
+ return $return;
+ }
+
+ /**
+ * Start capturing content to push into placeholder
+ *
+ * @param string $type How to capture content into placeholder; append, prepend, or set
+ * @param mixed $key Key to which to capture content
+ * @throws Exception\RuntimeException if nested captures detected
+ * @return void
+ */
+ public function captureStart($type = AbstractContainer::APPEND, $key = null)
+ {
+ if ($this->captureLock) {
+ throw new Exception\RuntimeException(
+ 'Cannot nest placeholder captures for the same placeholder'
+ );
+ }
+
+ $this->captureLock = true;
+ $this->captureType = $type;
+ if ((null !== $key) && is_scalar($key)) {
+ $this->captureKey = (string) $key;
+ }
+ ob_start();
+ }
+
+ /**
+ * End content capture
+ *
+ * @return void
+ */
+ public function captureEnd()
+ {
+ $data = ob_get_clean();
+ $key = null;
+ $this->captureLock = false;
+ if (null !== $this->captureKey) {
+ $key = $this->captureKey;
+ }
+ switch ($this->captureType) {
+ case self::SET:
+ if (null !== $key) {
+ $this[$key] = $data;
+ } else {
+ $this->exchangeArray(array($data));
+ }
+ break;
+ case self::PREPEND:
+ if (null !== $key) {
+ $array = array($key => $data);
+ $values = $this->getArrayCopy();
+ $final = $array + $values;
+ $this->exchangeArray($final);
+ } else {
+ $this->prepend($data);
+ }
+ break;
+ case self::APPEND:
+ default:
+ if (null !== $key) {
+ if (empty($this[$key])) {
+ $this[$key] = $data;
+ } else {
+ $this[$key] .= $data;
+ }
+ } else {
+ $this[$this->nextIndex()] = $data;
+ }
+ break;
+ }
+ }
+
+ /**
+ * Get keys
+ *
+ * @return array
+ */
+ public function getKeys()
+ {
+ $array = $this->getArrayCopy();
+
+ return array_keys($array);
+ }
+
+ /**
+ * Retrieve container value
+ *
+ * If single element registered, returns that element; otherwise,
+ * serializes to array.
+ *
+ * @return mixed
+ */
+ public function getValue()
+ {
+ if (1 == count($this)) {
+ $keys = $this->getKeys();
+ $key = array_shift($keys);
+ return $this[$key];
+ }
+
+ return $this->getArrayCopy();
+ }
+
+ /**
+ * Retrieve whitespace representation of $indent
+ *
+ * @param int|string $indent
+ * @return string
+ */
+ public function getWhitespace($indent)
+ {
+ if (is_int($indent)) {
+ $indent = str_repeat(' ', $indent);
+ }
+
+ return (string) $indent;
+ }
+
+ /**
+ * Set a single value
+ *
+ * @param mixed $value
+ * @return void
+ */
+ public function set($value)
+ {
+ $this->exchangeArray(array($value));
+
+ return $this;
+ }
+
+ /**
+ * Prepend a value to the top of the container
+ *
+ * @param mixed $value
+ * @return self
+ */
+ public function prepend($value)
+ {
+ $values = $this->getArrayCopy();
+ array_unshift($values, $value);
+ $this->exchangeArray($values);
+
+ return $this;
+ }
+
+ /**
+ * Append a value to the end of the container
+ *
+ * @param mixed $value
+ * @return self
+ */
+ public function append($value)
+ {
+ parent::append($value);
+ return $this;
+ }
+
+ /**
+ * Next Index as defined by the PHP manual
+ *
+ * @return int
+ */
+ public function nextIndex()
+ {
+ $keys = $this->getKeys();
+ if (0 == count($keys)) {
+ return 0;
+ }
+
+ return $nextIndex = max($keys) + 1;
+ }
+
+ /**
+ * Set the indentation string for __toString() serialization,
+ * optionally, if a number is passed, it will be the number of spaces
+ *
+ * @param string|int $indent
+ * @return self
+ */
+ public function setIndent($indent)
+ {
+ $this->indent = $this->getWhitespace($indent);
+ return $this;
+ }
+
+ /**
+ * Retrieve indentation
+ *
+ * @return string
+ */
+ public function getIndent()
+ {
+ return $this->indent;
+ }
+
+ /**
+ * Set postfix for __toString() serialization
+ *
+ * @param string $postfix
+ * @return self
+ */
+ public function setPostfix($postfix)
+ {
+ $this->postfix = (string) $postfix;
+ return $this;
+ }
+
+ /**
+ * Retrieve postfix
+ *
+ * @return string
+ */
+ public function getPostfix()
+ {
+ return $this->postfix;
+ }
+
+ /**
+ * Set prefix for __toString() serialization
+ *
+ * @param string $prefix
+ * @return self
+ */
+ public function setPrefix($prefix)
+ {
+ $this->prefix = (string) $prefix;
+ return $this;
+ }
+
+ /**
+ * Retrieve prefix
+ *
+ * @return string
+ */
+ public function getPrefix()
+ {
+ return $this->prefix;
+ }
+
+ /**
+ * Set separator for __toString() serialization
+ *
+ * Used to implode elements in container
+ *
+ * @param string $separator
+ * @return self
+ */
+ public function setSeparator($separator)
+ {
+ $this->separator = (string) $separator;
+ return $this;
+ }
+
+ /**
+ * Retrieve separator
+ *
+ * @return string
+ */
+ public function getSeparator()
+ {
+ return $this->separator;
+ }
+}
diff --git a/library/Zend/View/Helper/Placeholder/Container/AbstractStandalone.php b/library/Zend/View/Helper/Placeholder/Container/AbstractStandalone.php
new file mode 100755
index 0000000000..619ba70737
--- /dev/null
+++ b/library/Zend/View/Helper/Placeholder/Container/AbstractStandalone.php
@@ -0,0 +1,376 @@
+setContainer($this->getContainer());
+ }
+
+ /**
+ * Overload
+ *
+ * Proxy to container methods
+ *
+ * @param string $method
+ * @param array $args
+ * @throws Exception\BadMethodCallException
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ $container = $this->getContainer();
+ if (method_exists($container, $method)) {
+ $return = call_user_func_array(array($container, $method), $args);
+ if ($return === $container) {
+ // If the container is returned, we really want the current object
+ return $this;
+ }
+ return $return;
+ }
+
+ throw new Exception\BadMethodCallException('Method "' . $method . '" does not exist');
+ }
+
+ /**
+ * Overloading: set property value
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return void
+ */
+ public function __set($key, $value)
+ {
+ $container = $this->getContainer();
+ $container[$key] = $value;
+ }
+
+ /**
+ * Overloading: retrieve property
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function __get($key)
+ {
+ $container = $this->getContainer();
+ if (isset($container[$key])) {
+ return $container[$key];
+ }
+
+ return null;
+ }
+
+ /**
+ * Overloading: check if property is set
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function __isset($key)
+ {
+ $container = $this->getContainer();
+ return isset($container[$key]);
+ }
+
+ /**
+ * Overloading: unset property
+ *
+ * @param string $key
+ * @return void
+ */
+ public function __unset($key)
+ {
+ $container = $this->getContainer();
+ if (isset($container[$key])) {
+ unset($container[$key]);
+ }
+ }
+
+ /**
+ * Cast to string representation
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * String representation
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return $this->getContainer()->toString();
+ }
+
+ /**
+ * Escape a string
+ *
+ * @param string $string
+ * @return string
+ */
+ protected function escape($string)
+ {
+ if ($this->getView() instanceof RendererInterface
+ && method_exists($this->getView(), 'getEncoding')
+ ) {
+ $escaper = $this->getView()->plugin('escapeHtml');
+ return $escaper((string) $string);
+ }
+
+ return $this->getEscaper()->escapeHtml((string) $string);
+ }
+
+ /**
+ * Set whether or not auto escaping should be used
+ *
+ * @param bool $autoEscape whether or not to auto escape output
+ * @return AbstractStandalone
+ */
+ public function setAutoEscape($autoEscape = true)
+ {
+ $this->autoEscape = ($autoEscape) ? true : false;
+ return $this;
+ }
+
+ /**
+ * Return whether autoEscaping is enabled or disabled
+ *
+ * return bool
+ */
+ public function getAutoEscape()
+ {
+ return $this->autoEscape;
+ }
+
+ /**
+ * Set container on which to operate
+ *
+ * @param AbstractContainer $container
+ * @return AbstractStandalone
+ */
+ public function setContainer(AbstractContainer $container)
+ {
+ $this->container = $container;
+ return $this;
+ }
+
+ /**
+ * Retrieve placeholder container
+ *
+ * @return AbstractContainer
+ */
+ public function getContainer()
+ {
+ if (!$this->container instanceof AbstractContainer) {
+ $this->container = new $this->containerClass();
+ }
+ return $this->container;
+ }
+
+ /**
+ * Delete a container
+ *
+ * @return bool
+ */
+ public function deleteContainer()
+ {
+ if (null != $this->container) {
+ $this->container = null;
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Set the container class to use
+ *
+ * @param string $name
+ * @throws Exception\InvalidArgumentException
+ * @throws Exception\DomainException
+ * @return \Zend\View\Helper\Placeholder\Container\AbstractStandalone
+ */
+ public function setContainerClass($name)
+ {
+ if (!class_exists($name)) {
+ throw new Exception\DomainException(
+ sprintf(
+ '%s expects a valid container class name; received "%s", which did not resolve',
+ __METHOD__,
+ $name
+ )
+ );
+ }
+
+ if (!in_array('Zend\View\Helper\Placeholder\Container\AbstractContainer', class_parents($name))) {
+ throw new Exception\InvalidArgumentException('Invalid Container class specified');
+ }
+
+ $this->containerClass = $name;
+ return $this;
+ }
+
+ /**
+ * Retrieve the container class
+ *
+ * @return string
+ */
+ public function getContainerClass()
+ {
+ return $this->containerClass;
+ }
+
+ /**
+ * Set Escaper instance
+ *
+ * @param Escaper $escaper
+ * @return AbstractStandalone
+ */
+ public function setEscaper(Escaper $escaper)
+ {
+ $encoding = $escaper->getEncoding();
+ $this->escapers[$encoding] = $escaper;
+
+ return $this;
+ }
+
+ /**
+ * Get Escaper instance
+ *
+ * Lazy-loads one if none available
+ *
+ * @param string|null $enc Encoding to use
+ * @return mixed
+ */
+ public function getEscaper($enc = 'UTF-8')
+ {
+ $enc = strtolower($enc);
+ if (!isset($this->escapers[$enc])) {
+ $this->setEscaper(new Escaper($enc));
+ }
+
+ return $this->escapers[$enc];
+ }
+
+ /**
+ * Countable
+ *
+ * @return int
+ */
+ public function count()
+ {
+ $container = $this->getContainer();
+ return count($container);
+ }
+
+ /**
+ * ArrayAccess: offsetExists
+ *
+ * @param string|int $offset
+ * @return bool
+ */
+ public function offsetExists($offset)
+ {
+ return $this->getContainer()->offsetExists($offset);
+ }
+
+ /**
+ * ArrayAccess: offsetGet
+ *
+ * @param string|int $offset
+ * @return mixed
+ */
+ public function offsetGet($offset)
+ {
+ return $this->getContainer()->offsetGet($offset);
+ }
+
+ /**
+ * ArrayAccess: offsetSet
+ *
+ * @param string|int $offset
+ * @param mixed $value
+ * @return void
+ */
+ public function offsetSet($offset, $value)
+ {
+ return $this->getContainer()->offsetSet($offset, $value);
+ }
+
+ /**
+ * ArrayAccess: offsetUnset
+ *
+ * @param string|int $offset
+ * @return void
+ */
+ public function offsetUnset($offset)
+ {
+ return $this->getContainer()->offsetUnset($offset);
+ }
+
+ /**
+ * IteratorAggregate: get Iterator
+ *
+ * @return \Iterator
+ */
+ public function getIterator()
+ {
+ return $this->getContainer()->getIterator();
+ }
+}
diff --git a/library/Zend/View/Helper/Placeholder/Registry.php b/library/Zend/View/Helper/Placeholder/Registry.php
new file mode 100755
index 0000000000..f86b4e1ad6
--- /dev/null
+++ b/library/Zend/View/Helper/Placeholder/Registry.php
@@ -0,0 +1,185 @@
+items[$key] = $container;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve a placeholder container
+ *
+ * @param string $key
+ * @return Container\AbstractContainer
+ */
+ public function getContainer($key)
+ {
+ $key = (string) $key;
+ if (isset($this->items[$key])) {
+ return $this->items[$key];
+ }
+
+ $container = $this->createContainer($key);
+
+ return $container;
+ }
+
+ /**
+ * Does a particular container exist?
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function containerExists($key)
+ {
+ $key = (string) $key;
+
+ return array_key_exists($key, $this->items);
+ }
+
+ /**
+ * createContainer
+ *
+ * @param string $key
+ * @param array $value
+ * @return Container\AbstractContainer
+ */
+ public function createContainer($key, array $value = array())
+ {
+ $key = (string) $key;
+
+ $this->items[$key] = new $this->containerClass($value);
+
+ return $this->items[$key];
+ }
+
+ /**
+ * Delete a container
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function deleteContainer($key)
+ {
+ $key = (string) $key;
+ if (isset($this->items[$key])) {
+ unset($this->items[$key]);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Set the container class to use
+ *
+ * @param string $name
+ * @throws Exception\InvalidArgumentException
+ * @throws Exception\DomainException
+ * @return Registry
+ */
+ public function setContainerClass($name)
+ {
+ if (!class_exists($name)) {
+ throw new Exception\DomainException(
+ sprintf(
+ '%s expects a valid registry class name; received "%s", which did not resolve',
+ __METHOD__,
+ $name
+ )
+ );
+ }
+
+ if (!in_array('Zend\View\Helper\Placeholder\Container\AbstractContainer', class_parents($name))) {
+ throw new Exception\InvalidArgumentException('Invalid Container class specified');
+ }
+
+ $this->containerClass = $name;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve the container class
+ *
+ * @return string
+ */
+ public function getContainerClass()
+ {
+ return $this->containerClass;
+ }
+}
diff --git a/library/Zend/View/Helper/RenderChildModel.php b/library/Zend/View/Helper/RenderChildModel.php
new file mode 100755
index 0000000000..d59edfe055
--- /dev/null
+++ b/library/Zend/View/Helper/RenderChildModel.php
@@ -0,0 +1,133 @@
+render($child);
+ }
+
+ /**
+ * Render a model
+ *
+ * If a matching child model is found, it is rendered. If not, an empty
+ * string is returned.
+ *
+ * @param string $child
+ * @return string
+ */
+ public function render($child)
+ {
+ $model = $this->findChild($child);
+ if (!$model) {
+ return '';
+ }
+
+ $current = $this->current;
+ $view = $this->getView();
+ $return = $view->render($model);
+ $helper = $this->getViewModelHelper();
+ $helper->setCurrent($current);
+
+ return $return;
+ }
+
+ /**
+ * Find the named child model
+ *
+ * Iterates through the current view model, looking for a child model that
+ * has a captureTo value matching the requested $child. If found, that child
+ * model is returned; otherwise, a boolean false is returned.
+ *
+ * @param string $child
+ * @return false|Model
+ */
+ protected function findChild($child)
+ {
+ $this->current = $model = $this->getCurrent();
+ foreach ($model->getChildren() as $childModel) {
+ if ($childModel->captureTo() == $child) {
+ return $childModel;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the current view model
+ *
+ * @throws Exception\RuntimeException
+ * @return null|Model
+ */
+ protected function getCurrent()
+ {
+ $helper = $this->getViewModelHelper();
+ if (!$helper->hasCurrent()) {
+ throw new Exception\RuntimeException(sprintf(
+ '%s: no view model currently registered in renderer; cannot query for children',
+ __METHOD__
+ ));
+ }
+
+ return $helper->getCurrent();
+ }
+
+ /**
+ * Retrieve the view model helper
+ *
+ * @return ViewModel
+ */
+ protected function getViewModelHelper()
+ {
+ if ($this->viewModelHelper) {
+ return $this->viewModelHelper;
+ }
+
+ if (method_exists($this->getView(), 'plugin')) {
+ $this->viewModelHelper = $this->view->plugin('view_model');
+ }
+
+ return $this->viewModelHelper;
+ }
+}
diff --git a/library/Zend/View/Helper/RenderToPlaceholder.php b/library/Zend/View/Helper/RenderToPlaceholder.php
new file mode 100755
index 0000000000..3707995b33
--- /dev/null
+++ b/library/Zend/View/Helper/RenderToPlaceholder.php
@@ -0,0 +1,35 @@
+view->plugin('placeholder');
+ $placeholderHelper($placeholder)->captureStart();
+ echo $this->view->render($script);
+ $placeholderHelper($placeholder)->captureEnd();
+ }
+}
diff --git a/library/Zend/View/Helper/ServerUrl.php b/library/Zend/View/Helper/ServerUrl.php
new file mode 100755
index 0000000000..ae846a9baf
--- /dev/null
+++ b/library/Zend/View/Helper/ServerUrl.php
@@ -0,0 +1,330 @@
+getScheme() . '://' . $this->getHost() . $path;
+ }
+
+ /**
+ * Detect the host based on headers
+ *
+ * @return void
+ */
+ protected function detectHost()
+ {
+ if ($this->setHostFromProxy()) {
+ return;
+ }
+
+ if (isset($_SERVER['HTTP_HOST']) && !empty($_SERVER['HTTP_HOST'])) {
+ // Detect if the port is set in SERVER_PORT and included in HTTP_HOST
+ if (isset($_SERVER['SERVER_PORT'])) {
+ $portStr = ':' . $_SERVER['SERVER_PORT'];
+ if (substr($_SERVER['HTTP_HOST'], 0-strlen($portStr), strlen($portStr)) == $portStr) {
+ $this->setHost(substr($_SERVER['HTTP_HOST'], 0, 0-strlen($portStr)));
+ return;
+ }
+ }
+
+ $this->setHost($_SERVER['HTTP_HOST']);
+
+ return;
+ }
+
+ if (!isset($_SERVER['SERVER_NAME']) || !isset($_SERVER['SERVER_PORT'])) {
+ return;
+ }
+
+ $name = $_SERVER['SERVER_NAME'];
+ $this->setHost($name);
+ }
+
+ /**
+ * Detect the port
+ *
+ * @return null
+ */
+ protected function detectPort()
+ {
+ if ($this->setPortFromProxy()) {
+ return;
+ }
+
+ if (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT']) {
+ $this->setPort($_SERVER['SERVER_PORT']);
+ return;
+ }
+ }
+
+ /**
+ * Detect the scheme
+ *
+ * @return null
+ */
+ protected function detectScheme()
+ {
+ if ($this->setSchemeFromProxy()) {
+ return;
+ }
+
+ switch (true) {
+ case (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] === true)):
+ case (isset($_SERVER['HTTP_SCHEME']) && ($_SERVER['HTTP_SCHEME'] == 'https')):
+ case (443 === $this->getPort()):
+ $scheme = 'https';
+ break;
+ default:
+ $scheme = 'http';
+ break;
+ }
+
+ $this->setScheme($scheme);
+ }
+
+ /**
+ * Detect if a proxy is in use, and, if so, set the host based on it
+ *
+ * @return bool
+ */
+ protected function setHostFromProxy()
+ {
+ if (!$this->useProxy) {
+ return false;
+ }
+
+ if (!isset($_SERVER['HTTP_X_FORWARDED_HOST']) || empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
+ return false;
+ }
+
+ $host = $_SERVER['HTTP_X_FORWARDED_HOST'];
+ if (strpos($host, ',') !== false) {
+ $hosts = explode(',', $host);
+ $host = trim(array_pop($hosts));
+ }
+ if (empty($host)) {
+ return false;
+ }
+ $this->setHost($host);
+
+ return true;
+ }
+
+ /**
+ * Set port based on detected proxy headers
+ *
+ * @return bool
+ */
+ protected function setPortFromProxy()
+ {
+ if (!$this->useProxy) {
+ return false;
+ }
+
+ if (!isset($_SERVER['HTTP_X_FORWARDED_PORT']) || empty($_SERVER['HTTP_X_FORWARDED_PORT'])) {
+ return false;
+ }
+
+ $port = $_SERVER['HTTP_X_FORWARDED_PORT'];
+ $this->setPort($port);
+
+ return true;
+ }
+
+ /**
+ * Set the current scheme based on detected proxy headers
+ *
+ * @return bool
+ */
+ protected function setSchemeFromProxy()
+ {
+ if (!$this->useProxy) {
+ return false;
+ }
+
+ if (isset($_SERVER['SSL_HTTPS'])) {
+ $sslHttps = strtolower($_SERVER['SSL_HTTPS']);
+ if (in_array($sslHttps, array('on', 1))) {
+ $this->setScheme('https');
+ return true;
+ }
+ }
+
+ if (!isset($_SERVER['HTTP_X_FORWARDED_PROTO']) || empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
+ return false;
+ }
+
+ $scheme = trim(strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']));
+ if (empty($scheme)) {
+ return false;
+ }
+
+ $this->setScheme($scheme);
+
+ return true;
+ }
+
+ /**
+ * Sets host
+ *
+ * @param string $host
+ * @return ServerUrl
+ */
+ public function setHost($host)
+ {
+ $port = $this->getPort();
+ $scheme = $this->getScheme();
+
+ if (($scheme == 'http' && (null === $port || $port == 80))
+ || ($scheme == 'https' && (null === $port || $port == 443))
+ ) {
+ $this->host = $host;
+ return $this;
+ }
+
+ $this->host = $host . ':' . $port;
+
+ return $this;
+ }
+
+ /**
+ * Returns host
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ if (null === $this->host) {
+ $this->detectHost();
+ }
+
+ return $this->host;
+ }
+
+ /**
+ * Set server port
+ *
+ * @param int $port
+ * @return ServerUrl
+ */
+ public function setPort($port)
+ {
+ $this->port = (int) $port;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve the server port
+ *
+ * @return int|null
+ */
+ public function getPort()
+ {
+ if (null === $this->port) {
+ $this->detectPort();
+ }
+
+ return $this->port;
+ }
+
+ /**
+ * Sets scheme (typically http or https)
+ *
+ * @param string $scheme
+ * @return ServerUrl
+ */
+ public function setScheme($scheme)
+ {
+ $this->scheme = $scheme;
+
+ return $this;
+ }
+
+ /**
+ * Returns scheme (typically http or https)
+ *
+ * @return string
+ */
+ public function getScheme()
+ {
+ if (null === $this->scheme) {
+ $this->detectScheme();
+ }
+
+ return $this->scheme;
+ }
+
+ /**
+ * Set flag indicating whether or not to query proxy servers
+ *
+ * @param bool $useProxy
+ * @return ServerUrl
+ */
+ public function setUseProxy($useProxy = false)
+ {
+ $this->useProxy = (bool) $useProxy;
+
+ return $this;
+ }
+}
diff --git a/library/Zend/View/Helper/Service/FlashMessengerFactory.php b/library/Zend/View/Helper/Service/FlashMessengerFactory.php
new file mode 100755
index 0000000000..fbaf53e8bc
--- /dev/null
+++ b/library/Zend/View/Helper/Service/FlashMessengerFactory.php
@@ -0,0 +1,47 @@
+getServiceLocator();
+ $helper = new FlashMessenger();
+ $controllerPluginManager = $serviceLocator->get('ControllerPluginManager');
+ $flashMessenger = $controllerPluginManager->get('flashmessenger');
+ $helper->setPluginFlashMessenger($flashMessenger);
+ $config = $serviceLocator->get('Config');
+ if (isset($config['view_helper_config']['flashmessenger'])) {
+ $configHelper = $config['view_helper_config']['flashmessenger'];
+ if (isset($configHelper['message_open_format'])) {
+ $helper->setMessageOpenFormat($configHelper['message_open_format']);
+ }
+ if (isset($configHelper['message_separator_string'])) {
+ $helper->setMessageSeparatorString($configHelper['message_separator_string']);
+ }
+ if (isset($configHelper['message_close_string'])) {
+ $helper->setMessageCloseString($configHelper['message_close_string']);
+ }
+ }
+
+ return $helper;
+ }
+}
diff --git a/library/Zend/View/Helper/Service/IdentityFactory.php b/library/Zend/View/Helper/Service/IdentityFactory.php
new file mode 100755
index 0000000000..a065a1d9df
--- /dev/null
+++ b/library/Zend/View/Helper/Service/IdentityFactory.php
@@ -0,0 +1,32 @@
+getServiceLocator();
+ $helper = new Identity();
+ if ($services->has('Zend\Authentication\AuthenticationService')) {
+ $helper->setAuthenticationService($services->get('Zend\Authentication\AuthenticationService'));
+ }
+ return $helper;
+ }
+}
diff --git a/library/Zend/View/Helper/Url.php b/library/Zend/View/Helper/Url.php
new file mode 100755
index 0000000000..9b2af7e97c
--- /dev/null
+++ b/library/Zend/View/Helper/Url.php
@@ -0,0 +1,126 @@
+router) {
+ throw new Exception\RuntimeException('No RouteStackInterface instance provided');
+ }
+
+ if (3 == func_num_args() && is_bool($options)) {
+ $reuseMatchedParams = $options;
+ $options = array();
+ }
+
+ if ($name === null) {
+ if ($this->routeMatch === null) {
+ throw new Exception\RuntimeException('No RouteMatch instance provided');
+ }
+
+ $name = $this->routeMatch->getMatchedRouteName();
+
+ if ($name === null) {
+ throw new Exception\RuntimeException('RouteMatch does not contain a matched route name');
+ }
+ }
+
+ if (!is_array($params)) {
+ if (!$params instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(
+ 'Params is expected to be an array or a Traversable object'
+ );
+ }
+ $params = iterator_to_array($params);
+ }
+
+ if ($reuseMatchedParams && $this->routeMatch !== null) {
+ $routeMatchParams = $this->routeMatch->getParams();
+
+ if (isset($routeMatchParams[ModuleRouteListener::ORIGINAL_CONTROLLER])) {
+ $routeMatchParams['controller'] = $routeMatchParams[ModuleRouteListener::ORIGINAL_CONTROLLER];
+ unset($routeMatchParams[ModuleRouteListener::ORIGINAL_CONTROLLER]);
+ }
+
+ if (isset($routeMatchParams[ModuleRouteListener::MODULE_NAMESPACE])) {
+ unset($routeMatchParams[ModuleRouteListener::MODULE_NAMESPACE]);
+ }
+
+ $params = array_merge($routeMatchParams, $params);
+ }
+
+ $options['name'] = $name;
+
+ return $this->router->assemble($params, $options);
+ }
+
+ /**
+ * Set the router to use for assembling.
+ *
+ * @param RouteStackInterface $router
+ * @return Url
+ */
+ public function setRouter(RouteStackInterface $router)
+ {
+ $this->router = $router;
+ return $this;
+ }
+
+ /**
+ * Set route match returned by the router.
+ *
+ * @param RouteMatch $routeMatch
+ * @return Url
+ */
+ public function setRouteMatch(RouteMatch $routeMatch)
+ {
+ $this->routeMatch = $routeMatch;
+ return $this;
+ }
+}
diff --git a/library/Zend/View/Helper/ViewModel.php b/library/Zend/View/Helper/ViewModel.php
new file mode 100755
index 0000000000..49b1306496
--- /dev/null
+++ b/library/Zend/View/Helper/ViewModel.php
@@ -0,0 +1,92 @@
+current = $model;
+ return $this;
+ }
+
+ /**
+ * Get the current view model
+ *
+ * @return null|Model
+ */
+ public function getCurrent()
+ {
+ return $this->current;
+ }
+
+ /**
+ * Is a current view model composed?
+ *
+ * @return bool
+ */
+ public function hasCurrent()
+ {
+ return ($this->current instanceof Model);
+ }
+
+ /**
+ * Set the root view model
+ *
+ * @param Model $model
+ * @return ViewModel
+ */
+ public function setRoot(Model $model)
+ {
+ $this->root = $model;
+ return $this;
+ }
+
+ /**
+ * Get the root view model
+ *
+ * @return null|Model
+ */
+ public function getRoot()
+ {
+ return $this->root;
+ }
+
+ /**
+ * Is a root view model composed?
+ *
+ * @return bool
+ */
+ public function hasRoot()
+ {
+ return ($this->root instanceof Model);
+ }
+}
diff --git a/library/Zend/View/HelperPluginManager.php b/library/Zend/View/HelperPluginManager.php
new file mode 100755
index 0000000000..d2ddc69047
--- /dev/null
+++ b/library/Zend/View/HelperPluginManager.php
@@ -0,0 +1,194 @@
+ 'Zend\View\Helper\Service\FlashMessengerFactory',
+ 'identity' => 'Zend\View\Helper\Service\IdentityFactory',
+ );
+
+ /**
+ * Default set of helpers
+ *
+ * @var array
+ */
+ protected $invokableClasses = array(
+ // basepath, doctype, and url are set up as factories in the ViewHelperManagerFactory.
+ // basepath and url are not very useful without their factories, however the doctype
+ // helper works fine as an invokable. The factory for doctype simply checks for the
+ // config value from the merged config.
+ 'basepath' => 'Zend\View\Helper\BasePath',
+ 'cycle' => 'Zend\View\Helper\Cycle',
+ 'declarevars' => 'Zend\View\Helper\DeclareVars',
+ 'doctype' => 'Zend\View\Helper\Doctype', // overridden by a factory in ViewHelperManagerFactory
+ 'escapehtml' => 'Zend\View\Helper\EscapeHtml',
+ 'escapehtmlattr' => 'Zend\View\Helper\EscapeHtmlAttr',
+ 'escapejs' => 'Zend\View\Helper\EscapeJs',
+ 'escapecss' => 'Zend\View\Helper\EscapeCss',
+ 'escapeurl' => 'Zend\View\Helper\EscapeUrl',
+ 'gravatar' => 'Zend\View\Helper\Gravatar',
+ 'headlink' => 'Zend\View\Helper\HeadLink',
+ 'headmeta' => 'Zend\View\Helper\HeadMeta',
+ 'headscript' => 'Zend\View\Helper\HeadScript',
+ 'headstyle' => 'Zend\View\Helper\HeadStyle',
+ 'headtitle' => 'Zend\View\Helper\HeadTitle',
+ 'htmlflash' => 'Zend\View\Helper\HtmlFlash',
+ 'htmllist' => 'Zend\View\Helper\HtmlList',
+ 'htmlobject' => 'Zend\View\Helper\HtmlObject',
+ 'htmlpage' => 'Zend\View\Helper\HtmlPage',
+ 'htmlquicktime' => 'Zend\View\Helper\HtmlQuicktime',
+ 'inlinescript' => 'Zend\View\Helper\InlineScript',
+ 'json' => 'Zend\View\Helper\Json',
+ 'layout' => 'Zend\View\Helper\Layout',
+ 'paginationcontrol' => 'Zend\View\Helper\PaginationControl',
+ 'partialloop' => 'Zend\View\Helper\PartialLoop',
+ 'partial' => 'Zend\View\Helper\Partial',
+ 'placeholder' => 'Zend\View\Helper\Placeholder',
+ 'renderchildmodel' => 'Zend\View\Helper\RenderChildModel',
+ 'rendertoplaceholder' => 'Zend\View\Helper\RenderToPlaceholder',
+ 'serverurl' => 'Zend\View\Helper\ServerUrl',
+ 'url' => 'Zend\View\Helper\Url',
+ 'viewmodel' => 'Zend\View\Helper\ViewModel',
+ );
+
+ /**
+ * @var Renderer\RendererInterface
+ */
+ protected $renderer;
+
+ /**
+ * Constructor
+ *
+ * After invoking parent constructor, add an initializer to inject the
+ * attached renderer and translator, if any, to the currently requested helper.
+ *
+ * @param null|ConfigInterface $configuration
+ */
+ public function __construct(ConfigInterface $configuration = null)
+ {
+ parent::__construct($configuration);
+
+ $this->addInitializer(array($this, 'injectRenderer'))
+ ->addInitializer(array($this, 'injectTranslator'));
+ }
+
+ /**
+ * Set renderer
+ *
+ * @param Renderer\RendererInterface $renderer
+ * @return HelperPluginManager
+ */
+ public function setRenderer(Renderer\RendererInterface $renderer)
+ {
+ $this->renderer = $renderer;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve renderer instance
+ *
+ * @return null|Renderer\RendererInterface
+ */
+ public function getRenderer()
+ {
+ return $this->renderer;
+ }
+
+ /**
+ * Inject a helper instance with the registered renderer
+ *
+ * @param Helper\HelperInterface $helper
+ * @return void
+ */
+ public function injectRenderer($helper)
+ {
+ $renderer = $this->getRenderer();
+ if (null === $renderer) {
+ return;
+ }
+ $helper->setView($renderer);
+ }
+
+ /**
+ * Inject a helper instance with the registered translator
+ *
+ * @param Helper\HelperInterface $helper
+ * @return void
+ */
+ public function injectTranslator($helper)
+ {
+ if (!$helper instanceof TranslatorAwareInterface) {
+ return;
+ }
+
+ $locator = $this->getServiceLocator();
+
+ if (!$locator) {
+ return;
+ }
+
+ if ($locator->has('MvcTranslator')) {
+ $helper->setTranslator($locator->get('MvcTranslator'));
+ return;
+ }
+
+ if ($locator->has('Zend\I18n\Translator\TranslatorInterface')) {
+ $helper->setTranslator($locator->get('Zend\I18n\Translator\TranslatorInterface'));
+ return;
+ }
+
+ if ($locator->has('Translator')) {
+ $helper->setTranslator($locator->get('Translator'));
+ return;
+ }
+ }
+
+ /**
+ * Validate the plugin
+ *
+ * Checks that the helper loaded is an instance of Helper\HelperInterface.
+ *
+ * @param mixed $plugin
+ * @return void
+ * @throws Exception\InvalidHelperException if invalid
+ */
+ public function validatePlugin($plugin)
+ {
+ if ($plugin instanceof Helper\HelperInterface) {
+ // we're okay
+ return;
+ }
+
+ throw new Exception\InvalidHelperException(sprintf(
+ 'Plugin of type %s is invalid; must implement %s\Helper\HelperInterface',
+ (is_object($plugin) ? get_class($plugin) : gettype($plugin)),
+ __NAMESPACE__
+ ));
+ }
+}
diff --git a/library/Zend/View/Model/ClearableModelInterface.php b/library/Zend/View/Model/ClearableModelInterface.php
new file mode 100755
index 0000000000..2edfe719e9
--- /dev/null
+++ b/library/Zend/View/Model/ClearableModelInterface.php
@@ -0,0 +1,23 @@
+options['errorLevel'] = $errorLevel;
+ }
+
+ /**
+ * @return int
+ */
+ public function getErrorLevel()
+ {
+ if (array_key_exists('errorLevel', $this->options)) {
+ return $this->options['errorLevel'];
+ }
+ }
+
+ /**
+ * Set result text.
+ *
+ * @param string $text
+ * @return \Zend\View\Model\ConsoleModel
+ */
+ public function setResult($text)
+ {
+ $this->setVariable(self::RESULT, $text);
+ return $this;
+ }
+
+ /**
+ * Get result text.
+ *
+ * @return mixed
+ */
+ public function getResult()
+ {
+ return $this->getVariable(self::RESULT);
+ }
+}
diff --git a/library/Zend/View/Model/FeedModel.php b/library/Zend/View/Model/FeedModel.php
new file mode 100755
index 0000000000..fc8351cd04
--- /dev/null
+++ b/library/Zend/View/Model/FeedModel.php
@@ -0,0 +1,89 @@
+feed instanceof Feed) {
+ return $this->feed;
+ }
+
+ if (!$this->type) {
+ $options = $this->getOptions();
+ if (isset($options['feed_type'])) {
+ $this->type = $options['feed_type'];
+ }
+ }
+
+ $variables = $this->getVariables();
+ $feed = FeedFactory::factory($variables);
+ $this->setFeed($feed);
+
+ return $this->feed;
+ }
+
+ /**
+ * Set the feed object
+ *
+ * @param Feed $feed
+ * @return FeedModel
+ */
+ public function setFeed(Feed $feed)
+ {
+ $this->feed = $feed;
+ return $this;
+ }
+
+ /**
+ * Get the feed type
+ *
+ * @return false|string
+ */
+ public function getFeedType()
+ {
+ if ($this->type) {
+ return $this->type;
+ }
+
+ $options = $this->getOptions();
+ if (isset($options['feed_type'])) {
+ $this->type = $options['feed_type'];
+ }
+ return $this->type;
+ }
+}
diff --git a/library/Zend/View/Model/JsonModel.php b/library/Zend/View/Model/JsonModel.php
new file mode 100755
index 0000000000..191d3f5ec8
--- /dev/null
+++ b/library/Zend/View/Model/JsonModel.php
@@ -0,0 +1,69 @@
+jsonpCallback = $callback;
+ return $this;
+ }
+
+ /**
+ * Serialize to JSON
+ *
+ * @return string
+ */
+ public function serialize()
+ {
+ $variables = $this->getVariables();
+ if ($variables instanceof Traversable) {
+ $variables = ArrayUtils::iteratorToArray($variables);
+ }
+
+ if (null !== $this->jsonpCallback) {
+ return $this->jsonpCallback.'('.Json::encode($variables).');';
+ }
+ return Json::encode($variables);
+ }
+}
diff --git a/library/Zend/View/Model/ModelInterface.php b/library/Zend/View/Model/ModelInterface.php
new file mode 100755
index 0000000000..810540d703
--- /dev/null
+++ b/library/Zend/View/Model/ModelInterface.php
@@ -0,0 +1,167 @@
+setVariables($variables, true);
+
+ if (null !== $options) {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * Property overloading: set variable value
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return void
+ */
+ public function __set($name, $value)
+ {
+ $this->setVariable($name, $value);
+ }
+
+ /**
+ * Property overloading: get variable value
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ if (!$this->__isset($name)) {
+ return null;
+ }
+
+ $variables = $this->getVariables();
+ return $variables[$name];
+ }
+
+ /**
+ * Property overloading: do we have the requested variable value?
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ $variables = $this->getVariables();
+ return isset($variables[$name]);
+ }
+
+ /**
+ * Property overloading: unset the requested variable
+ *
+ * @param string $name
+ * @return void
+ */
+ public function __unset($name)
+ {
+ if (!$this->__isset($name)) {
+ return null;
+ }
+
+ unset($this->variables[$name]);
+ }
+
+ /**
+ * Set a single option
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return ViewModel
+ */
+ public function setOption($name, $value)
+ {
+ $this->options[(string) $name] = $value;
+ return $this;
+ }
+
+ /**
+ * Get a single option
+ *
+ * @param string $name The option to get.
+ * @param mixed|null $default (optional) A default value if the option is not yet set.
+ * @return mixed
+ */
+ public function getOption($name, $default = null)
+ {
+ $name = (string) $name;
+ return array_key_exists($name, $this->options) ? $this->options[$name] : $default;
+ }
+
+ /**
+ * Set renderer options/hints en masse
+ *
+ * @param array|Traversable $options
+ * @throws \Zend\View\Exception\InvalidArgumentException
+ * @return ViewModel
+ */
+ public function setOptions($options)
+ {
+ // Assumption is that lowest common denominator for renderer configuration
+ // is an array
+ if ($options instanceof Traversable) {
+ $options = ArrayUtils::iteratorToArray($options);
+ }
+
+ if (!is_array($options)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s: expects an array, or Traversable argument; received "%s"',
+ __METHOD__,
+ (is_object($options) ? get_class($options) : gettype($options))
+ ));
+ }
+
+ $this->options = $options;
+ return $this;
+ }
+
+ /**
+ * Get renderer options/hints
+ *
+ * @return array
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * Clear any existing renderer options/hints
+ *
+ * @return ViewModel
+ */
+ public function clearOptions()
+ {
+ $this->options = array();
+ return $this;
+ }
+
+ /**
+ * Get a single view variable
+ *
+ * @param string $name
+ * @param mixed|null $default (optional) default value if the variable is not present.
+ * @return mixed
+ */
+ public function getVariable($name, $default = null)
+ {
+ $name = (string) $name;
+ if (array_key_exists($name, $this->variables)) {
+ return $this->variables[$name];
+ }
+
+ return $default;
+ }
+
+ /**
+ * Set view variable
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return ViewModel
+ */
+ public function setVariable($name, $value)
+ {
+ $this->variables[(string) $name] = $value;
+ return $this;
+ }
+
+ /**
+ * Set view variables en masse
+ *
+ * Can be an array or a Traversable + ArrayAccess object.
+ *
+ * @param array|ArrayAccess|Traversable $variables
+ * @param bool $overwrite Whether or not to overwrite the internal container with $variables
+ * @throws Exception\InvalidArgumentException
+ * @return ViewModel
+ */
+ public function setVariables($variables, $overwrite = false)
+ {
+ if (!is_array($variables) && !$variables instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s: expects an array, or Traversable argument; received "%s"',
+ __METHOD__,
+ (is_object($variables) ? get_class($variables) : gettype($variables))
+ ));
+ }
+
+ if ($overwrite) {
+ if (is_object($variables) && !$variables instanceof ArrayAccess) {
+ $variables = ArrayUtils::iteratorToArray($variables);
+ }
+
+ $this->variables = $variables;
+ return $this;
+ }
+
+ foreach ($variables as $key => $value) {
+ $this->setVariable($key, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get view variables
+ *
+ * @return array|ArrayAccess|Traversable
+ */
+ public function getVariables()
+ {
+ return $this->variables;
+ }
+
+ /**
+ * Clear all variables
+ *
+ * Resets the internal variable container to an empty container.
+ *
+ * @return ViewModel
+ */
+ public function clearVariables()
+ {
+ $this->variables = new ViewVariables();
+ return $this;
+ }
+
+ /**
+ * Set the template to be used by this model
+ *
+ * @param string $template
+ * @return ViewModel
+ */
+ public function setTemplate($template)
+ {
+ $this->template = (string) $template;
+ return $this;
+ }
+
+ /**
+ * Get the template to be used by this model
+ *
+ * @return string
+ */
+ public function getTemplate()
+ {
+ return $this->template;
+ }
+
+ /**
+ * Add a child model
+ *
+ * @param ModelInterface $child
+ * @param null|string $captureTo Optional; if specified, the "capture to" value to set on the child
+ * @param null|bool $append Optional; if specified, append to child with the same capture
+ * @return ViewModel
+ */
+ public function addChild(ModelInterface $child, $captureTo = null, $append = null)
+ {
+ $this->children[] = $child;
+ if (null !== $captureTo) {
+ $child->setCaptureTo($captureTo);
+ }
+ if (null !== $append) {
+ $child->setAppend($append);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return all children.
+ *
+ * Return specifies an array, but may be any iterable object.
+ *
+ * @return array
+ */
+ public function getChildren()
+ {
+ return $this->children;
+ }
+
+ /**
+ * Does the model have any children?
+ *
+ * @return bool
+ */
+ public function hasChildren()
+ {
+ return (0 < count($this->children));
+ }
+
+ /**
+ * Clears out all child models
+ *
+ * @return ViewModel
+ */
+ public function clearChildren()
+ {
+ $this->children = array();
+ return $this;
+ }
+
+ /**
+ * Returns an array of Viewmodels with captureTo value $capture
+ *
+ * @param string $capture
+ * @param bool $recursive search recursive through children, default true
+ * @return array
+ */
+ public function getChildrenByCaptureTo($capture, $recursive = true)
+ {
+ $children = array();
+
+ foreach ($this->children as $child) {
+ if ($recursive === true) {
+ $children += $child->getChildrenByCaptureTo($capture);
+ }
+
+ if ($child->captureTo() === $capture) {
+ $children[] = $child;
+ }
+ }
+
+ return $children;
+ }
+
+ /**
+ * Set the name of the variable to capture this model to, if it is a child model
+ *
+ * @param string $capture
+ * @return ViewModel
+ */
+ public function setCaptureTo($capture)
+ {
+ $this->captureTo = (string) $capture;
+ return $this;
+ }
+
+ /**
+ * Get the name of the variable to which to capture this model
+ *
+ * @return string
+ */
+ public function captureTo()
+ {
+ return $this->captureTo;
+ }
+
+ /**
+ * Set flag indicating whether or not this is considered a terminal or standalone model
+ *
+ * @param bool $terminate
+ * @return ViewModel
+ */
+ public function setTerminal($terminate)
+ {
+ $this->terminate = (bool) $terminate;
+ return $this;
+ }
+
+ /**
+ * Is this considered a terminal or standalone model?
+ *
+ * @return bool
+ */
+ public function terminate()
+ {
+ return $this->terminate;
+ }
+
+ /**
+ * Set flag indicating whether or not append to child with the same capture
+ *
+ * @param bool $append
+ * @return ViewModel
+ */
+ public function setAppend($append)
+ {
+ $this->append = (bool) $append;
+ return $this;
+ }
+
+ /**
+ * Is this append to child with the same capture?
+ *
+ * @return bool
+ */
+ public function isAppend()
+ {
+ return $this->append;
+ }
+
+ /**
+ * Return count of children
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->children);
+ }
+
+ /**
+ * Get iterator of children
+ *
+ * @return ArrayIterator
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->children);
+ }
+}
diff --git a/library/Zend/View/README.md b/library/Zend/View/README.md
new file mode 100755
index 0000000000..ec19b2c016
--- /dev/null
+++ b/library/Zend/View/README.md
@@ -0,0 +1,15 @@
+View Component from ZF2
+=======================
+
+This is the View component for ZF2.
+
+- File issues at https://github.com/zendframework/zf2/issues
+- Create pull requests against https://github.com/zendframework/zf2
+- Documentation is at http://framework.zend.com/docs
+
+LICENSE
+-------
+
+The files in this archive are released under the [Zend Framework
+license](http://framework.zend.com/license), which is a 3-clause BSD license.
+
diff --git a/library/Zend/View/Renderer/ConsoleRenderer.php b/library/Zend/View/Renderer/ConsoleRenderer.php
new file mode 100755
index 0000000000..5f0cf10b26
--- /dev/null
+++ b/library/Zend/View/Renderer/ConsoleRenderer.php
@@ -0,0 +1,149 @@
+init();
+ }
+
+ public function setResolver(ResolverInterface $resolver)
+ {
+ return $this;
+ }
+
+ /**
+ * Return the template engine object
+ *
+ * Returns the object instance, as it is its own template engine
+ *
+ * @return PhpRenderer
+ */
+ public function getEngine()
+ {
+ return $this;
+ }
+
+ /**
+ * Allow custom object initialization when extending ConsoleRenderer
+ *
+ * Triggered by {@link __construct() the constructor} as its final action.
+ *
+ * @return void
+ */
+ public function init()
+ {
+ }
+
+ /**
+ * Set filter chain
+ *
+ * @param FilterChain $filters
+ * @return ConsoleRenderer
+ */
+ public function setFilterChain(FilterChain $filters)
+ {
+ $this->__filterChain = $filters;
+ return $this;
+ }
+
+ /**
+ * Retrieve filter chain for post-filtering script content
+ *
+ * @return FilterChain
+ */
+ public function getFilterChain()
+ {
+ if (null === $this->__filterChain) {
+ $this->setFilterChain(new FilterChain());
+ }
+ return $this->__filterChain;
+ }
+
+ /**
+ * Recursively processes all ViewModels and returns output.
+ *
+ * @param string|ModelInterface $model A ViewModel instance.
+ * @param null|array|\Traversable $values Values to use when rendering. If none
+ * provided, uses those in the composed
+ * variables container.
+ * @return string Console output.
+ */
+ public function render($model, $values = null)
+ {
+ if (!$model instanceof ModelInterface) {
+ return '';
+ }
+
+ $result = '';
+ $options = $model->getOptions();
+ foreach ($options as $setting => $value) {
+ $method = 'set' . $setting;
+ if (method_exists($this, $method)) {
+ $this->$method($value);
+ }
+ unset($method, $setting, $value);
+ }
+ unset($options);
+
+ $values = $model->getVariables();
+
+ if (isset($values['result'])) {
+ // filter and append the result
+ $result .= $this->getFilterChain()->filter($values['result']);
+ }
+
+ if ($model->hasChildren()) {
+ // recursively render all children
+ foreach ($model->getChildren() as $child) {
+ $result .= $this->render($child, $values);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * @see Zend\View\Renderer\TreeRendererInterface
+ * @return bool
+ */
+ public function canRenderTrees()
+ {
+ return true;
+ }
+}
diff --git a/library/Zend/View/Renderer/FeedRenderer.php b/library/Zend/View/Renderer/FeedRenderer.php
new file mode 100755
index 0000000000..b91964683e
--- /dev/null
+++ b/library/Zend/View/Renderer/FeedRenderer.php
@@ -0,0 +1,138 @@
+resolver = $resolver;
+ }
+
+ /**
+ * Renders values as JSON
+ *
+ * @todo Determine what use case exists for accepting only $nameOrModel
+ * @param string|Model $nameOrModel The script/resource process, or a view model
+ * @param null|array|\ArrayAccess $values Values to use during rendering
+ * @throws Exception\InvalidArgumentException
+ * @return string The script output.
+ */
+ public function render($nameOrModel, $values = null)
+ {
+ if ($nameOrModel instanceof Model) {
+ // Use case 1: View Model provided
+ // Non-FeedModel: cast to FeedModel
+ if (!$nameOrModel instanceof FeedModel) {
+ $vars = $nameOrModel->getVariables();
+ $options = $nameOrModel->getOptions();
+ $type = $this->getFeedType();
+ if (isset($options['feed_type'])) {
+ $type = $options['feed_type'];
+ } else {
+ $this->setFeedType($type);
+ }
+ $nameOrModel = new FeedModel($vars, array('feed_type' => $type));
+ }
+ } elseif (is_string($nameOrModel)) {
+ // Use case 2: string $nameOrModel + array|Traversable|Feed $values
+ $nameOrModel = new FeedModel($values, (array) $nameOrModel);
+ } else {
+ // Use case 3: failure
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects a ViewModel or a string feed type as the first argument; received "%s"',
+ __METHOD__,
+ (is_object($nameOrModel) ? get_class($nameOrModel) : gettype($nameOrModel))
+ ));
+ }
+
+ // Get feed and type
+ $feed = $nameOrModel->getFeed();
+ $type = $nameOrModel->getFeedType();
+ if (!$type) {
+ $type = $this->getFeedType();
+ } else {
+ $this->setFeedType($type);
+ }
+
+ // Render feed
+ return $feed->export($type);
+ }
+
+ /**
+ * Set feed type ('rss' or 'atom')
+ *
+ * @param string $feedType
+ * @throws Exception\InvalidArgumentException
+ * @return FeedRenderer
+ */
+ public function setFeedType($feedType)
+ {
+ $feedType = strtolower($feedType);
+ if (!in_array($feedType, array('rss', 'atom'))) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects a string of either "rss" or "atom"',
+ __METHOD__
+ ));
+ }
+
+ $this->feedType = $feedType;
+ return $this;
+ }
+
+ /**
+ * Get feed type
+ *
+ * @return string
+ */
+ public function getFeedType()
+ {
+ return $this->feedType;
+ }
+}
diff --git a/library/Zend/View/Renderer/JsonRenderer.php b/library/Zend/View/Renderer/JsonRenderer.php
new file mode 100755
index 0000000000..9666261db8
--- /dev/null
+++ b/library/Zend/View/Renderer/JsonRenderer.php
@@ -0,0 +1,243 @@
+resolver = $resolver;
+ }
+
+ /**
+ * Set flag indicating whether or not to merge unnamed children
+ *
+ * @param bool $mergeUnnamedChildren
+ * @return JsonRenderer
+ */
+ public function setMergeUnnamedChildren($mergeUnnamedChildren)
+ {
+ $this->mergeUnnamedChildren = (bool) $mergeUnnamedChildren;
+ return $this;
+ }
+
+ /**
+ * Set the JSONP callback function name
+ *
+ * @param string $callback
+ * @return JsonRenderer
+ */
+ public function setJsonpCallback($callback)
+ {
+ $callback = (string) $callback;
+ if (!empty($callback)) {
+ $this->jsonpCallback = $callback;
+ }
+ return $this;
+ }
+
+ /**
+ * Returns whether or not the jsonpCallback has been set
+ *
+ * @return bool
+ */
+ public function hasJsonpCallback()
+ {
+ return (null !== $this->jsonpCallback);
+ }
+
+ /**
+ * Should we merge unnamed children?
+ *
+ * @return bool
+ */
+ public function mergeUnnamedChildren()
+ {
+ return $this->mergeUnnamedChildren;
+ }
+
+ /**
+ * Renders values as JSON
+ *
+ * @todo Determine what use case exists for accepting both $nameOrModel and $values
+ * @param string|Model $nameOrModel The script/resource process, or a view model
+ * @param null|array|\ArrayAccess $values Values to use during rendering
+ * @throws Exception\DomainException
+ * @return string The script output.
+ */
+ public function render($nameOrModel, $values = null)
+ {
+ // use case 1: View Models
+ // Serialize variables in view model
+ if ($nameOrModel instanceof Model) {
+ if ($nameOrModel instanceof JsonModel) {
+ $children = $this->recurseModel($nameOrModel, false);
+ $this->injectChildren($nameOrModel, $children);
+ $values = $nameOrModel->serialize();
+ } else {
+ $values = $this->recurseModel($nameOrModel);
+ $values = Json::encode($values);
+ }
+
+ if ($this->hasJsonpCallback()) {
+ $values = $this->jsonpCallback . '(' . $values . ');';
+ }
+ return $values;
+ }
+
+ // use case 2: $nameOrModel is populated, $values is not
+ // Serialize $nameOrModel
+ if (null === $values) {
+ if (!is_object($nameOrModel) || $nameOrModel instanceof JsonSerializable) {
+ $return = Json::encode($nameOrModel);
+ } elseif ($nameOrModel instanceof Traversable) {
+ $nameOrModel = ArrayUtils::iteratorToArray($nameOrModel);
+ $return = Json::encode($nameOrModel);
+ } else {
+ $return = Json::encode(get_object_vars($nameOrModel));
+ }
+
+ if ($this->hasJsonpCallback()) {
+ $return = $this->jsonpCallback . '(' . $return . ');';
+ }
+ return $return;
+ }
+
+ // use case 3: Both $nameOrModel and $values are populated
+ throw new Exception\DomainException(sprintf(
+ '%s: Do not know how to handle operation when both $nameOrModel and $values are populated',
+ __METHOD__
+ ));
+ }
+
+ /**
+ * Can this renderer render trees of view models?
+ *
+ * Yes.
+ *
+ * @return true
+ */
+ public function canRenderTrees()
+ {
+ return true;
+ }
+
+ /**
+ * Retrieve values from a model and recurse its children to build a data structure
+ *
+ * @param Model $model
+ * @param bool $mergeWithVariables Whether or not to merge children with
+ * the variables of the $model
+ * @return array
+ */
+ protected function recurseModel(Model $model, $mergeWithVariables = true)
+ {
+ $values = array();
+ if ($mergeWithVariables) {
+ $values = $model->getVariables();
+ }
+
+ if ($values instanceof Traversable) {
+ $values = ArrayUtils::iteratorToArray($values);
+ }
+
+ if (!$model->hasChildren()) {
+ return $values;
+ }
+
+ $mergeChildren = $this->mergeUnnamedChildren();
+ foreach ($model as $child) {
+ $captureTo = $child->captureTo();
+ if (!$captureTo && !$mergeChildren) {
+ // We don't want to do anything with this child
+ continue;
+ }
+
+ $childValues = $this->recurseModel($child);
+ if ($captureTo) {
+ // Capturing to a specific key
+ // TODO please complete if append is true. must change old
+ // value to array and append to array?
+ $values[$captureTo] = $childValues;
+ } elseif ($mergeChildren) {
+ // Merging values with parent
+ $values = array_replace_recursive($values, $childValues);
+ }
+ }
+ return $values;
+ }
+
+ /**
+ * Inject discovered child model values into parent model
+ *
+ * @todo detect collisions and decide whether to append and/or aggregate?
+ * @param Model $model
+ * @param array $children
+ */
+ protected function injectChildren(Model $model, array $children)
+ {
+ foreach ($children as $child => $value) {
+ // TODO detect collisions and decide whether to append and/or aggregate?
+ $model->setVariable($child, $value);
+ }
+ }
+}
diff --git a/library/Zend/View/Renderer/PhpRenderer.php b/library/Zend/View/Renderer/PhpRenderer.php
new file mode 100755
index 0000000000..87baf3d988
--- /dev/null
+++ b/library/Zend/View/Renderer/PhpRenderer.php
@@ -0,0 +1,574 @@
+init();
+ }
+
+ /**
+ * Return the template engine object
+ *
+ * Returns the object instance, as it is its own template engine
+ *
+ * @return PhpRenderer
+ */
+ public function getEngine()
+ {
+ return $this;
+ }
+
+ /**
+ * Allow custom object initialization when extending PhpRenderer
+ *
+ * Triggered by {@link __construct() the constructor} as its final action.
+ *
+ * @return void
+ */
+ public function init()
+ {
+ }
+
+ /**
+ * Set script resolver
+ *
+ * @param Resolver $resolver
+ * @return PhpRenderer
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setResolver(Resolver $resolver)
+ {
+ $this->__templateResolver = $resolver;
+ return $this;
+ }
+
+ /**
+ * Retrieve template name or template resolver
+ *
+ * @param null|string $name
+ * @return string|Resolver
+ */
+ public function resolver($name = null)
+ {
+ if (null === $this->__templateResolver) {
+ $this->setResolver(new TemplatePathStack());
+ }
+
+ if (null !== $name) {
+ return $this->__templateResolver->resolve($name, $this);
+ }
+
+ return $this->__templateResolver;
+ }
+
+ /**
+ * Set variable storage
+ *
+ * Expects either an array, or an object implementing ArrayAccess.
+ *
+ * @param array|ArrayAccess $variables
+ * @return PhpRenderer
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setVars($variables)
+ {
+ if (!is_array($variables) && !$variables instanceof ArrayAccess) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Expected array or ArrayAccess object; received "%s"',
+ (is_object($variables) ? get_class($variables) : gettype($variables))
+ ));
+ }
+
+ // Enforce a Variables container
+ if (!$variables instanceof Variables) {
+ $variablesAsArray = array();
+ foreach ($variables as $key => $value) {
+ $variablesAsArray[$key] = $value;
+ }
+ $variables = new Variables($variablesAsArray);
+ }
+
+ $this->__vars = $variables;
+ return $this;
+ }
+
+ /**
+ * Get a single variable, or all variables
+ *
+ * @param mixed $key
+ * @return mixed
+ */
+ public function vars($key = null)
+ {
+ if (null === $this->__vars) {
+ $this->setVars(new Variables());
+ }
+
+ if (null === $key) {
+ return $this->__vars;
+ }
+ return $this->__vars[$key];
+ }
+
+ /**
+ * Get a single variable
+ *
+ * @param mixed $key
+ * @return mixed
+ */
+ public function get($key)
+ {
+ if (null === $this->__vars) {
+ $this->setVars(new Variables());
+ }
+
+ return $this->__vars[$key];
+ }
+
+ /**
+ * Overloading: proxy to Variables container
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ $vars = $this->vars();
+ return $vars[$name];
+ }
+
+ /**
+ * Overloading: proxy to Variables container
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return void
+ */
+ public function __set($name, $value)
+ {
+ $vars = $this->vars();
+ $vars[$name] = $value;
+ }
+
+ /**
+ * Overloading: proxy to Variables container
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ $vars = $this->vars();
+ return isset($vars[$name]);
+ }
+
+ /**
+ * Overloading: proxy to Variables container
+ *
+ * @param string $name
+ * @return void
+ */
+ public function __unset($name)
+ {
+ $vars = $this->vars();
+ if (!isset($vars[$name])) {
+ return;
+ }
+ unset($vars[$name]);
+ }
+
+ /**
+ * Set helper plugin manager instance
+ *
+ * @param string|HelperPluginManager $helpers
+ * @return PhpRenderer
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setHelperPluginManager($helpers)
+ {
+ if (is_string($helpers)) {
+ if (!class_exists($helpers)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Invalid helper helpers class provided (%s)',
+ $helpers
+ ));
+ }
+ $helpers = new $helpers();
+ }
+ if (!$helpers instanceof HelperPluginManager) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Helper helpers must extend Zend\View\HelperPluginManager; got type "%s" instead',
+ (is_object($helpers) ? get_class($helpers) : gettype($helpers))
+ ));
+ }
+ $helpers->setRenderer($this);
+ $this->__helpers = $helpers;
+
+ return $this;
+ }
+
+ /**
+ * Get helper plugin manager instance
+ *
+ * @return HelperPluginManager
+ */
+ public function getHelperPluginManager()
+ {
+ if (null === $this->__helpers) {
+ $this->setHelperPluginManager(new HelperPluginManager());
+ }
+ return $this->__helpers;
+ }
+
+ /**
+ * Get plugin instance
+ *
+ * @param string $name Name of plugin to return
+ * @param null|array $options Options to pass to plugin constructor (if not already instantiated)
+ * @return AbstractHelper
+ */
+ public function plugin($name, array $options = null)
+ {
+ return $this->getHelperPluginManager()->get($name, $options);
+ }
+
+ /**
+ * Overloading: proxy to helpers
+ *
+ * Proxies to the attached plugin manager to retrieve, return, and potentially
+ * execute helpers.
+ *
+ * * If the helper does not define __invoke, it will be returned
+ * * If the helper does define __invoke, it will be called as a functor
+ *
+ * @param string $method
+ * @param array $argv
+ * @return mixed
+ */
+ public function __call($method, $argv)
+ {
+ if (!isset($this->__pluginCache[$method])) {
+ $this->__pluginCache[$method] = $this->plugin($method);
+ }
+ if (is_callable($this->__pluginCache[$method])) {
+ return call_user_func_array($this->__pluginCache[$method], $argv);
+ }
+ return $this->__pluginCache[$method];
+ }
+
+ /**
+ * Set filter chain
+ *
+ * @param FilterChain $filters
+ * @return PhpRenderer
+ */
+ public function setFilterChain(FilterChain $filters)
+ {
+ $this->__filterChain = $filters;
+ return $this;
+ }
+
+ /**
+ * Retrieve filter chain for post-filtering script content
+ *
+ * @return FilterChain
+ */
+ public function getFilterChain()
+ {
+ if (null === $this->__filterChain) {
+ $this->setFilterChain(new FilterChain());
+ }
+ return $this->__filterChain;
+ }
+
+ /**
+ * Processes a view script and returns the output.
+ *
+ * @param string|Model $nameOrModel Either the template to use, or a
+ * ViewModel. The ViewModel must have the
+ * template as an option in order to be
+ * valid.
+ * @param null|array|Traversable $values Values to use when rendering. If none
+ * provided, uses those in the composed
+ * variables container.
+ * @return string The script output.
+ * @throws Exception\DomainException if a ViewModel is passed, but does not
+ * contain a template option.
+ * @throws Exception\InvalidArgumentException if the values passed are not
+ * an array or ArrayAccess object
+ * @throws Exception\RuntimeException if the template cannot be rendered
+ */
+ public function render($nameOrModel, $values = null)
+ {
+ if ($nameOrModel instanceof Model) {
+ $model = $nameOrModel;
+ $nameOrModel = $model->getTemplate();
+ if (empty($nameOrModel)) {
+ throw new Exception\DomainException(sprintf(
+ '%s: received View Model argument, but template is empty',
+ __METHOD__
+ ));
+ }
+ $options = $model->getOptions();
+ foreach ($options as $setting => $value) {
+ $method = 'set' . $setting;
+ if (method_exists($this, $method)) {
+ $this->$method($value);
+ }
+ unset($method, $setting, $value);
+ }
+ unset($options);
+
+ // Give view model awareness via ViewModel helper
+ $helper = $this->plugin('view_model');
+ $helper->setCurrent($model);
+
+ $values = $model->getVariables();
+ unset($model);
+ }
+
+ // find the script file name using the parent private method
+ $this->addTemplate($nameOrModel);
+ unset($nameOrModel); // remove $name from local scope
+
+ $this->__varsCache[] = $this->vars();
+
+ if (null !== $values) {
+ $this->setVars($values);
+ }
+ unset($values);
+
+ // extract all assigned vars (pre-escaped), but not 'this'.
+ // assigns to a double-underscored variable, to prevent naming collisions
+ $__vars = $this->vars()->getArrayCopy();
+ if (array_key_exists('this', $__vars)) {
+ unset($__vars['this']);
+ }
+ extract($__vars);
+ unset($__vars); // remove $__vars from local scope
+
+ while ($this->__template = array_pop($this->__templates)) {
+ $this->__file = $this->resolver($this->__template);
+ if (!$this->__file) {
+ throw new Exception\RuntimeException(sprintf(
+ '%s: Unable to render template "%s"; resolver could not resolve to a file',
+ __METHOD__,
+ $this->__template
+ ));
+ }
+ try {
+ ob_start();
+ $includeReturn = include $this->__file;
+ $this->__content = ob_get_clean();
+ } catch (\Exception $ex) {
+ ob_end_clean();
+ throw $ex;
+ }
+ if ($includeReturn === false && empty($this->__content)) {
+ throw new Exception\UnexpectedValueException(sprintf(
+ '%s: Unable to render template "%s"; file include failed',
+ __METHOD__,
+ $this->__file
+ ));
+ }
+ }
+
+ $this->setVars(array_pop($this->__varsCache));
+
+ return $this->getFilterChain()->filter($this->__content); // filter output
+ }
+
+ /**
+ * Set flag indicating whether or not we should render trees of view models
+ *
+ * If set to true, the View instance will not attempt to render children
+ * separately, but instead pass the root view model directly to the PhpRenderer.
+ * It is then up to the developer to render the children from within the
+ * view script.
+ *
+ * @param bool $renderTrees
+ * @return PhpRenderer
+ */
+ public function setCanRenderTrees($renderTrees)
+ {
+ $this->__renderTrees = (bool) $renderTrees;
+ return $this;
+ }
+
+ /**
+ * Can we render trees, or are we configured to do so?
+ *
+ * @return bool
+ */
+ public function canRenderTrees()
+ {
+ return $this->__renderTrees;
+ }
+
+ /**
+ * Add a template to the stack
+ *
+ * @param string $template
+ * @return PhpRenderer
+ */
+ public function addTemplate($template)
+ {
+ $this->__templates[] = $template;
+ return $this;
+ }
+
+ /**
+ * Make sure View variables are cloned when the view is cloned.
+ *
+ * @return PhpRenderer
+ */
+ public function __clone()
+ {
+ $this->__vars = clone $this->vars();
+ }
+}
diff --git a/library/Zend/View/Renderer/RendererInterface.php b/library/Zend/View/Renderer/RendererInterface.php
new file mode 100755
index 0000000000..d3dfa77f7c
--- /dev/null
+++ b/library/Zend/View/Renderer/RendererInterface.php
@@ -0,0 +1,47 @@
+queue = new PriorityQueue();
+ }
+
+ /**
+ * Return count of attached resolvers
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return $this->queue->count();
+ }
+
+ /**
+ * IteratorAggregate: return internal iterator
+ *
+ * @return PriorityQueue
+ */
+ public function getIterator()
+ {
+ return $this->queue;
+ }
+
+ /**
+ * Attach a resolver
+ *
+ * @param Resolver $resolver
+ * @param int $priority
+ * @return AggregateResolver
+ */
+ public function attach(Resolver $resolver, $priority = 1)
+ {
+ $this->queue->insert($resolver, $priority);
+ return $this;
+ }
+
+ /**
+ * Resolve a template/pattern name to a resource the renderer can consume
+ *
+ * @param string $name
+ * @param null|Renderer $renderer
+ * @return false|string
+ */
+ public function resolve($name, Renderer $renderer = null)
+ {
+ $this->lastLookupFailure = false;
+ $this->lastSuccessfulResolver = null;
+
+ if (0 === count($this->queue)) {
+ $this->lastLookupFailure = static::FAILURE_NO_RESOLVERS;
+ return false;
+ }
+
+ foreach ($this->queue as $resolver) {
+ $resource = $resolver->resolve($name, $renderer);
+ if (!$resource) {
+ // No resource found; try next resolver
+ continue;
+ }
+
+ // Resource found; return it
+ $this->lastSuccessfulResolver = $resolver;
+ return $resource;
+ }
+
+ $this->lastLookupFailure = static::FAILURE_NOT_FOUND;
+ return false;
+ }
+
+ /**
+ * Return the last successful resolver, if any
+ *
+ * @return Resolver
+ */
+ public function getLastSuccessfulResolver()
+ {
+ return $this->lastSuccessfulResolver;
+ }
+
+ /**
+ * Get last lookup failure
+ *
+ * @return false|string
+ */
+ public function getLastLookupFailure()
+ {
+ return $this->lastLookupFailure;
+ }
+}
diff --git a/library/Zend/View/Resolver/ResolverInterface.php b/library/Zend/View/Resolver/ResolverInterface.php
new file mode 100755
index 0000000000..8ffe130c66
--- /dev/null
+++ b/library/Zend/View/Resolver/ResolverInterface.php
@@ -0,0 +1,24 @@
+setMap($map);
+ }
+
+ /**
+ * IteratorAggregate: return internal iterator
+ *
+ * @return Traversable
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->map);
+ }
+
+ /**
+ * Set (overwrite) template map
+ *
+ * Maps should be arrays or Traversable objects with name => path pairs
+ *
+ * @param array|Traversable $map
+ * @throws Exception\InvalidArgumentException
+ * @return TemplateMapResolver
+ */
+ public function setMap($map)
+ {
+ if (!is_array($map) && !$map instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s: expects an array or Traversable, received "%s"',
+ __METHOD__,
+ (is_object($map) ? get_class($map) : gettype($map))
+ ));
+ }
+
+ if ($map instanceof Traversable) {
+ $map = ArrayUtils::iteratorToArray($map);
+ }
+
+ $this->map = $map;
+ return $this;
+ }
+
+ /**
+ * Add an entry to the map
+ *
+ * @param string|array|Traversable $nameOrMap
+ * @param null|string $path
+ * @throws Exception\InvalidArgumentException
+ * @return TemplateMapResolver
+ */
+ public function add($nameOrMap, $path = null)
+ {
+ if (is_array($nameOrMap) || $nameOrMap instanceof Traversable) {
+ $this->merge($nameOrMap);
+ return $this;
+ }
+
+ if (!is_string($nameOrMap)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s: expects a string, array, or Traversable for the first argument; received "%s"',
+ __METHOD__,
+ (is_object($nameOrMap) ? get_class($nameOrMap) : gettype($nameOrMap))
+ ));
+ }
+
+ if (empty($path)) {
+ if (isset($this->map[$nameOrMap])) {
+ unset($this->map[$nameOrMap]);
+ }
+ return $this;
+ }
+
+ $this->map[$nameOrMap] = $path;
+ return $this;
+ }
+
+ /**
+ * Merge internal map with provided map
+ *
+ * @param array|Traversable $map
+ * @throws Exception\InvalidArgumentException
+ * @return TemplateMapResolver
+ */
+ public function merge($map)
+ {
+ if (!is_array($map) && !$map instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s: expects an array or Traversable, received "%s"',
+ __METHOD__,
+ (is_object($map) ? get_class($map) : gettype($map))
+ ));
+ }
+
+ if ($map instanceof Traversable) {
+ $map = ArrayUtils::iteratorToArray($map);
+ }
+
+ $this->map = array_replace_recursive($this->map, $map);
+ return $this;
+ }
+
+ /**
+ * Does the resolver contain an entry for the given name?
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function has($name)
+ {
+ return array_key_exists($name, $this->map);
+ }
+
+ /**
+ * Retrieve a template path by name
+ *
+ * @param string $name
+ * @return false|string
+ * @throws Exception\DomainException if no entry exists
+ */
+ public function get($name)
+ {
+ if (!$this->has($name)) {
+ return false;
+ }
+ return $this->map[$name];
+ }
+
+ /**
+ * Retrieve the template map
+ *
+ * @return array
+ */
+ public function getMap()
+ {
+ return $this->map;
+ }
+
+ /**
+ * Resolve a template/pattern name to a resource the renderer can consume
+ *
+ * @param string $name
+ * @param null|Renderer $renderer
+ * @return string
+ */
+ public function resolve($name, Renderer $renderer = null)
+ {
+ return $this->get($name);
+ }
+}
diff --git a/library/Zend/View/Resolver/TemplatePathStack.php b/library/Zend/View/Resolver/TemplatePathStack.php
new file mode 100755
index 0000000000..359be18f34
--- /dev/null
+++ b/library/Zend/View/Resolver/TemplatePathStack.php
@@ -0,0 +1,338 @@
+useViewStream = (bool) ini_get('short_open_tag');
+ if ($this->useViewStream) {
+ if (!in_array('zend.view', stream_get_wrappers())) {
+ stream_wrapper_register('zend.view', 'Zend\View\Stream');
+ }
+ }
+
+ $this->paths = new SplStack;
+ if (null !== $options) {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * Configure object
+ *
+ * @param array|Traversable $options
+ * @return void
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setOptions($options)
+ {
+ if (!is_array($options) && !$options instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Expected array or Traversable object; received "%s"',
+ (is_object($options) ? get_class($options) : gettype($options))
+ ));
+ }
+
+ foreach ($options as $key => $value) {
+ switch (strtolower($key)) {
+ case 'lfi_protection':
+ $this->setLfiProtection($value);
+ break;
+ case 'script_paths':
+ $this->addPaths($value);
+ break;
+ case 'use_stream_wrapper':
+ $this->setUseStreamWrapper($value);
+ break;
+ case 'default_suffix':
+ $this->setDefaultSuffix($value);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * Set default file suffix
+ *
+ * @param string $defaultSuffix
+ * @return TemplatePathStack
+ */
+ public function setDefaultSuffix($defaultSuffix)
+ {
+ $this->defaultSuffix = (string) $defaultSuffix;
+ $this->defaultSuffix = ltrim($this->defaultSuffix, '.');
+ return $this;
+ }
+
+ /**
+ * Get default file suffix
+ *
+ * @return string
+ */
+ public function getDefaultSuffix()
+ {
+ return $this->defaultSuffix;
+ }
+
+ /**
+ * Add many paths to the stack at once
+ *
+ * @param array $paths
+ * @return TemplatePathStack
+ */
+ public function addPaths(array $paths)
+ {
+ foreach ($paths as $path) {
+ $this->addPath($path);
+ }
+ return $this;
+ }
+
+ /**
+ * Rest the path stack to the paths provided
+ *
+ * @param SplStack|array $paths
+ * @return TemplatePathStack
+ * @throws Exception\InvalidArgumentException
+ */
+ public function setPaths($paths)
+ {
+ if ($paths instanceof SplStack) {
+ $this->paths = $paths;
+ } elseif (is_array($paths)) {
+ $this->clearPaths();
+ $this->addPaths($paths);
+ } else {
+ throw new Exception\InvalidArgumentException(
+ "Invalid argument provided for \$paths, expecting either an array or SplStack object"
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Normalize a path for insertion in the stack
+ *
+ * @param string $path
+ * @return string
+ */
+ public static function normalizePath($path)
+ {
+ $path = rtrim($path, '/');
+ $path = rtrim($path, '\\');
+ $path .= DIRECTORY_SEPARATOR;
+ return $path;
+ }
+
+ /**
+ * Add a single path to the stack
+ *
+ * @param string $path
+ * @return TemplatePathStack
+ * @throws Exception\InvalidArgumentException
+ */
+ public function addPath($path)
+ {
+ if (!is_string($path)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Invalid path provided; must be a string, received %s',
+ gettype($path)
+ ));
+ }
+ $this->paths[] = static::normalizePath($path);
+ return $this;
+ }
+
+ /**
+ * Clear all paths
+ *
+ * @return void
+ */
+ public function clearPaths()
+ {
+ $this->paths = new SplStack;
+ }
+
+ /**
+ * Returns stack of paths
+ *
+ * @return SplStack
+ */
+ public function getPaths()
+ {
+ return $this->paths;
+ }
+
+ /**
+ * Set LFI protection flag
+ *
+ * @param bool $flag
+ * @return TemplatePathStack
+ */
+ public function setLfiProtection($flag)
+ {
+ $this->lfiProtectionOn = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Return status of LFI protection flag
+ *
+ * @return bool
+ */
+ public function isLfiProtectionOn()
+ {
+ return $this->lfiProtectionOn;
+ }
+
+ /**
+ * Set flag indicating if stream wrapper should be used if short_open_tag is off
+ *
+ * @param bool $flag
+ * @return TemplatePathStack
+ */
+ public function setUseStreamWrapper($flag)
+ {
+ $this->useStreamWrapper = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Should the stream wrapper be used if short_open_tag is off?
+ *
+ * Returns true if the use_stream_wrapper flag is set, and if short_open_tag
+ * is disabled.
+ *
+ * @return bool
+ */
+ public function useStreamWrapper()
+ {
+ return ($this->useViewStream && $this->useStreamWrapper);
+ }
+
+ /**
+ * Retrieve the filesystem path to a view script
+ *
+ * @param string $name
+ * @param null|Renderer $renderer
+ * @return string
+ * @throws Exception\DomainException
+ */
+ public function resolve($name, Renderer $renderer = null)
+ {
+ $this->lastLookupFailure = false;
+
+ if ($this->isLfiProtectionOn() && preg_match('#\.\.[\\\/]#', $name)) {
+ throw new Exception\DomainException(
+ 'Requested scripts may not include parent directory traversal ("../", "..\\" notation)'
+ );
+ }
+
+ if (!count($this->paths)) {
+ $this->lastLookupFailure = static::FAILURE_NO_PATHS;
+ return false;
+ }
+
+ // Ensure we have the expected file extension
+ $defaultSuffix = $this->getDefaultSuffix();
+ if (pathinfo($name, PATHINFO_EXTENSION) == '') {
+ $name .= '.' . $defaultSuffix;
+ }
+
+ foreach ($this->paths as $path) {
+ $file = new SplFileInfo($path . $name);
+ if ($file->isReadable()) {
+ // Found! Return it.
+ if (($filePath = $file->getRealPath()) === false && substr($path, 0, 7) === 'phar://') {
+ // Do not try to expand phar paths (realpath + phars == fail)
+ $filePath = $path . $name;
+ if (!file_exists($filePath)) {
+ break;
+ }
+ }
+ if ($this->useStreamWrapper()) {
+ // If using a stream wrapper, prepend the spec to the path
+ $filePath = 'zend.view://' . $filePath;
+ }
+ return $filePath;
+ }
+ }
+
+ $this->lastLookupFailure = static::FAILURE_NOT_FOUND;
+ return false;
+ }
+
+ /**
+ * Get the last lookup failure message, if any
+ *
+ * @return false|string
+ */
+ public function getLastLookupFailure()
+ {
+ return $this->lastLookupFailure;
+ }
+}
diff --git a/library/Zend/View/Strategy/FeedStrategy.php b/library/Zend/View/Strategy/FeedStrategy.php
new file mode 100755
index 0000000000..40140da5a6
--- /dev/null
+++ b/library/Zend/View/Strategy/FeedStrategy.php
@@ -0,0 +1,111 @@
+renderer = $renderer;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function attach(EventManagerInterface $events, $priority = 1)
+ {
+ $this->listeners[] = $events->attach(ViewEvent::EVENT_RENDERER, array($this, 'selectRenderer'), $priority);
+ $this->listeners[] = $events->attach(ViewEvent::EVENT_RESPONSE, array($this, 'injectResponse'), $priority);
+ }
+
+ /**
+ * Detect if we should use the FeedRenderer based on model type and/or
+ * Accept header
+ *
+ * @param ViewEvent $e
+ * @return null|FeedRenderer
+ */
+ public function selectRenderer(ViewEvent $e)
+ {
+ $model = $e->getModel();
+
+ if (!$model instanceof Model\FeedModel) {
+ // no FeedModel present; do nothing
+ return;
+ }
+
+ // FeedModel found
+ return $this->renderer;
+ }
+
+ /**
+ * Inject the response with the feed payload and appropriate Content-Type header
+ *
+ * @param ViewEvent $e
+ * @return void
+ */
+ public function injectResponse(ViewEvent $e)
+ {
+ $renderer = $e->getRenderer();
+ if ($renderer !== $this->renderer) {
+ // Discovered renderer is not ours; do nothing
+ return;
+ }
+
+ $result = $e->getResult();
+ if (!is_string($result) && !$result instanceof Feed) {
+ // We don't have a string, and thus, no feed
+ return;
+ }
+
+ // If the result is a feed, export it
+ if ($result instanceof Feed) {
+ $result = $result->export($renderer->getFeedType());
+ }
+
+ // Get the content-type header based on feed type
+ $feedType = $renderer->getFeedType();
+ $feedType = ('rss' == $feedType)
+ ? 'application/rss+xml'
+ : 'application/atom+xml';
+
+ $model = $e->getModel();
+ $charset = '';
+
+ if ($model instanceof Model\FeedModel) {
+ $feed = $model->getFeed();
+
+ $charset = '; charset=' . $feed->getEncoding() . ';';
+ }
+
+ // Populate response
+ $response = $e->getResponse();
+ $response->setContent($result);
+ $headers = $response->getHeaders();
+ $headers->addHeaderLine('content-type', $feedType . $charset);
+ }
+}
diff --git a/library/Zend/View/Strategy/JsonStrategy.php b/library/Zend/View/Strategy/JsonStrategy.php
new file mode 100755
index 0000000000..7cf4b91f09
--- /dev/null
+++ b/library/Zend/View/Strategy/JsonStrategy.php
@@ -0,0 +1,141 @@
+renderer = $renderer;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function attach(EventManagerInterface $events, $priority = 1)
+ {
+ $this->listeners[] = $events->attach(ViewEvent::EVENT_RENDERER, array($this, 'selectRenderer'), $priority);
+ $this->listeners[] = $events->attach(ViewEvent::EVENT_RESPONSE, array($this, 'injectResponse'), $priority);
+ }
+
+ /**
+ * Set the content-type character set
+ *
+ * @param string $charset
+ * @return JsonStrategy
+ */
+ public function setCharset($charset)
+ {
+ $this->charset = (string) $charset;
+ return $this;
+ }
+
+ /**
+ * Retrieve the current character set
+ *
+ * @return string
+ */
+ public function getCharset()
+ {
+ return $this->charset;
+ }
+
+ /**
+ * Detect if we should use the JsonRenderer based on model type and/or
+ * Accept header
+ *
+ * @param ViewEvent $e
+ * @return null|JsonRenderer
+ */
+ public function selectRenderer(ViewEvent $e)
+ {
+ $model = $e->getModel();
+
+ if (!$model instanceof Model\JsonModel) {
+ // no JsonModel; do nothing
+ return;
+ }
+
+ // JsonModel found
+ return $this->renderer;
+ }
+
+ /**
+ * Inject the response with the JSON payload and appropriate Content-Type header
+ *
+ * @param ViewEvent $e
+ * @return void
+ */
+ public function injectResponse(ViewEvent $e)
+ {
+ $renderer = $e->getRenderer();
+ if ($renderer !== $this->renderer) {
+ // Discovered renderer is not ours; do nothing
+ return;
+ }
+
+ $result = $e->getResult();
+ if (!is_string($result)) {
+ // We don't have a string, and thus, no JSON
+ return;
+ }
+
+ // Populate response
+ $response = $e->getResponse();
+ $response->setContent($result);
+ $headers = $response->getHeaders();
+
+ if ($this->renderer->hasJsonpCallback()) {
+ $contentType = 'application/javascript';
+ } else {
+ $contentType = 'application/json';
+ }
+
+ $contentType .= '; charset=' . $this->charset;
+ $headers->addHeaderLine('content-type', $contentType);
+
+ if (in_array(strtoupper($this->charset), $this->multibyteCharsets)) {
+ $headers->addHeaderLine('content-transfer-encoding', 'BINARY');
+ }
+ }
+}
diff --git a/library/Zend/View/Strategy/PhpRendererStrategy.php b/library/Zend/View/Strategy/PhpRendererStrategy.php
new file mode 100755
index 0000000000..e8e04ceb3a
--- /dev/null
+++ b/library/Zend/View/Strategy/PhpRendererStrategy.php
@@ -0,0 +1,127 @@
+renderer = $renderer;
+ }
+
+ /**
+ * Retrieve the composed renderer
+ *
+ * @return PhpRenderer
+ */
+ public function getRenderer()
+ {
+ return $this->renderer;
+ }
+
+ /**
+ * Set list of possible content placeholders
+ *
+ * @param array $contentPlaceholders
+ * @return PhpRendererStrategy
+ */
+ public function setContentPlaceholders(array $contentPlaceholders)
+ {
+ $this->contentPlaceholders = $contentPlaceholders;
+ return $this;
+ }
+
+ /**
+ * Get list of possible content placeholders
+ *
+ * @return array
+ */
+ public function getContentPlaceholders()
+ {
+ return $this->contentPlaceholders;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function attach(EventManagerInterface $events, $priority = 1)
+ {
+ $this->listeners[] = $events->attach(ViewEvent::EVENT_RENDERER, array($this, 'selectRenderer'), $priority);
+ $this->listeners[] = $events->attach(ViewEvent::EVENT_RESPONSE, array($this, 'injectResponse'), $priority);
+ }
+
+ /**
+ * Select the PhpRenderer; typically, this will be registered last or at
+ * low priority.
+ *
+ * @param ViewEvent $e
+ * @return PhpRenderer
+ */
+ public function selectRenderer(ViewEvent $e)
+ {
+ return $this->renderer;
+ }
+
+ /**
+ * Populate the response object from the View
+ *
+ * Populates the content of the response object from the view rendering
+ * results.
+ *
+ * @param ViewEvent $e
+ * @return void
+ */
+ public function injectResponse(ViewEvent $e)
+ {
+ $renderer = $e->getRenderer();
+ if ($renderer !== $this->renderer) {
+ return;
+ }
+
+ $result = $e->getResult();
+ $response = $e->getResponse();
+
+ // Set content
+ // If content is empty, check common placeholders to determine if they are
+ // populated, and set the content from them.
+ if (empty($result)) {
+ $placeholders = $renderer->plugin('placeholder');
+ foreach ($this->contentPlaceholders as $placeholder) {
+ if ($placeholders->containerExists($placeholder)) {
+ $result = (string) $placeholders->getContainer($placeholder);
+ break;
+ }
+ }
+ }
+ $response->setContent($result);
+ }
+}
diff --git a/library/Zend/View/Stream.php b/library/Zend/View/Stream.php
new file mode 100755
index 0000000000..42bb7a962d
--- /dev/null
+++ b/library/Zend/View/Stream.php
@@ -0,0 +1,183 @@
+data = file_get_contents($path);
+
+ /**
+ * If reading the file failed, update our local stat store
+ * to reflect the real stat of the file, then return on failure
+ */
+ if ($this->data === false) {
+ $this->stat = stat($path);
+ return false;
+ }
+
+ /**
+ * Convert = ?> to long-form and to
+ *
+ */
+ $this->data = preg_replace('/\<\?\=/', "data);
+ $this->data = preg_replace('/<\?(?!xml|php)/s', 'data);
+
+ /**
+ * file_get_contents() won't update PHP's stat cache, so we grab a stat
+ * of the file to prevent additional reads should the script be
+ * requested again, which will make include() happy.
+ */
+ $this->stat = stat($path);
+
+ return true;
+ }
+
+ /**
+ * Included so that __FILE__ returns the appropriate info
+ *
+ * @return array
+ */
+ public function url_stat()
+ {
+ return $this->stat;
+ }
+
+ /**
+ * Reads from the stream.
+ *
+ * @param int $count
+ * @return string
+ */
+ public function stream_read($count)
+ {
+ $ret = substr($this->data, $this->pos, $count);
+ $this->pos += strlen($ret);
+ return $ret;
+ }
+
+ /**
+ * Tells the current position in the stream.
+ *
+ * @return int
+ */
+ public function stream_tell()
+ {
+ return $this->pos;
+ }
+
+ /**
+ * Tells if we are at the end of the stream.
+ *
+ * @return bool
+ */
+ public function stream_eof()
+ {
+ return $this->pos >= strlen($this->data);
+ }
+
+ /**
+ * Stream statistics.
+ *
+ * @return array
+ */
+ public function stream_stat()
+ {
+ return $this->stat;
+ }
+
+ /**
+ * Seek to a specific point in the stream.
+ *
+ * @param $offset
+ * @param $whence
+ * @return bool
+ */
+ public function stream_seek($offset, $whence)
+ {
+ switch ($whence) {
+ case SEEK_SET:
+ if ($offset < strlen($this->data) && $offset >= 0) {
+ $this->pos = $offset;
+ return true;
+ } else {
+ return false;
+ }
+ break;
+
+ case SEEK_CUR:
+ if ($offset >= 0) {
+ $this->pos += $offset;
+ return true;
+ } else {
+ return false;
+ }
+ break;
+
+ case SEEK_END:
+ if (strlen($this->data) + $offset >= 0) {
+ $this->pos = strlen($this->data) + $offset;
+ return true;
+ } else {
+ return false;
+ }
+ break;
+
+ default:
+ return false;
+ }
+ }
+}
diff --git a/library/Zend/View/Variables.php b/library/Zend/View/Variables.php
new file mode 100755
index 0000000000..17b14724c8
--- /dev/null
+++ b/library/Zend/View/Variables.php
@@ -0,0 +1,162 @@
+setOptions($options);
+ }
+
+ /**
+ * Configure object
+ *
+ * @param array $options
+ * @return Variables
+ */
+ public function setOptions(array $options)
+ {
+ foreach ($options as $key => $value) {
+ switch (strtolower($key)) {
+ case 'strict_vars':
+ $this->setStrictVars($value);
+ break;
+ default:
+ // Unknown options are considered variables
+ $this[$key] = $value;
+ break;
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Set status of "strict vars" flag
+ *
+ * @param bool $flag
+ * @return Variables
+ */
+ public function setStrictVars($flag)
+ {
+ $this->strictVars = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Are we operating with strict variables?
+ *
+ * @return bool
+ */
+ public function isStrict()
+ {
+ return $this->strictVars;
+ }
+
+ /**
+ * Assign many values at once
+ *
+ * @param array|object $spec
+ * @return Variables
+ * @throws Exception\InvalidArgumentException
+ */
+ public function assign($spec)
+ {
+ if (is_object($spec)) {
+ if (method_exists($spec, 'toArray')) {
+ $spec = $spec->toArray();
+ } else {
+ $spec = (array) $spec;
+ }
+ }
+ if (!is_array($spec)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'assign() expects either an array or an object as an argument; received "%s"',
+ gettype($spec)
+ ));
+ }
+ foreach ($spec as $key => $value) {
+ $this[$key] = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the variable value
+ *
+ * If the value has not been defined, a null value will be returned; if
+ * strict vars on in place, a notice will also be raised.
+ *
+ * Otherwise, returns _escaped_ version of the value.
+ *
+ * @param mixed $key
+ * @return mixed
+ */
+ public function offsetGet($key)
+ {
+ if (!$this->offsetExists($key)) {
+ if ($this->isStrict()) {
+ trigger_error(sprintf(
+ 'View variable "%s" does not exist', $key
+ ), E_USER_NOTICE);
+ }
+ return null;
+ }
+
+ $return = parent::offsetGet($key);
+
+ // If we have a closure/functor, invoke it, and return its return value
+ if (is_object($return) && is_callable($return)) {
+ $return = call_user_func($return);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Clear all variables
+ *
+ * @return void
+ */
+ public function clear()
+ {
+ $this->exchangeArray(array());
+ }
+}
diff --git a/library/Zend/View/View.php b/library/Zend/View/View.php
new file mode 100755
index 0000000000..aebae58a09
--- /dev/null
+++ b/library/Zend/View/View.php
@@ -0,0 +1,264 @@
+request = $request;
+ return $this;
+ }
+
+ /**
+ * Set MVC response object
+ *
+ * @param Response $response
+ * @return View
+ */
+ public function setResponse(Response $response)
+ {
+ $this->response = $response;
+ return $this;
+ }
+
+ /**
+ * Get MVC request object
+ *
+ * @return null|Request
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ /**
+ * Get MVC response object
+ *
+ * @return null|Response
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ /**
+ * Set the event manager instance
+ *
+ * @param EventManagerInterface $events
+ * @return View
+ */
+ public function setEventManager(EventManagerInterface $events)
+ {
+ $events->setIdentifiers(array(
+ __CLASS__,
+ get_class($this),
+ ));
+ $this->events = $events;
+ return $this;
+ }
+
+ /**
+ * Retrieve the event manager instance
+ *
+ * Lazy-loads a default instance if none available
+ *
+ * @return EventManagerInterface
+ */
+ public function getEventManager()
+ {
+ if (!$this->events instanceof EventManagerInterface) {
+ $this->setEventManager(new EventManager());
+ }
+ return $this->events;
+ }
+
+ /**
+ * Add a rendering strategy
+ *
+ * Expects a callable. Strategies should accept a ViewEvent object, and should
+ * return a Renderer instance if the strategy is selected.
+ *
+ * Internally, the callable provided will be subscribed to the "renderer"
+ * event, at the priority specified.
+ *
+ * @param callable $callable
+ * @param int $priority
+ * @return View
+ */
+ public function addRenderingStrategy($callable, $priority = 1)
+ {
+ $this->getEventManager()->attach(ViewEvent::EVENT_RENDERER, $callable, $priority);
+ return $this;
+ }
+
+ /**
+ * Add a response strategy
+ *
+ * Expects a callable. Strategies should accept a ViewEvent object. The return
+ * value will be ignored.
+ *
+ * Typical usages for a response strategy are to populate the Response object.
+ *
+ * Internally, the callable provided will be subscribed to the "response"
+ * event, at the priority specified.
+ *
+ * @param callable $callable
+ * @param int $priority
+ * @return View
+ */
+ public function addResponseStrategy($callable, $priority = 1)
+ {
+ $this->getEventManager()->attach(ViewEvent::EVENT_RESPONSE, $callable, $priority);
+ return $this;
+ }
+
+ /**
+ * Render the provided model.
+ *
+ * Internally, the following workflow is used:
+ *
+ * - Trigger the "renderer" event to select a renderer.
+ * - Call the selected renderer with the provided Model
+ * - Trigger the "response" event
+ *
+ * @triggers renderer(ViewEvent)
+ * @triggers response(ViewEvent)
+ * @param Model $model
+ * @throws Exception\RuntimeException
+ * @return void
+ */
+ public function render(Model $model)
+ {
+ $event = $this->getEvent();
+ $event->setModel($model);
+ $events = $this->getEventManager();
+ $results = $events->trigger(ViewEvent::EVENT_RENDERER, $event, function ($result) {
+ return ($result instanceof Renderer);
+ });
+ $renderer = $results->last();
+ if (!$renderer instanceof Renderer) {
+ throw new Exception\RuntimeException(sprintf(
+ '%s: no renderer selected!',
+ __METHOD__
+ ));
+ }
+
+ $event->setRenderer($renderer);
+ $events->trigger(ViewEvent::EVENT_RENDERER_POST, $event);
+
+ // If EVENT_RENDERER or EVENT_RENDERER_POST changed the model, make sure
+ // we use this new model instead of the current $model
+ $model = $event->getModel();
+
+ // If we have children, render them first, but only if:
+ // a) the renderer does not implement TreeRendererInterface, or
+ // b) it does, but canRenderTrees() returns false
+ if ($model->hasChildren()
+ && (!$renderer instanceof TreeRendererInterface
+ || !$renderer->canRenderTrees())
+ ) {
+ $this->renderChildren($model);
+ }
+
+ // Reset the model, in case it has changed, and set the renderer
+ $event->setModel($model);
+ $event->setRenderer($renderer);
+
+ $rendered = $renderer->render($model);
+
+ // If this is a child model, return the rendered content; do not
+ // invoke the response strategy.
+ $options = $model->getOptions();
+ if (array_key_exists('has_parent', $options) && $options['has_parent']) {
+ return $rendered;
+ }
+
+ $event->setResult($rendered);
+
+ $events->trigger(ViewEvent::EVENT_RESPONSE, $event);
+ }
+
+ /**
+ * Loop through children, rendering each
+ *
+ * @param Model $model
+ * @throws Exception\DomainException
+ * @return void
+ */
+ protected function renderChildren(Model $model)
+ {
+ foreach ($model as $child) {
+ if ($child->terminate()) {
+ throw new Exception\DomainException('Inconsistent state; child view model is marked as terminal');
+ }
+ $child->setOption('has_parent', true);
+ $result = $this->render($child);
+ $child->setOption('has_parent', null);
+ $capture = $child->captureTo();
+ if (!empty($capture)) {
+ if ($child->isAppend()) {
+ $oldResult=$model->{$capture};
+ $model->setVariable($capture, $oldResult . $result);
+ } else {
+ $model->setVariable($capture, $result);
+ }
+ }
+ }
+ }
+
+ /**
+ * Create and return ViewEvent used by render()
+ *
+ * @return ViewEvent
+ */
+ protected function getEvent()
+ {
+ $event = new ViewEvent();
+ $event->setTarget($this);
+ if (null !== ($request = $this->getRequest())) {
+ $event->setRequest($request);
+ }
+ if (null !== ($response = $this->getResponse())) {
+ $event->setResponse($response);
+ }
+ return $event;
+ }
+}
diff --git a/library/Zend/View/ViewEvent.php b/library/Zend/View/ViewEvent.php
new file mode 100755
index 0000000000..34baa2136f
--- /dev/null
+++ b/library/Zend/View/ViewEvent.php
@@ -0,0 +1,258 @@
+model = $model;
+ return $this;
+ }
+
+ /**
+ * Set the MVC request object
+ *
+ * @param Request $request
+ * @return ViewEvent
+ */
+ public function setRequest(Request $request)
+ {
+ $this->request = $request;
+ return $this;
+ }
+
+ /**
+ * Set the MVC response object
+ *
+ * @param Response $response
+ * @return ViewEvent
+ */
+ public function setResponse(Response $response)
+ {
+ $this->response = $response;
+ return $this;
+ }
+
+ /**
+ * Set result of rendering
+ *
+ * @param mixed $result
+ * @return ViewEvent
+ */
+ public function setResult($result)
+ {
+ $this->result = $result;
+ return $this;
+ }
+
+ /**
+ * Retrieve the view model
+ *
+ * @return null|Model
+ */
+ public function getModel()
+ {
+ return $this->model;
+ }
+
+ /**
+ * Set value for renderer
+ *
+ * @param Renderer $renderer
+ * @return ViewEvent
+ */
+ public function setRenderer(Renderer $renderer)
+ {
+ $this->renderer = $renderer;
+ return $this;
+ }
+
+ /**
+ * Get value for renderer
+ *
+ * @return null|Renderer
+ */
+ public function getRenderer()
+ {
+ return $this->renderer;
+ }
+
+ /**
+ * Retrieve the MVC request object
+ *
+ * @return null|Request
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ /**
+ * Retrieve the MVC response object
+ *
+ * @return null|Response
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ /**
+ * Retrieve the result of rendering
+ *
+ * @return mixed
+ */
+ public function getResult()
+ {
+ return $this->result;
+ }
+
+ /**
+ * Get event parameter
+ *
+ * @param string $name
+ * @param mixed $default
+ * @return mixed
+ */
+ public function getParam($name, $default = null)
+ {
+ switch ($name) {
+ case 'model':
+ return $this->getModel();
+ case 'renderer':
+ return $this->getRenderer();
+ case 'request':
+ return $this->getRequest();
+ case 'response':
+ return $this->getResponse();
+ case 'result':
+ return $this->getResult();
+ default:
+ return parent::getParam($name, $default);
+ }
+ }
+
+ /**
+ * Get all event parameters
+ *
+ * @return array|\ArrayAccess
+ */
+ public function getParams()
+ {
+ $params = parent::getParams();
+ $params['model'] = $this->getModel();
+ $params['renderer'] = $this->getRenderer();
+ $params['request'] = $this->getRequest();
+ $params['response'] = $this->getResponse();
+ $params['result'] = $this->getResult();
+ return $params;
+ }
+
+ /**
+ * Set event parameters
+ *
+ * @param array|object|ArrayAccess $params
+ * @return ViewEvent
+ */
+ public function setParams($params)
+ {
+ parent::setParams($params);
+ if (!is_array($params) && !$params instanceof ArrayAccess) {
+ return $this;
+ }
+
+ foreach (array('model', 'renderer', 'request', 'response', 'result') as $param) {
+ if (isset($params[$param])) {
+ $method = 'set' . $param;
+ $this->$method($params[$param]);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Set an individual event parameter
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return ViewEvent
+ */
+ public function setParam($name, $value)
+ {
+ switch ($name) {
+ case 'model':
+ $this->setModel($value);
+ break;
+ case 'renderer':
+ $this->setRenderer($value);
+ break;
+ case 'request':
+ $this->setRequest($value);
+ break;
+ case 'response':
+ $this->setResponse($value);
+ break;
+ case 'result':
+ $this->setResult($value);
+ break;
+ default:
+ parent::setParam($name, $value);
+ break;
+ }
+ return $this;
+ }
+}
diff --git a/library/Zend/View/composer.json b/library/Zend/View/composer.json
new file mode 100755
index 0000000000..44bb5c2751
--- /dev/null
+++ b/library/Zend/View/composer.json
@@ -0,0 +1,58 @@
+{
+ "name": "zendframework/zend-view",
+ "description": "provides a system of helpers, output filters, and variable escaping",
+ "license": "BSD-3-Clause",
+ "keywords": [
+ "zf2",
+ "view"
+ ],
+ "homepage": "https://github.com/zendframework/zf2",
+ "autoload": {
+ "psr-0": {
+ "Zend\\View\\": ""
+ }
+ },
+ "target-dir": "Zend/View",
+ "require": {
+ "php": ">=5.3.23",
+ "zendframework/zend-eventmanager": "self.version",
+ "zendframework/zend-loader": "self.version",
+ "zendframework/zend-stdlib": "self.version"
+ },
+ "require-dev": {
+ "zendframework/zend-authentication": "self.version",
+ "zendframework/zend-escaper": "self.version",
+ "zendframework/zend-feed": "self.version",
+ "zendframework/zend-filter": "self.version",
+ "zendframework/zend-http": "self.version",
+ "zendframework/zend-i18n": "self.version",
+ "zendframework/zend-json": "self.version",
+ "zendframework/zend-mvc": "self.version",
+ "zendframework/zend-navigation": "self.version",
+ "zendframework/zend-paginator": "self.version",
+ "zendframework/zend-permissions-acl": "self.version",
+ "zendframework/zend-servicemanager": "self.version",
+ "zendframework/zend-uri": "self.version"
+ },
+ "suggest": {
+ "zendframework/zend-authentication": "Zend\\Authentication component",
+ "zendframework/zend-escaper": "Zend\\Escaper component",
+ "zendframework/zend-feed": "Zend\\Feed component",
+ "zendframework/zend-filter": "Zend\\Filter component",
+ "zendframework/zend-http": "Zend\\Http component",
+ "zendframework/zend-i18n": "Zend\\I18n component",
+ "zendframework/zend-json": "Zend\\Json component",
+ "zendframework/zend-mvc": "Zend\\Mvc component",
+ "zendframework/zend-navigation": "Zend\\Navigation component",
+ "zendframework/zend-paginator": "Zend\\Paginator component",
+ "zendframework/zend-permissions-acl": "Zend\\Permissions\\Acl component",
+ "zendframework/zend-servicemanager": "Zend\\ServiceManager component",
+ "zendframework/zend-uri": "Zend\\Uri component"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev",
+ "dev-develop": "2.4-dev"
+ }
+ }
+}
diff --git a/library/Zend/XmlRpc/AbstractValue.php b/library/Zend/XmlRpc/AbstractValue.php
new file mode 100755
index 0000000000..221238a094
--- /dev/null
+++ b/library/Zend/XmlRpc/AbstractValue.php
@@ -0,0 +1,462 @@
+type;
+ }
+
+ /**
+ * Get XML generator instance
+ *
+ * @return \Zend\XmlRpc\Generator\GeneratorInterface
+ */
+ public static function getGenerator()
+ {
+ if (!static::$generator) {
+ if (extension_loaded('xmlwriter')) {
+ static::$generator = new Generator\XmlWriter();
+ } else {
+ static::$generator = new Generator\DomDocument();
+ }
+ }
+
+ return static::$generator;
+ }
+
+ /**
+ * Sets XML generator instance
+ *
+ * @param null|Generator\GeneratorInterface $generator
+ * @return void
+ */
+ public static function setGenerator(Generator\GeneratorInterface $generator = null)
+ {
+ static::$generator = $generator;
+ }
+
+ /**
+ * Changes the encoding of the generator
+ *
+ * @param string $encoding
+ * @return void
+ */
+ public static function setEncoding($encoding)
+ {
+ $generator = static::getGenerator();
+ $newGenerator = new $generator($encoding);
+ static::setGenerator($newGenerator);
+ }
+
+ /**
+ * Return the value of this object, convert the XML-RPC native value into a PHP variable
+ *
+ * @return mixed
+ */
+ abstract public function getValue();
+
+
+ /**
+ * Return the XML code that represent a native MXL-RPC value
+ *
+ * @return string
+ */
+ public function saveXml()
+ {
+ if (!$this->xml) {
+ $this->generateXml();
+ $this->xml = (string) $this->getGenerator();
+ }
+ return $this->xml;
+ }
+
+ /**
+ * Generate XML code that represent a native XML/RPC value
+ *
+ * @return void
+ */
+ public function generateXml()
+ {
+ $this->_generateXml();
+ }
+
+ /**
+ * Creates a Value* object, representing a native XML-RPC value
+ * A XmlRpcValue object can be created in 3 ways:
+ * 1. Autodetecting the native type out of a PHP variable
+ * (if $type is not set or equal to Value::AUTO_DETECT_TYPE)
+ * 2. By specifying the native type ($type is one of the Value::XMLRPC_TYPE_* constants)
+ * 3. From a XML string ($type is set to Value::XML_STRING)
+ *
+ * By default the value type is autodetected according to it's PHP type
+ *
+ * @param mixed $value
+ * @param Zend\XmlRpc\Value::constant $type
+ * @throws Exception\ValueException
+ * @return AbstractValue
+ */
+ public static function getXmlRpcValue($value, $type = self::AUTO_DETECT_TYPE)
+ {
+ switch ($type) {
+ case self::AUTO_DETECT_TYPE:
+ // Auto detect the XML-RPC native type from the PHP type of $value
+ return static::_phpVarToNativeXmlRpc($value);
+
+ case self::XML_STRING:
+ // Parse the XML string given in $value and get the XML-RPC value in it
+ return static::_xmlStringToNativeXmlRpc($value);
+
+ case self::XMLRPC_TYPE_I4:
+ // fall through to the next case
+ case self::XMLRPC_TYPE_INTEGER:
+ return new Value\Integer($value);
+
+ case self::XMLRPC_TYPE_I8:
+ // fall through to the next case
+ case self::XMLRPC_TYPE_APACHEI8:
+ return new Value\BigInteger($value);
+
+ case self::XMLRPC_TYPE_DOUBLE:
+ return new Value\Double($value);
+
+ case self::XMLRPC_TYPE_BOOLEAN:
+ return new Value\Boolean($value);
+
+ case self::XMLRPC_TYPE_STRING:
+ return new Value\String($value);
+
+ case self::XMLRPC_TYPE_BASE64:
+ return new Value\Base64($value);
+
+ case self::XMLRPC_TYPE_NIL:
+ // fall through to the next case
+ case self::XMLRPC_TYPE_APACHENIL:
+ return new Value\Nil();
+
+ case self::XMLRPC_TYPE_DATETIME:
+ return new Value\DateTime($value);
+
+ case self::XMLRPC_TYPE_ARRAY:
+ return new Value\ArrayValue($value);
+
+ case self::XMLRPC_TYPE_STRUCT:
+ return new Value\Struct($value);
+
+ default:
+ throw new Exception\ValueException('Given type is not a '. __CLASS__ .' constant');
+ }
+ }
+
+ /**
+ * Get XML-RPC type for a PHP native variable
+ *
+ * @static
+ * @param mixed $value
+ * @throws Exception\InvalidArgumentException
+ * @return string
+ */
+ public static function getXmlRpcTypeByValue($value)
+ {
+ if (is_object($value)) {
+ if ($value instanceof AbstractValue) {
+ return $value->getType();
+ } elseif ($value instanceof DateTime) {
+ return self::XMLRPC_TYPE_DATETIME;
+ }
+ return static::getXmlRpcTypeByValue(get_object_vars($value));
+ } elseif (is_array($value)) {
+ if (!empty($value) && is_array($value) && (array_keys($value) !== range(0, count($value) - 1))) {
+ return self::XMLRPC_TYPE_STRUCT;
+ }
+ return self::XMLRPC_TYPE_ARRAY;
+ } elseif (is_int($value)) {
+ return ($value > PHP_INT_MAX) ? self::XMLRPC_TYPE_I8 : self::XMLRPC_TYPE_INTEGER;
+ } elseif (is_double($value)) {
+ return self::XMLRPC_TYPE_DOUBLE;
+ } elseif (is_bool($value)) {
+ return self::XMLRPC_TYPE_BOOLEAN;
+ } elseif (null === $value) {
+ return self::XMLRPC_TYPE_NIL;
+ } elseif (is_string($value)) {
+ return self::XMLRPC_TYPE_STRING;
+ }
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'No matching XMLRPC type found for php type %s.',
+ gettype($value)
+ ));
+ }
+
+ /**
+ * Transform a PHP native variable into a XML-RPC native value
+ *
+ * @param mixed $value The PHP variable for conversion
+ *
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractValue
+ * @static
+ */
+ protected static function _phpVarToNativeXmlRpc($value)
+ {
+ // @see http://framework.zend.com/issues/browse/ZF-8623
+ if ($value instanceof AbstractValue) {
+ return $value;
+ }
+
+ switch (static::getXmlRpcTypeByValue($value)) {
+ case self::XMLRPC_TYPE_DATETIME:
+ return new Value\DateTime($value);
+
+ case self::XMLRPC_TYPE_ARRAY:
+ return new Value\ArrayValue($value);
+
+ case self::XMLRPC_TYPE_STRUCT:
+ return new Value\Struct($value);
+
+ case self::XMLRPC_TYPE_INTEGER:
+ return new Value\Integer($value);
+
+ case self::XMLRPC_TYPE_DOUBLE:
+ return new Value\Double($value);
+
+ case self::XMLRPC_TYPE_BOOLEAN:
+ return new Value\Boolean($value);
+
+ case self::XMLRPC_TYPE_NIL:
+ return new Value\Nil;
+
+ case self::XMLRPC_TYPE_STRING:
+ // Fall through to the next case
+ default:
+ // If type isn't identified (or identified as string), it treated as string
+ return new Value\String($value);
+ }
+ }
+
+ /**
+ * Transform an XML string into a XML-RPC native value
+ *
+ * @param string|\SimpleXMLElement $xml A SimpleXMLElement object represent the XML string
+ * It can be also a valid XML string for conversion
+ *
+ * @throws Exception\ValueException
+ * @return \Zend\XmlRpc\AbstractValue
+ * @static
+ */
+ protected static function _xmlStringToNativeXmlRpc($xml)
+ {
+ static::_createSimpleXMLElement($xml);
+
+ static::_extractTypeAndValue($xml, $type, $value);
+
+ switch ($type) {
+ // All valid and known XML-RPC native values
+ case self::XMLRPC_TYPE_I4:
+ // Fall through to the next case
+ case self::XMLRPC_TYPE_INTEGER:
+ $xmlrpcValue = new Value\Integer($value);
+ break;
+ case self::XMLRPC_TYPE_APACHEI8:
+ // Fall through to the next case
+ case self::XMLRPC_TYPE_I8:
+ $xmlrpcValue = new Value\BigInteger($value);
+ break;
+ case self::XMLRPC_TYPE_DOUBLE:
+ $xmlrpcValue = new Value\Double($value);
+ break;
+ case self::XMLRPC_TYPE_BOOLEAN:
+ $xmlrpcValue = new Value\Boolean($value);
+ break;
+ case self::XMLRPC_TYPE_STRING:
+ $xmlrpcValue = new Value\String($value);
+ break;
+ case self::XMLRPC_TYPE_DATETIME: // The value should already be in an iso8601 format
+ $xmlrpcValue = new Value\DateTime($value);
+ break;
+ case self::XMLRPC_TYPE_BASE64: // The value should already be base64 encoded
+ $xmlrpcValue = new Value\Base64($value, true);
+ break;
+ case self::XMLRPC_TYPE_NIL:
+ // Fall through to the next case
+ case self::XMLRPC_TYPE_APACHENIL:
+ // The value should always be NULL
+ $xmlrpcValue = new Value\Nil();
+ break;
+ case self::XMLRPC_TYPE_ARRAY:
+ // PHP 5.2.4 introduced a regression in how empty($xml->value)
+ // returns; need to look for the item specifically
+ $data = null;
+ foreach ($value->children() as $key => $value) {
+ if ('data' == $key) {
+ $data = $value;
+ break;
+ }
+ }
+
+ if (null === $data) {
+ throw new Exception\ValueException('Invalid XML for XML-RPC native '. self::XMLRPC_TYPE_ARRAY .' type: ARRAY tag must contain DATA tag');
+ }
+ $values = array();
+ // Parse all the elements of the array from the XML string
+ // (simple xml element) to Value objects
+ foreach ($data->value as $element) {
+ $values[] = static::_xmlStringToNativeXmlRpc($element);
+ }
+ $xmlrpcValue = new Value\ArrayValue($values);
+ break;
+ case self::XMLRPC_TYPE_STRUCT:
+ $values = array();
+ // Parse all the members of the struct from the XML string
+ // (simple xml element) to Value objects
+ foreach ($value->member as $member) {
+ // @todo? If a member doesn't have a tag, we don't add it to the struct
+ // Maybe we want to throw an exception here ?
+ if (!isset($member->value) or !isset($member->name)) {
+ continue;
+ //throw new Value_Exception('Member of the '. self::XMLRPC_TYPE_STRUCT .' XML-RPC native type must contain a VALUE tag');
+ }
+ $values[(string) $member->name] = static::_xmlStringToNativeXmlRpc($member->value);
+ }
+ $xmlrpcValue = new Value\Struct($values);
+ break;
+ default:
+ throw new Exception\ValueException('Value type \''. $type .'\' parsed from the XML string is not a known XML-RPC native type');
+ break;
+ }
+ $xmlrpcValue->_setXML($xml->asXML());
+
+ return $xmlrpcValue;
+ }
+
+ protected static function _createSimpleXMLElement(&$xml)
+ {
+ if ($xml instanceof \SimpleXMLElement) {
+ return;
+ }
+
+ try {
+ $xml = new \SimpleXMLElement($xml);
+ } catch (\Exception $e) {
+ // The given string is not a valid XML
+ throw new Exception\ValueException('Failed to create XML-RPC value from XML string: ' . $e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Extract XML/RPC type and value from SimpleXMLElement object
+ *
+ * @param \SimpleXMLElement $xml
+ * @param string &$type Type bind variable
+ * @param string &$value Value bind variable
+ * @return void
+ */
+ protected static function _extractTypeAndValue(\SimpleXMLElement $xml, &$type, &$value)
+ {
+ list($type, $value) = each($xml);
+ if (!$type and $value === null) {
+ $namespaces = array('ex' => 'http://ws.apache.org/xmlrpc/namespaces/extensions');
+ foreach ($namespaces as $namespaceName => $namespaceUri) {
+ $namespaceXml = $xml->children($namespaceUri);
+ list($type, $value) = each($namespaceXml);
+ if ($type !== null) {
+ $type = $namespaceName . ':' . $type;
+ break;
+ }
+ }
+ }
+
+ // If no type was specified, the default is string
+ if (!$type) {
+ $type = self::XMLRPC_TYPE_STRING;
+ if (empty($value) and preg_match('#^.*$#', $xml->asXML())) {
+ $value = str_replace(array('', ''), '', $xml->asXML());
+ }
+ }
+ }
+
+ /**
+ * @param $xml
+ * @return void
+ */
+ protected function _setXML($xml)
+ {
+ $this->xml = $this->getGenerator()->stripDeclaration($xml);
+ }
+}
diff --git a/library/Zend/XmlRpc/CONTRIBUTING.md b/library/Zend/XmlRpc/CONTRIBUTING.md
new file mode 100755
index 0000000000..e77f5d2d5b
--- /dev/null
+++ b/library/Zend/XmlRpc/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# CONTRIBUTING
+
+Please don't open pull requests against this repository, please use https://github.com/zendframework/zf2.
\ No newline at end of file
diff --git a/library/Zend/XmlRpc/Client.php b/library/Zend/XmlRpc/Client.php
new file mode 100755
index 0000000000..504a3bf231
--- /dev/null
+++ b/library/Zend/XmlRpc/Client.php
@@ -0,0 +1,343 @@
+httpClient = new Http\Client();
+ } else {
+ $this->httpClient = $httpClient;
+ }
+
+ $this->introspector = new Client\ServerIntrospection($this);
+ $this->serverAddress = $server;
+ }
+
+
+ /**
+ * Sets the HTTP client object to use for connecting the XML-RPC server.
+ *
+ * @param \Zend\Http\Client $httpClient
+ * @return \Zend\Http\Client
+ */
+ public function setHttpClient(Http\Client $httpClient)
+ {
+ return $this->httpClient = $httpClient;
+ }
+
+
+ /**
+ * Gets the HTTP client object.
+ *
+ * @return \Zend\Http\Client
+ */
+ public function getHttpClient()
+ {
+ return $this->httpClient;
+ }
+
+
+ /**
+ * Sets the object used to introspect remote servers
+ *
+ * @param \Zend\XmlRpc\Client\ServerIntrospection
+ * @return \Zend\XmlRpc\Client\ServerIntrospection
+ */
+ public function setIntrospector(Client\ServerIntrospection $introspector)
+ {
+ return $this->introspector = $introspector;
+ }
+
+
+ /**
+ * Gets the introspection object.
+ *
+ * @return \Zend\XmlRpc\Client\ServerIntrospection
+ */
+ public function getIntrospector()
+ {
+ return $this->introspector;
+ }
+
+
+ /**
+ * The request of the last method call
+ *
+ * @return \Zend\XmlRpc\Request
+ */
+ public function getLastRequest()
+ {
+ return $this->lastRequest;
+ }
+
+
+ /**
+ * The response received from the last method call
+ *
+ * @return \Zend\XmlRpc\Response
+ */
+ public function getLastResponse()
+ {
+ return $this->lastResponse;
+ }
+
+
+ /**
+ * Returns a proxy object for more convenient method calls
+ *
+ * @param string $namespace Namespace to proxy or empty string for none
+ * @return \Zend\XmlRpc\Client\ServerProxy
+ */
+ public function getProxy($namespace = '')
+ {
+ if (empty($this->proxyCache[$namespace])) {
+ $proxy = new Client\ServerProxy($this, $namespace);
+ $this->proxyCache[$namespace] = $proxy;
+ }
+ return $this->proxyCache[$namespace];
+ }
+
+ /**
+ * Set skip system lookup flag
+ *
+ * @param bool $flag
+ * @return \Zend\XmlRpc\Client
+ */
+ public function setSkipSystemLookup($flag = true)
+ {
+ $this->skipSystemLookup = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Skip system lookup when determining if parameter should be array or struct?
+ *
+ * @return bool
+ */
+ public function skipSystemLookup()
+ {
+ return $this->skipSystemLookup;
+ }
+
+ /**
+ * Perform an XML-RPC request and return a response.
+ *
+ * @param \Zend\XmlRpc\Request $request
+ * @param null|\Zend\XmlRpc\Response $response
+ * @return void
+ * @throws \Zend\XmlRpc\Client\Exception\HttpException
+ */
+ public function doRequest($request, $response = null)
+ {
+ $this->lastRequest = $request;
+
+ if (PHP_VERSION_ID < 50600) {
+ iconv_set_encoding('input_encoding', 'UTF-8');
+ iconv_set_encoding('output_encoding', 'UTF-8');
+ iconv_set_encoding('internal_encoding', 'UTF-8');
+ } else {
+ ini_set('default_charset', 'UTF-8');
+ }
+
+ $http = $this->getHttpClient();
+ $httpRequest = $http->getRequest();
+ if ($httpRequest->getUriString() === null) {
+ $http->setUri($this->serverAddress);
+ }
+
+ $headers = $httpRequest->getHeaders();
+ $headers->addHeaders(array(
+ 'Content-Type: text/xml; charset=utf-8',
+ 'Accept: text/xml',
+ ));
+
+ if (!$headers->get('user-agent')) {
+ $headers->addHeaderLine('user-agent', 'Zend_XmlRpc_Client');
+ }
+
+ $xml = $this->lastRequest->__toString();
+ $http->setRawBody($xml);
+ $httpResponse = $http->setMethod('POST')->send();
+
+ if (!$httpResponse->isSuccess()) {
+ /**
+ * Exception thrown when an HTTP error occurs
+ */
+ throw new Client\Exception\HttpException(
+ $httpResponse->getReasonPhrase(),
+ $httpResponse->getStatusCode()
+ );
+ }
+
+ if ($response === null) {
+ $response = new Response();
+ }
+
+ $this->lastResponse = $response;
+ $this->lastResponse->loadXml(trim($httpResponse->getBody()));
+ }
+
+ /**
+ * Send an XML-RPC request to the service (for a specific method)
+ *
+ * @param string $method Name of the method we want to call
+ * @param array $params Array of parameters for the method
+ * @return mixed
+ * @throws \Zend\XmlRpc\Client\Exception\FaultException
+ */
+ public function call($method, $params=array())
+ {
+ if (!$this->skipSystemLookup() && ('system.' != substr($method, 0, 7))) {
+ // Ensure empty array/struct params are cast correctly
+ // If system.* methods are not available, bypass. (ZF-2978)
+ $success = true;
+ try {
+ $signatures = $this->getIntrospector()->getMethodSignature($method);
+ } catch (\Zend\XmlRpc\Exception\ExceptionInterface $e) {
+ $success = false;
+ }
+ if ($success) {
+ $validTypes = array(
+ AbstractValue::XMLRPC_TYPE_ARRAY,
+ AbstractValue::XMLRPC_TYPE_BASE64,
+ AbstractValue::XMLRPC_TYPE_BOOLEAN,
+ AbstractValue::XMLRPC_TYPE_DATETIME,
+ AbstractValue::XMLRPC_TYPE_DOUBLE,
+ AbstractValue::XMLRPC_TYPE_I4,
+ AbstractValue::XMLRPC_TYPE_INTEGER,
+ AbstractValue::XMLRPC_TYPE_NIL,
+ AbstractValue::XMLRPC_TYPE_STRING,
+ AbstractValue::XMLRPC_TYPE_STRUCT,
+ );
+
+ if (!is_array($params)) {
+ $params = array($params);
+ }
+ foreach ($params as $key => $param) {
+ if ($param instanceof AbstractValue) {
+ continue;
+ }
+
+ if (count($signatures) > 1) {
+ $type = AbstractValue::getXmlRpcTypeByValue($param);
+ foreach ($signatures as $signature) {
+ if (!is_array($signature)) {
+ continue;
+ }
+ if (isset($signature['parameters'][$key])) {
+ if ($signature['parameters'][$key] == $type) {
+ break;
+ }
+ }
+ }
+ } elseif (isset($signatures[0]['parameters'][$key])) {
+ $type = $signatures[0]['parameters'][$key];
+ } else {
+ $type = null;
+ }
+
+ if (empty($type) || !in_array($type, $validTypes)) {
+ $type = AbstractValue::AUTO_DETECT_TYPE;
+ }
+
+ $params[$key] = AbstractValue::getXmlRpcValue($param, $type);
+ }
+ }
+ }
+
+ $request = $this->_createRequest($method, $params);
+
+ $this->doRequest($request);
+
+ if ($this->lastResponse->isFault()) {
+ $fault = $this->lastResponse->getFault();
+ /**
+ * Exception thrown when an XML-RPC fault is returned
+ */
+ throw new Client\Exception\FaultException(
+ $fault->getMessage(),
+ $fault->getCode()
+ );
+ }
+
+ return $this->lastResponse->getReturnValue();
+ }
+
+ /**
+ * Create request object
+ *
+ * @param string $method
+ * @param array $params
+ * @return \Zend\XmlRpc\Request
+ */
+ protected function _createRequest($method, $params)
+ {
+ return new Request($method, $params);
+ }
+}
diff --git a/library/Zend/XmlRpc/Client/Exception/ExceptionInterface.php b/library/Zend/XmlRpc/Client/Exception/ExceptionInterface.php
new file mode 100755
index 0000000000..03c09959c7
--- /dev/null
+++ b/library/Zend/XmlRpc/Client/Exception/ExceptionInterface.php
@@ -0,0 +1,19 @@
+system = $client->getProxy('system');
+ }
+
+ /**
+ * Returns the signature for each method on the server,
+ * autodetecting whether system.multicall() is supported and
+ * using it if so.
+ *
+ * @return array
+ */
+ public function getSignatureForEachMethod()
+ {
+ $methods = $this->listMethods();
+
+ try {
+ $signatures = $this->getSignatureForEachMethodByMulticall($methods);
+ } catch (Exception\FaultException $e) {
+ // degrade to looping
+ }
+
+ if (empty($signatures)) {
+ $signatures = $this->getSignatureForEachMethodByLooping($methods);
+ }
+
+ return $signatures;
+ }
+
+ /**
+ * Attempt to get the method signatures in one request via system.multicall().
+ * This is a boxcar feature of XML-RPC and is found on fewer servers. However,
+ * can significantly improve performance if present.
+ *
+ * @param array $methods
+ * @throws Exception\IntrospectException
+ * @return array array(array(return, param, param, param...))
+ */
+ public function getSignatureForEachMethodByMulticall($methods = null)
+ {
+ if ($methods === null) {
+ $methods = $this->listMethods();
+ }
+
+ $multicallParams = array();
+ foreach ($methods as $method) {
+ $multicallParams[] = array('methodName' => 'system.methodSignature',
+ 'params' => array($method));
+ }
+
+ $serverSignatures = $this->system->multicall($multicallParams);
+
+ if (! is_array($serverSignatures)) {
+ $type = gettype($serverSignatures);
+ $error = "Multicall return is malformed. Expected array, got $type";
+ throw new Exception\IntrospectException($error);
+ }
+
+ if (count($serverSignatures) != count($methods)) {
+ $error = 'Bad number of signatures received from multicall';
+ throw new Exception\IntrospectException($error);
+ }
+
+ // Create a new signatures array with the methods name as keys and the signature as value
+ $signatures = array();
+ foreach ($serverSignatures as $i => $signature) {
+ $signatures[$methods[$i]] = $signature;
+ }
+
+ return $signatures;
+ }
+
+ /**
+ * Get the method signatures for every method by
+ * successively calling system.methodSignature
+ *
+ * @param array $methods
+ * @return array
+ */
+ public function getSignatureForEachMethodByLooping($methods = null)
+ {
+ if ($methods === null) {
+ $methods = $this->listMethods();
+ }
+
+ $signatures = array();
+ foreach ($methods as $method) {
+ $signatures[$method] = $this->getMethodSignature($method);
+ }
+
+ return $signatures;
+ }
+
+ /**
+ * Call system.methodSignature() for the given method
+ *
+ * @param array $method
+ * @throws Exception\IntrospectException
+ * @return array array(array(return, param, param, param...))
+ */
+ public function getMethodSignature($method)
+ {
+ $signature = $this->system->methodSignature($method);
+ if (!is_array($signature)) {
+ $error = 'Invalid signature for method "' . $method . '"';
+ throw new Exception\IntrospectException($error);
+ }
+ return $signature;
+ }
+
+ /**
+ * Call system.listMethods()
+ *
+ * @return array array(method, method, method...)
+ */
+ public function listMethods()
+ {
+ return $this->system->listMethods();
+ }
+}
diff --git a/library/Zend/XmlRpc/Client/ServerProxy.php b/library/Zend/XmlRpc/Client/ServerProxy.php
new file mode 100755
index 0000000000..861b615905
--- /dev/null
+++ b/library/Zend/XmlRpc/Client/ServerProxy.php
@@ -0,0 +1,79 @@
+foo->bar->baz()".
+ */
+class ServerProxy
+{
+ /**
+ * @var \Zend\XmlRpc\Client
+ */
+ private $client = null;
+
+ /**
+ * @var string
+ */
+ private $namespace = '';
+
+
+ /**
+ * @var array of \Zend\XmlRpc\Client\ServerProxy
+ */
+ private $cache = array();
+
+
+ /**
+ * Class constructor
+ *
+ * @param \Zend\XmlRpc\Client $client
+ * @param string $namespace
+ */
+ public function __construct(XMLRPCClient $client, $namespace = '')
+ {
+ $this->client = $client;
+ $this->namespace = $namespace;
+ }
+
+
+ /**
+ * Get the next successive namespace
+ *
+ * @param string $namespace
+ * @return \Zend\XmlRpc\Client\ServerProxy
+ */
+ public function __get($namespace)
+ {
+ $namespace = ltrim("$this->namespace.$namespace", '.');
+ if (!isset($this->cache[$namespace])) {
+ $this->cache[$namespace] = new $this($this->client, $namespace);
+ }
+ return $this->cache[$namespace];
+ }
+
+
+ /**
+ * Call a method in this namespace.
+ *
+ * @param string $method
+ * @param array $args
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ $method = ltrim("{$this->namespace}.{$method}", '.');
+ return $this->client->call($method, $args);
+ }
+}
diff --git a/library/Zend/XmlRpc/Exception/BadMethodCallException.php b/library/Zend/XmlRpc/Exception/BadMethodCallException.php
new file mode 100755
index 0000000000..db36424ab9
--- /dev/null
+++ b/library/Zend/XmlRpc/Exception/BadMethodCallException.php
@@ -0,0 +1,14 @@
+ messages
+ * @var array
+ */
+ protected $internal = array(
+ 404 => 'Unknown Error',
+
+ // 610 - 619 reflection errors
+ 610 => 'Invalid method class',
+ 611 => 'Unable to attach function or callback; not callable',
+ 612 => 'Unable to load array; not an array',
+ 613 => 'One or more method records are corrupt or otherwise unusable',
+
+ // 620 - 629 dispatch errors
+ 620 => 'Method does not exist',
+ 621 => 'Error instantiating class to invoke method',
+ 622 => 'Method missing implementation',
+ 623 => 'Calling parameters do not match signature',
+
+ // 630 - 639 request errors
+ 630 => 'Unable to read request',
+ 631 => 'Failed to parse request',
+ 632 => 'Invalid request, no method passed; request must contain a \'methodName\' tag',
+ 633 => 'Param must contain a value',
+ 634 => 'Invalid method name',
+ 635 => 'Invalid XML provided to request',
+ 636 => 'Error creating xmlrpc value',
+
+ // 640 - 649 system.* errors
+ 640 => 'Method does not exist',
+
+ // 650 - 659 response errors
+ 650 => 'Invalid XML provided for response',
+ 651 => 'Failed to parse response',
+ 652 => 'Invalid response',
+ 653 => 'Invalid XMLRPC value in response',
+ );
+
+ /**
+ * Constructor
+ *
+ */
+ public function __construct($code = 404, $message = '')
+ {
+ $this->setCode($code);
+ $code = $this->getCode();
+
+ if (empty($message) && isset($this->internal[$code])) {
+ $message = $this->internal[$code];
+ } elseif (empty($message)) {
+ $message = 'Unknown error';
+ }
+ $this->setMessage($message);
+ }
+
+ /**
+ * Set the fault code
+ *
+ * @param int $code
+ * @return Fault
+ */
+ public function setCode($code)
+ {
+ $this->code = (int) $code;
+ return $this;
+ }
+
+ /**
+ * Return fault code
+ *
+ * @return int
+ */
+ public function getCode()
+ {
+ return $this->code;
+ }
+
+ /**
+ * Retrieve fault message
+ *
+ * @param string
+ * @return Fault
+ */
+ public function setMessage($message)
+ {
+ $this->message = (string) $message;
+ return $this;
+ }
+
+ /**
+ * Retrieve fault message
+ *
+ * @return string
+ */
+ public function getMessage()
+ {
+ return $this->message;
+ }
+
+ /**
+ * Set encoding to use in fault response
+ *
+ * @param string $encoding
+ * @return Fault
+ */
+ public function setEncoding($encoding)
+ {
+ $this->encoding = $encoding;
+ AbstractValue::setEncoding($encoding);
+ return $this;
+ }
+
+ /**
+ * Retrieve current fault encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->encoding;
+ }
+
+ /**
+ * Load an XMLRPC fault from XML
+ *
+ * @param string $fault
+ * @return bool Returns true if successfully loaded fault response, false
+ * if response was not a fault response
+ * @throws Exception\ExceptionInterface if no or faulty XML provided, or if fault
+ * response does not contain either code or message
+ */
+ public function loadXml($fault)
+ {
+ if (!is_string($fault)) {
+ throw new Exception\InvalidArgumentException('Invalid XML provided to fault');
+ }
+
+ $xmlErrorsFlag = libxml_use_internal_errors(true);
+ try {
+ $xml = XmlSecurity::scan($fault);
+ } catch (\ZendXml\Exception\RuntimeException $e) {
+ // Unsecure XML
+ throw new Exception\RuntimeException('Failed to parse XML fault: ' . $e->getMessage(), 500, $e);
+ }
+ if (!$xml instanceof SimpleXMLElement) {
+ $errors = libxml_get_errors();
+ $errors = array_reduce($errors, function ($result, $item) {
+ if (empty($result)) {
+ return $item->message;
+ }
+ return $result . '; ' . $item->message;
+ }, '');
+ libxml_use_internal_errors($xmlErrorsFlag);
+ throw new Exception\InvalidArgumentException('Failed to parse XML fault: ' . $errors, 500);
+ }
+ libxml_use_internal_errors($xmlErrorsFlag);
+
+ // Check for fault
+ if (!$xml->fault) {
+ // Not a fault
+ return false;
+ }
+
+ if (!$xml->fault->value->struct) {
+ // not a proper fault
+ throw new Exception\InvalidArgumentException('Invalid fault structure', 500);
+ }
+
+ $structXml = $xml->fault->value->asXML();
+ $struct = AbstractValue::getXmlRpcValue($structXml, AbstractValue::XML_STRING);
+ $struct = $struct->getValue();
+
+ if (isset($struct['faultCode'])) {
+ $code = $struct['faultCode'];
+ }
+ if (isset($struct['faultString'])) {
+ $message = $struct['faultString'];
+ }
+
+ if (empty($code) && empty($message)) {
+ throw new Exception\InvalidArgumentException('Fault code and string required');
+ }
+
+ if (empty($code)) {
+ $code = '404';
+ }
+
+ if (empty($message)) {
+ if (isset($this->internal[$code])) {
+ $message = $this->internal[$code];
+ } else {
+ $message = 'Unknown Error';
+ }
+ }
+
+ $this->setCode($code);
+ $this->setMessage($message);
+
+ return true;
+ }
+
+ /**
+ * Determine if an XML response is an XMLRPC fault
+ *
+ * @param string $xml
+ * @return bool
+ */
+ public static function isFault($xml)
+ {
+ $fault = new static();
+ try {
+ $isFault = $fault->loadXml($xml);
+ } catch (Exception\ExceptionInterface $e) {
+ $isFault = false;
+ }
+
+ return $isFault;
+ }
+
+ /**
+ * Serialize fault to XML
+ *
+ * @return string
+ */
+ public function saveXml()
+ {
+ // Create fault value
+ $faultStruct = array(
+ 'faultCode' => $this->getCode(),
+ 'faultString' => $this->getMessage()
+ );
+ $value = AbstractValue::getXmlRpcValue($faultStruct);
+
+ $generator = AbstractValue::getGenerator();
+ $generator->openElement('methodResponse')
+ ->openElement('fault');
+ $value->generateXml();
+ $generator->closeElement('fault')
+ ->closeElement('methodResponse');
+
+ return $generator->flush();
+ }
+
+ /**
+ * Return XML fault response
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->saveXML();
+ }
+}
diff --git a/library/Zend/XmlRpc/Generator/AbstractGenerator.php b/library/Zend/XmlRpc/Generator/AbstractGenerator.php
new file mode 100755
index 0000000000..693f026c09
--- /dev/null
+++ b/library/Zend/XmlRpc/Generator/AbstractGenerator.php
@@ -0,0 +1,151 @@
+setEncoding($encoding);
+ $this->_init();
+ }
+
+ /**
+ * Initialize internal objects
+ *
+ * @return void
+ */
+ abstract protected function _init();
+
+ /**
+ * Start XML element
+ *
+ * Method opens a new XML element with an element name and an optional value
+ *
+ * @param string $name XML tag name
+ * @param string $value Optional value of the XML tag
+ * @return AbstractGenerator Fluent interface
+ */
+ public function openElement($name, $value = null)
+ {
+ $this->_openElement($name);
+ if ($value !== null) {
+ $this->_writeTextData($value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * End of an XML element
+ *
+ * Method marks the end of an XML element
+ *
+ * @param string $name XML tag name
+ * @return AbstractGenerator Fluent interface
+ */
+ public function closeElement($name)
+ {
+ $this->_closeElement($name);
+
+ return $this;
+ }
+
+ /**
+ * Return encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->encoding;
+ }
+
+ /**
+ * Set XML encoding
+ *
+ * @param string $encoding
+ * @return AbstractGenerator
+ */
+ public function setEncoding($encoding)
+ {
+ $this->encoding = $encoding;
+ return $this;
+ }
+
+ /**
+ * Returns the XML as a string and flushes all internal buffers
+ *
+ * @return string
+ */
+ public function flush()
+ {
+ $xml = $this->saveXml();
+ $this->_init();
+ return $xml;
+ }
+
+ /**
+ * Returns XML without document declaration
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->stripDeclaration($this->saveXml());
+ }
+
+ /**
+ * Removes XML declaration from a string
+ *
+ * @param string $xml
+ * @return string
+ */
+ public function stripDeclaration($xml)
+ {
+ return preg_replace('/<\?xml version="1.0"( encoding="[^\"]*")?\?>\n/u', '', $xml);
+ }
+
+ /**
+ * Start XML element
+ *
+ * @param string $name XML element name
+ */
+ abstract protected function _openElement($name);
+
+ /**
+ * Write XML text data into the currently opened XML element
+ *
+ * @param string $text
+ */
+ abstract protected function _writeTextData($text);
+
+ /**
+ * End XML element
+ *
+ * @param string $name
+ */
+ abstract protected function _closeElement($name);
+}
diff --git a/library/Zend/XmlRpc/Generator/DomDocument.php b/library/Zend/XmlRpc/Generator/DomDocument.php
new file mode 100755
index 0000000000..47e7a72238
--- /dev/null
+++ b/library/Zend/XmlRpc/Generator/DomDocument.php
@@ -0,0 +1,85 @@
+dom->createElement($name);
+
+ $this->currentElement = $this->currentElement->appendChild($newElement);
+ }
+
+ /**
+ * Write XML text data into the currently opened XML element
+ *
+ * @param string $text
+ */
+ protected function _writeTextData($text)
+ {
+ $this->currentElement->appendChild($this->dom->createTextNode($text));
+ }
+
+ /**
+ * Close a previously opened XML element
+ *
+ * Resets $currentElement to the next parent node in the hierarchy
+ *
+ * @param string $name
+ * @return void
+ */
+ protected function _closeElement($name)
+ {
+ if (isset($this->currentElement->parentNode)) {
+ $this->currentElement = $this->currentElement->parentNode;
+ }
+ }
+
+ /**
+ * Save XML as a string
+ *
+ * @return string
+ */
+ public function saveXml()
+ {
+ return $this->dom->saveXml();
+ }
+
+ /**
+ * Initializes internal objects
+ *
+ * @return void
+ */
+ protected function _init()
+ {
+ $this->dom = new \DOMDocument('1.0', $this->encoding);
+ $this->currentElement = $this->dom;
+ }
+}
diff --git a/library/Zend/XmlRpc/Generator/GeneratorInterface.php b/library/Zend/XmlRpc/Generator/GeneratorInterface.php
new file mode 100755
index 0000000000..ca2af243b2
--- /dev/null
+++ b/library/Zend/XmlRpc/Generator/GeneratorInterface.php
@@ -0,0 +1,32 @@
+xmlWriter = new \XMLWriter();
+ $this->xmlWriter->openMemory();
+ $this->xmlWriter->startDocument('1.0', $this->encoding);
+ }
+
+
+ /**
+ * Open a new XML element
+ *
+ * @param string $name XML element name
+ * @return void
+ */
+ protected function _openElement($name)
+ {
+ $this->xmlWriter->startElement($name);
+ }
+
+ /**
+ * Write XML text data into the currently opened XML element
+ *
+ * @param string $text XML text data
+ * @return void
+ */
+ protected function _writeTextData($text)
+ {
+ $this->xmlWriter->text($text);
+ }
+
+ /**
+ * Close a previously opened XML element
+ *
+ * @param string $name
+ * @return XmlWriter
+ */
+ protected function _closeElement($name)
+ {
+ $this->xmlWriter->endElement();
+
+ return $this;
+ }
+
+ /**
+ * Emit XML document
+ *
+ * @return string
+ */
+ public function saveXml()
+ {
+ return $this->xmlWriter->flush(false);
+ }
+}
diff --git a/library/Zend/XmlRpc/README.md b/library/Zend/XmlRpc/README.md
new file mode 100755
index 0000000000..f003296a43
--- /dev/null
+++ b/library/Zend/XmlRpc/README.md
@@ -0,0 +1,15 @@
+XML-RPC Component from ZF2
+==========================
+
+This is the XML-RPC component for ZF2.
+
+- File issues at https://github.com/zendframework/zf2/issues
+- Create pull requests against https://github.com/zendframework/zf2
+- Documentation is at http://framework.zend.com/docs
+
+LICENSE
+-------
+
+The files in this archive are released under the [Zend Framework
+license](http://framework.zend.com/license), which is a 3-clause BSD license.
+
diff --git a/library/Zend/XmlRpc/Request.php b/library/Zend/XmlRpc/Request.php
new file mode 100755
index 0000000000..82a7eedbe2
--- /dev/null
+++ b/library/Zend/XmlRpc/Request.php
@@ -0,0 +1,444 @@
+setMethod($method);
+ }
+
+ if ($params !== null) {
+ $this->setParams($params);
+ }
+ }
+
+
+ /**
+ * Set encoding to use in request
+ *
+ * @param string $encoding
+ * @return \Zend\XmlRpc\Request
+ */
+ public function setEncoding($encoding)
+ {
+ $this->encoding = $encoding;
+ AbstractValue::setEncoding($encoding);
+ return $this;
+ }
+
+ /**
+ * Retrieve current request encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->encoding;
+ }
+
+ /**
+ * Set method to call
+ *
+ * @param string $method
+ * @return bool Returns true on success, false if method name is invalid
+ */
+ public function setMethod($method)
+ {
+ if (!is_string($method) || !preg_match('/^[a-z0-9_.:\\\\\/]+$/i', $method)) {
+ $this->fault = new Fault(634, 'Invalid method name ("' . $method . '")');
+ $this->fault->setEncoding($this->getEncoding());
+ return false;
+ }
+
+ $this->method = $method;
+ return true;
+ }
+
+ /**
+ * Retrieve call method
+ *
+ * @return string
+ */
+ public function getMethod()
+ {
+ return $this->method;
+ }
+
+ /**
+ * Add a parameter to the parameter stack
+ *
+ * Adds a parameter to the parameter stack, associating it with the type
+ * $type if provided
+ *
+ * @param mixed $value
+ * @param string $type Optional; type hinting
+ * @return void
+ */
+ public function addParam($value, $type = null)
+ {
+ $this->params[] = $value;
+ if (null === $type) {
+ // Detect type if not provided explicitly
+ if ($value instanceof AbstractValue) {
+ $type = $value->getType();
+ } else {
+ $xmlRpcValue = AbstractValue::getXmlRpcValue($value);
+ $type = $xmlRpcValue->getType();
+ }
+ }
+ $this->types[] = $type;
+ $this->xmlRpcParams[] = array('value' => $value, 'type' => $type);
+ }
+
+ /**
+ * Set the parameters array
+ *
+ * If called with a single, array value, that array is used to set the
+ * parameters stack. If called with multiple values or a single non-array
+ * value, the arguments are used to set the parameters stack.
+ *
+ * Best is to call with array of the format, in order to allow type hinting
+ * when creating the XMLRPC values for each parameter:
+ *
+ * $array = array(
+ * array(
+ * 'value' => $value,
+ * 'type' => $type
+ * )[, ... ]
+ * );
+ *
+ *
+ * @access public
+ * @return void
+ */
+ public function setParams()
+ {
+ $argc = func_num_args();
+ $argv = func_get_args();
+ if (0 == $argc) {
+ return;
+ }
+
+ if ((1 == $argc) && is_array($argv[0])) {
+ $params = array();
+ $types = array();
+ $wellFormed = true;
+ foreach ($argv[0] as $arg) {
+ if (!is_array($arg) || !isset($arg['value'])) {
+ $wellFormed = false;
+ break;
+ }
+ $params[] = $arg['value'];
+
+ if (!isset($arg['type'])) {
+ $xmlRpcValue = AbstractValue::getXmlRpcValue($arg['value']);
+ $arg['type'] = $xmlRpcValue->getType();
+ }
+ $types[] = $arg['type'];
+ }
+ if ($wellFormed) {
+ $this->xmlRpcParams = $argv[0];
+ $this->params = $params;
+ $this->types = $types;
+ } else {
+ $this->params = $argv[0];
+ $this->types = array();
+ $xmlRpcParams = array();
+ foreach ($argv[0] as $arg) {
+ if ($arg instanceof AbstractValue) {
+ $type = $arg->getType();
+ } else {
+ $xmlRpcValue = AbstractValue::getXmlRpcValue($arg);
+ $type = $xmlRpcValue->getType();
+ }
+ $xmlRpcParams[] = array('value' => $arg, 'type' => $type);
+ $this->types[] = $type;
+ }
+ $this->xmlRpcParams = $xmlRpcParams;
+ }
+ return;
+ }
+
+ $this->params = $argv;
+ $this->types = array();
+ $xmlRpcParams = array();
+ foreach ($argv as $arg) {
+ if ($arg instanceof AbstractValue) {
+ $type = $arg->getType();
+ } else {
+ $xmlRpcValue = AbstractValue::getXmlRpcValue($arg);
+ $type = $xmlRpcValue->getType();
+ }
+ $xmlRpcParams[] = array('value' => $arg, 'type' => $type);
+ $this->types[] = $type;
+ }
+ $this->xmlRpcParams = $xmlRpcParams;
+ }
+
+ /**
+ * Retrieve the array of parameters
+ *
+ * @return array
+ */
+ public function getParams()
+ {
+ return $this->params;
+ }
+
+ /**
+ * Return parameter types
+ *
+ * @return array
+ */
+ public function getTypes()
+ {
+ return $this->types;
+ }
+
+ /**
+ * Load XML and parse into request components
+ *
+ * @param string $request
+ * @throws Exception\ValueException if invalid XML
+ * @return bool True on success, false if an error occurred.
+ */
+ public function loadXml($request)
+ {
+ if (!is_string($request)) {
+ $this->fault = new Fault(635);
+ $this->fault->setEncoding($this->getEncoding());
+ return false;
+ }
+
+ // @see ZF-12293 - disable external entities for security purposes
+ $loadEntities = libxml_disable_entity_loader(true);
+ $xmlErrorsFlag = libxml_use_internal_errors(true);
+ try {
+ $dom = new DOMDocument;
+ $dom->loadXML($request);
+ foreach ($dom->childNodes as $child) {
+ if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
+ throw new Exception\ValueException(
+ 'Invalid XML: Detected use of illegal DOCTYPE'
+ );
+ }
+ }
+ ErrorHandler::start();
+ $xml = simplexml_import_dom($dom);
+ $error = ErrorHandler::stop();
+ libxml_disable_entity_loader($loadEntities);
+ libxml_use_internal_errors($xmlErrorsFlag);
+ } catch (\Exception $e) {
+ // Not valid XML
+ $this->fault = new Fault(631);
+ $this->fault->setEncoding($this->getEncoding());
+ libxml_disable_entity_loader($loadEntities);
+ libxml_use_internal_errors($xmlErrorsFlag);
+ return false;
+ }
+ if (!$xml instanceof SimpleXMLElement || $error) {
+ // Not valid XML
+ $this->fault = new Fault(631);
+ $this->fault->setEncoding($this->getEncoding());
+ libxml_use_internal_errors($xmlErrorsFlag);
+ return false;
+ }
+
+ // Check for method name
+ if (empty($xml->methodName)) {
+ // Missing method name
+ $this->fault = new Fault(632);
+ $this->fault->setEncoding($this->getEncoding());
+ return false;
+ }
+
+ $this->method = (string) $xml->methodName;
+
+ // Check for parameters
+ if (!empty($xml->params)) {
+ $types = array();
+ $argv = array();
+ foreach ($xml->params->children() as $param) {
+ if (!isset($param->value)) {
+ $this->fault = new Fault(633);
+ $this->fault->setEncoding($this->getEncoding());
+ return false;
+ }
+
+ try {
+ $param = AbstractValue::getXmlRpcValue($param->value, AbstractValue::XML_STRING);
+ $types[] = $param->getType();
+ $argv[] = $param->getValue();
+ } catch (\Exception $e) {
+ $this->fault = new Fault(636);
+ $this->fault->setEncoding($this->getEncoding());
+ return false;
+ }
+ }
+
+ $this->types = $types;
+ $this->params = $argv;
+ }
+
+ $this->xml = $request;
+
+ return true;
+ }
+
+ /**
+ * Does the current request contain errors and should it return a fault
+ * response?
+ *
+ * @return bool
+ */
+ public function isFault()
+ {
+ return $this->fault instanceof Fault;
+ }
+
+ /**
+ * Retrieve the fault response, if any
+ *
+ * @return null|\Zend\XmlRpc\Fault
+ */
+ public function getFault()
+ {
+ return $this->fault;
+ }
+
+ /**
+ * Retrieve method parameters as XMLRPC values
+ *
+ * @return array
+ */
+ protected function _getXmlRpcParams()
+ {
+ $params = array();
+ if (is_array($this->xmlRpcParams)) {
+ foreach ($this->xmlRpcParams as $param) {
+ $value = $param['value'];
+ $type = $param['type'] ?: AbstractValue::AUTO_DETECT_TYPE;
+
+ if (!$value instanceof AbstractValue) {
+ $value = AbstractValue::getXmlRpcValue($value, $type);
+ }
+ $params[] = $value;
+ }
+ }
+
+ return $params;
+ }
+
+ /**
+ * Create XML request
+ *
+ * @return string
+ */
+ public function saveXml()
+ {
+ $args = $this->_getXmlRpcParams();
+ $method = $this->getMethod();
+
+ $generator = AbstractValue::getGenerator();
+ $generator->openElement('methodCall')
+ ->openElement('methodName', $method)
+ ->closeElement('methodName');
+
+ if (is_array($args) && count($args)) {
+ $generator->openElement('params');
+
+ foreach ($args as $arg) {
+ $generator->openElement('param');
+ $arg->generateXml();
+ $generator->closeElement('param');
+ }
+ $generator->closeElement('params');
+ }
+ $generator->closeElement('methodCall');
+
+ return $generator->flush();
+ }
+
+ /**
+ * Return XML request
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->saveXML();
+ }
+}
diff --git a/library/Zend/XmlRpc/Request/Http.php b/library/Zend/XmlRpc/Request/Http.php
new file mode 100755
index 0000000000..d42a8331dd
--- /dev/null
+++ b/library/Zend/XmlRpc/Request/Http.php
@@ -0,0 +1,108 @@
+fault = new Fault(630);
+ return;
+ }
+
+ $this->xml = $xml;
+
+ $this->loadXml($xml);
+ }
+
+ /**
+ * Retrieve the raw XML request
+ *
+ * @return string
+ */
+ public function getRawRequest()
+ {
+ return $this->xml;
+ }
+
+ /**
+ * Get headers
+ *
+ * Gets all headers as key => value pairs and returns them.
+ *
+ * @return array
+ */
+ public function getHeaders()
+ {
+ if (null === $this->headers) {
+ $this->headers = array();
+ foreach ($_SERVER as $key => $value) {
+ if ('HTTP_' == substr($key, 0, 5)) {
+ $header = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))));
+ $this->headers[$header] = $value;
+ }
+ }
+ }
+
+ return $this->headers;
+ }
+
+ /**
+ * Retrieve the full HTTP request, including headers and XML
+ *
+ * @return string
+ */
+ public function getFullRequest()
+ {
+ $request = '';
+ foreach ($this->getHeaders() as $key => $value) {
+ $request .= $key . ': ' . $value . "\n";
+ }
+
+ $request .= $this->xml;
+
+ return $request;
+ }
+}
diff --git a/library/Zend/XmlRpc/Request/Stdin.php b/library/Zend/XmlRpc/Request/Stdin.php
new file mode 100755
index 0000000000..bcf748e95c
--- /dev/null
+++ b/library/Zend/XmlRpc/Request/Stdin.php
@@ -0,0 +1,66 @@
+fault = new Fault(630);
+ return;
+ }
+
+ $xml = '';
+ while (!feof($fh)) {
+ $xml .= fgets($fh);
+ }
+ fclose($fh);
+
+ $this->xml = $xml;
+
+ $this->loadXml($xml);
+ }
+
+ /**
+ * Retrieve the raw XML request
+ *
+ * @return string
+ */
+ public function getRawRequest()
+ {
+ return $this->xml;
+ }
+}
diff --git a/library/Zend/XmlRpc/Response.php b/library/Zend/XmlRpc/Response.php
new file mode 100755
index 0000000000..f8537584e4
--- /dev/null
+++ b/library/Zend/XmlRpc/Response.php
@@ -0,0 +1,224 @@
+setReturnValue($return, $type);
+ }
+
+ /**
+ * Set encoding to use in response
+ *
+ * @param string $encoding
+ * @return \Zend\XmlRpc\Response
+ */
+ public function setEncoding($encoding)
+ {
+ $this->encoding = $encoding;
+ AbstractValue::setEncoding($encoding);
+ return $this;
+ }
+
+ /**
+ * Retrieve current response encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->encoding;
+ }
+
+ /**
+ * Set the return value
+ *
+ * Sets the return value, with optional type hinting if provided.
+ *
+ * @param mixed $value
+ * @param string $type
+ * @return void
+ */
+ public function setReturnValue($value, $type = null)
+ {
+ $this->return = $value;
+ $this->type = (string) $type;
+ }
+
+ /**
+ * Retrieve the return value
+ *
+ * @return mixed
+ */
+ public function getReturnValue()
+ {
+ return $this->return;
+ }
+
+ /**
+ * Retrieve the XMLRPC value for the return value
+ *
+ * @return \Zend\XmlRpc\AbstractValue
+ */
+ protected function _getXmlRpcReturn()
+ {
+ return AbstractValue::getXmlRpcValue($this->return);
+ }
+
+ /**
+ * Is the response a fault response?
+ *
+ * @return bool
+ */
+ public function isFault()
+ {
+ return $this->fault instanceof Fault;
+ }
+
+ /**
+ * Returns the fault, if any.
+ *
+ * @return null|\Zend\XmlRpc\Fault
+ */
+ public function getFault()
+ {
+ return $this->fault;
+ }
+
+ /**
+ * Load a response from an XML response
+ *
+ * Attempts to load a response from an XMLRPC response, autodetecting if it
+ * is a fault response.
+ *
+ * @param string $response
+ * @throws Exception\ValueException if invalid XML
+ * @return bool True if a valid XMLRPC response, false if a fault
+ * response or invalid input
+ */
+ public function loadXml($response)
+ {
+ if (!is_string($response)) {
+ $this->fault = new Fault(650);
+ $this->fault->setEncoding($this->getEncoding());
+ return false;
+ }
+
+ try {
+ $xml = XmlSecurity::scan($response);
+ } catch (\ZendXml\Exception\RuntimeException $e) {
+ $this->fault = new Fault(651);
+ $this->fault->setEncoding($this->getEncoding());
+ return false;
+ }
+
+ if (!empty($xml->fault)) {
+ // fault response
+ $this->fault = new Fault();
+ $this->fault->setEncoding($this->getEncoding());
+ $this->fault->loadXml($response);
+ return false;
+ }
+
+ if (empty($xml->params)) {
+ // Invalid response
+ $this->fault = new Fault(652);
+ $this->fault->setEncoding($this->getEncoding());
+ return false;
+ }
+
+ try {
+ if (!isset($xml->params) || !isset($xml->params->param) || !isset($xml->params->param->value)) {
+ throw new Exception\ValueException('Missing XML-RPC value in XML');
+ }
+ $valueXml = $xml->params->param->value->asXML();
+ $value = AbstractValue::getXmlRpcValue($valueXml, AbstractValue::XML_STRING);
+ } catch (Exception\ValueException $e) {
+ $this->fault = new Fault(653);
+ $this->fault->setEncoding($this->getEncoding());
+ return false;
+ }
+
+ $this->setReturnValue($value->getValue());
+ return true;
+ }
+
+ /**
+ * Return response as XML
+ *
+ * @return string
+ */
+ public function saveXml()
+ {
+ $value = $this->_getXmlRpcReturn();
+ $generator = AbstractValue::getGenerator();
+ $generator->openElement('methodResponse')
+ ->openElement('params')
+ ->openElement('param');
+ $value->generateXml();
+ $generator->closeElement('param')
+ ->closeElement('params')
+ ->closeElement('methodResponse');
+
+ return $generator->flush();
+ }
+
+ /**
+ * Return XML response
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->saveXML();
+ }
+}
diff --git a/library/Zend/XmlRpc/Response/Http.php b/library/Zend/XmlRpc/Response/Http.php
new file mode 100755
index 0000000000..00ae07cc9c
--- /dev/null
+++ b/library/Zend/XmlRpc/Response/Http.php
@@ -0,0 +1,32 @@
+getEncoding()));
+ }
+
+ return parent::__toString();
+ }
+}
diff --git a/library/Zend/XmlRpc/Server.php b/library/Zend/XmlRpc/Server.php
new file mode 100755
index 0000000000..e2cfcea6b3
--- /dev/null
+++ b/library/Zend/XmlRpc/Server.php
@@ -0,0 +1,609 @@
+
+ * use Zend\XmlRpc;
+ *
+ * // Instantiate server
+ * $server = new XmlRpc\Server();
+ *
+ * // Allow some exceptions to report as fault responses:
+ * XmlRpc\Server\Fault::attachFaultException('My\\Exception');
+ * XmlRpc\Server\Fault::attachObserver('My\\Fault\\Observer');
+ *
+ * // Get or build dispatch table:
+ * if (!XmlRpc\Server\Cache::get($filename, $server)) {
+ *
+ * // Attach Some_Service_Class in 'some' namespace
+ * $server->setClass('Some\\Service\\Class', 'some');
+ *
+ * // Attach Another_Service_Class in 'another' namespace
+ * $server->setClass('Another\\Service\\Class', 'another');
+ *
+ * // Create dispatch table cache file
+ * XmlRpc\Server\Cache::save($filename, $server);
+ * }
+ *
+ * $response = $server->handle();
+ * echo $response;
+ *
+ */
+class Server extends AbstractServer
+{
+ /**
+ * Character encoding
+ * @var string
+ */
+ protected $encoding = 'UTF-8';
+
+ /**
+ * Request processed
+ * @var null|Request
+ */
+ protected $request = null;
+
+ /**
+ * Class to use for responses; defaults to {@link Response\Http}
+ * @var string
+ */
+ protected $responseClass = 'Zend\XmlRpc\Response\Http';
+
+ /**
+ * Dispatch table of name => method pairs
+ * @var Definition
+ */
+ protected $table;
+
+ /**
+ * PHP types => XML-RPC types
+ * @var array
+ */
+ protected $typeMap = array(
+ 'i4' => 'i4',
+ 'int' => 'int',
+ 'integer' => 'int',
+ 'i8' => 'i8',
+ 'ex:i8' => 'i8',
+ 'double' => 'double',
+ 'float' => 'double',
+ 'real' => 'double',
+ 'boolean' => 'boolean',
+ 'bool' => 'boolean',
+ 'true' => 'boolean',
+ 'false' => 'boolean',
+ 'string' => 'string',
+ 'str' => 'string',
+ 'base64' => 'base64',
+ 'dateTime.iso8601' => 'dateTime.iso8601',
+ 'date' => 'dateTime.iso8601',
+ 'time' => 'dateTime.iso8601',
+ 'DateTime' => 'dateTime.iso8601',
+ 'array' => 'array',
+ 'struct' => 'struct',
+ 'null' => 'nil',
+ 'nil' => 'nil',
+ 'ex:nil' => 'nil',
+ 'void' => 'void',
+ 'mixed' => 'struct',
+ );
+
+ /**
+ * Send arguments to all methods or just constructor?
+ *
+ * @var bool
+ */
+ protected $sendArgumentsToAllMethods = true;
+
+ /**
+ * Flag: whether or not {@link handle()} should return a response instead
+ * of automatically emitting it.
+ * @var bool
+ */
+ protected $returnResponse = false;
+
+ /**
+ * Last response results.
+ * @var Response
+ */
+ protected $response;
+
+ /**
+ * Constructor
+ *
+ * Creates system.* methods.
+ *
+ */
+ public function __construct()
+ {
+ $this->table = new Definition();
+ $this->registerSystemMethods();
+ }
+
+ /**
+ * Proxy calls to system object
+ *
+ * @param string $method
+ * @param array $params
+ * @return mixed
+ * @throws Server\Exception\BadMethodCallException
+ */
+ public function __call($method, $params)
+ {
+ $system = $this->getSystem();
+ if (!method_exists($system, $method)) {
+ throw new Server\Exception\BadMethodCallException('Unknown instance method called on server: ' . $method);
+ }
+ return call_user_func_array(array($system, $method), $params);
+ }
+
+ /**
+ * Attach a callback as an XMLRPC method
+ *
+ * Attaches a callback as an XMLRPC method, prefixing the XMLRPC method name
+ * with $namespace, if provided. Reflection is done on the callback's
+ * docblock to create the methodHelp for the XMLRPC method.
+ *
+ * Additional arguments to pass to the function at dispatch may be passed;
+ * any arguments following the namespace will be aggregated and passed at
+ * dispatch time.
+ *
+ * @param string|array|callable $function Valid callback
+ * @param string $namespace Optional namespace prefix
+ * @throws Server\Exception\InvalidArgumentException
+ * @return void
+ */
+ public function addFunction($function, $namespace = '')
+ {
+ if (!is_string($function) && !is_array($function)) {
+ throw new Server\Exception\InvalidArgumentException('Unable to attach function; invalid', 611);
+ }
+
+ $argv = null;
+ if (2 < func_num_args()) {
+ $argv = func_get_args();
+ $argv = array_slice($argv, 2);
+ }
+
+ $function = (array) $function;
+ foreach ($function as $func) {
+ if (!is_string($func) || !function_exists($func)) {
+ throw new Server\Exception\InvalidArgumentException('Unable to attach function; invalid', 611);
+ }
+ $reflection = Reflection::reflectFunction($func, $argv, $namespace);
+ $this->_buildSignature($reflection);
+ }
+ }
+
+ /**
+ * Attach class methods as XMLRPC method handlers
+ *
+ * $class may be either a class name or an object. Reflection is done on the
+ * class or object to determine the available public methods, and each is
+ * attached to the server as an available method; if a $namespace has been
+ * provided, that namespace is used to prefix the XMLRPC method names.
+ *
+ * Any additional arguments beyond $namespace will be passed to a method at
+ * invocation.
+ *
+ * @param string|object $class
+ * @param string $namespace Optional
+ * @param mixed $argv Optional arguments to pass to methods
+ * @return void
+ * @throws Server\Exception\InvalidArgumentException on invalid input
+ */
+ public function setClass($class, $namespace = '', $argv = null)
+ {
+ if (is_string($class) && !class_exists($class)) {
+ throw new Server\Exception\InvalidArgumentException('Invalid method class', 610);
+ }
+
+ if (2 < func_num_args()) {
+ $argv = func_get_args();
+ $argv = array_slice($argv, 2);
+ }
+
+ $dispatchable = Reflection::reflectClass($class, $argv, $namespace);
+ foreach ($dispatchable->getMethods() as $reflection) {
+ $this->_buildSignature($reflection, $class);
+ }
+ }
+
+ /**
+ * Raise an xmlrpc server fault
+ *
+ * @param string|\Exception $fault
+ * @param int $code
+ * @return Server\Fault
+ */
+ public function fault($fault = null, $code = 404)
+ {
+ if (!$fault instanceof \Exception) {
+ $fault = (string) $fault;
+ if (empty($fault)) {
+ $fault = 'Unknown Error';
+ }
+ $fault = new Server\Exception\RuntimeException($fault, $code);
+ }
+
+ return Server\Fault::getInstance($fault);
+ }
+
+ /**
+ * Set return response flag
+ *
+ * If true, {@link handle()} will return the response instead of
+ * automatically sending it back to the requesting client.
+ *
+ * The response is always available via {@link getResponse()}.
+ *
+ * @param bool $flag
+ * @return Server
+ */
+ public function setReturnResponse($flag = true)
+ {
+ $this->returnResponse = ($flag) ? true : false;
+ return $this;
+ }
+
+ /**
+ * Retrieve return response flag
+ *
+ * @return bool
+ */
+ public function getReturnResponse()
+ {
+ return $this->returnResponse;
+ }
+
+ /**
+ * Handle an xmlrpc call
+ *
+ * @param Request $request Optional
+ * @return Response|Fault
+ */
+ public function handle($request = false)
+ {
+ // Get request
+ if ((!$request || !$request instanceof Request)
+ && (null === ($request = $this->getRequest()))
+ ) {
+ $request = new Request\Http();
+ $request->setEncoding($this->getEncoding());
+ }
+
+ $this->setRequest($request);
+
+ if ($request->isFault()) {
+ $response = $request->getFault();
+ } else {
+ try {
+ $response = $this->handleRequest($request);
+ } catch (\Exception $e) {
+ $response = $this->fault($e);
+ }
+ }
+
+ // Set output encoding
+ $response->setEncoding($this->getEncoding());
+ $this->response = $response;
+
+ if (!$this->returnResponse) {
+ echo $response;
+ return;
+ }
+
+ return $response;
+ }
+
+ /**
+ * Load methods as returned from {@link getFunctions}
+ *
+ * Typically, you will not use this method; it will be called using the
+ * results pulled from {@link Zend\XmlRpc\Server\Cache::get()}.
+ *
+ * @param array|Definition $definition
+ * @return void
+ * @throws Server\Exception\InvalidArgumentException on invalid input
+ */
+ public function loadFunctions($definition)
+ {
+ if (!is_array($definition) && (!$definition instanceof Definition)) {
+ if (is_object($definition)) {
+ $type = get_class($definition);
+ } else {
+ $type = gettype($definition);
+ }
+ throw new Server\Exception\InvalidArgumentException('Unable to load server definition; must be an array or Zend\Server\Definition, received ' . $type, 612);
+ }
+
+ $this->table->clearMethods();
+ $this->registerSystemMethods();
+
+ if ($definition instanceof Definition) {
+ $definition = $definition->getMethods();
+ }
+
+ foreach ($definition as $key => $method) {
+ if ('system.' == substr($key, 0, 7)) {
+ continue;
+ }
+ $this->table->addMethod($method, $key);
+ }
+ }
+
+ /**
+ * Set encoding
+ *
+ * @param string $encoding
+ * @return Server
+ */
+ public function setEncoding($encoding)
+ {
+ $this->encoding = $encoding;
+ AbstractValue::setEncoding($encoding);
+ return $this;
+ }
+
+ /**
+ * Retrieve current encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->encoding;
+ }
+
+ /**
+ * Do nothing; persistence is handled via {@link Zend\XmlRpc\Server\Cache}
+ *
+ * @param mixed $mode
+ * @return void
+ */
+ public function setPersistence($mode)
+ {
+ }
+
+ /**
+ * Set the request object
+ *
+ * @param string|Request $request
+ * @return Server
+ * @throws Server\Exception\InvalidArgumentException on invalid request class or object
+ */
+ public function setRequest($request)
+ {
+ if (is_string($request) && class_exists($request)) {
+ $request = new $request();
+ if (!$request instanceof Request) {
+ throw new Server\Exception\InvalidArgumentException('Invalid request class');
+ }
+ $request->setEncoding($this->getEncoding());
+ } elseif (!$request instanceof Request) {
+ throw new Server\Exception\InvalidArgumentException('Invalid request object');
+ }
+
+ $this->request = $request;
+ return $this;
+ }
+
+ /**
+ * Return currently registered request object
+ *
+ * @return null|Request
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ /**
+ * Last response.
+ *
+ * @return Response
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ /**
+ * Set the class to use for the response
+ *
+ * @param string $class
+ * @throws Server\Exception\InvalidArgumentException if invalid response class
+ * @return bool True if class was set, false if not
+ */
+ public function setResponseClass($class)
+ {
+ if (!class_exists($class) || !static::isSubclassOf($class, 'Zend\XmlRpc\Response')) {
+ throw new Server\Exception\InvalidArgumentException('Invalid response class');
+ }
+ $this->responseClass = $class;
+ return true;
+ }
+
+ /**
+ * Retrieve current response class
+ *
+ * @return string
+ */
+ public function getResponseClass()
+ {
+ return $this->responseClass;
+ }
+
+ /**
+ * Retrieve dispatch table
+ *
+ * @return array
+ */
+ public function getDispatchTable()
+ {
+ return $this->table;
+ }
+
+ /**
+ * Returns a list of registered methods
+ *
+ * Returns an array of dispatchables (Zend\Server\Reflection\ReflectionFunction,
+ * ReflectionMethod, and ReflectionClass items).
+ *
+ * @return array
+ */
+ public function getFunctions()
+ {
+ return $this->table->toArray();
+ }
+
+ /**
+ * Retrieve system object
+ *
+ * @return Server\System
+ */
+ public function getSystem()
+ {
+ return $this->system;
+ }
+
+ /**
+ * Send arguments to all methods?
+ *
+ * If setClass() is used to add classes to the server, this flag defined
+ * how to handle arguments. If set to true, all methods including constructor
+ * will receive the arguments. If set to false, only constructor will receive the
+ * arguments
+ */
+ public function sendArgumentsToAllMethods($flag = null)
+ {
+ if ($flag === null) {
+ return $this->sendArgumentsToAllMethods;
+ }
+
+ $this->sendArgumentsToAllMethods = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Map PHP type to XML-RPC type
+ *
+ * @param string $type
+ * @return string
+ */
+ protected function _fixType($type)
+ {
+ if (isset($this->typeMap[$type])) {
+ return $this->typeMap[$type];
+ }
+ return 'void';
+ }
+
+ /**
+ * Handle an xmlrpc call (actual work)
+ *
+ * @param Request $request
+ * @return Response
+ * @throws Server\Exception\RuntimeException
+ * Zend\XmlRpc\Server\Exceptions are thrown for internal errors; otherwise,
+ * any other exception may be thrown by the callback
+ */
+ protected function handleRequest(Request $request)
+ {
+ $method = $request->getMethod();
+
+ // Check for valid method
+ if (!$this->table->hasMethod($method)) {
+ throw new Server\Exception\RuntimeException('Method "' . $method . '" does not exist', 620);
+ }
+
+ $info = $this->table->getMethod($method);
+ $params = $request->getParams();
+ $argv = $info->getInvokeArguments();
+ if (0 < count($argv) and $this->sendArgumentsToAllMethods()) {
+ $params = array_merge($params, $argv);
+ }
+
+ // Check calling parameters against signatures
+ $matched = false;
+ $sigCalled = $request->getTypes();
+
+ $sigLength = count($sigCalled);
+ $paramsLen = count($params);
+ if ($sigLength < $paramsLen) {
+ for ($i = $sigLength; $i < $paramsLen; ++$i) {
+ $xmlRpcValue = AbstractValue::getXmlRpcValue($params[$i]);
+ $sigCalled[] = $xmlRpcValue->getType();
+ }
+ }
+
+ $signatures = $info->getPrototypes();
+ foreach ($signatures as $signature) {
+ $sigParams = $signature->getParameters();
+ if ($sigCalled === $sigParams) {
+ $matched = true;
+ break;
+ }
+ }
+ if (!$matched) {
+ throw new Server\Exception\RuntimeException('Calling parameters do not match signature', 623);
+ }
+
+ $return = $this->_dispatch($info, $params);
+ $responseClass = $this->getResponseClass();
+ return new $responseClass($return);
+ }
+
+ /**
+ * Register system methods with the server
+ *
+ * @return void
+ */
+ protected function registerSystemMethods()
+ {
+ $system = new Server\System($this);
+ $this->system = $system;
+ $this->setClass($system, 'system');
+ }
+
+ /**
+ * Checks if the object has this class as one of its parents
+ *
+ * @see https://bugs.php.net/bug.php?id=53727
+ * @see https://github.com/zendframework/zf2/pull/1807
+ *
+ * @param string $className
+ * @param string $type
+ * @return bool
+ */
+ protected static function isSubclassOf($className, $type)
+ {
+ if (is_subclass_of($className, $type)) {
+ return true;
+ }
+ if (PHP_VERSION_ID >= 50307) {
+ return false;
+ }
+ if (!interface_exists($type)) {
+ return false;
+ }
+ $r = new ReflectionClass($className);
+ return $r->implementsInterface($type);
+ }
+}
diff --git a/library/Zend/XmlRpc/Server/Cache.php b/library/Zend/XmlRpc/Server/Cache.php
new file mode 100755
index 0000000000..f4e643ea6d
--- /dev/null
+++ b/library/Zend/XmlRpc/Server/Cache.php
@@ -0,0 +1,26 @@
+ true);
+
+ /**
+ * @var array Array of fault observers
+ */
+ protected static $observers = array();
+
+ /**
+ * Constructor
+ *
+ * @param \Exception $e
+ * @return Fault
+ */
+ public function __construct(\Exception $e)
+ {
+ $this->exception = $e;
+ $code = 404;
+ $message = 'Unknown error';
+
+ foreach (array_keys(static::$faultExceptionClasses) as $class) {
+ if ($e instanceof $class) {
+ $code = $e->getCode();
+ $message = $e->getMessage();
+ break;
+ }
+ }
+
+ parent::__construct($code, $message);
+
+ // Notify exception observers, if present
+ if (!empty(static::$observers)) {
+ foreach (array_keys(static::$observers) as $observer) {
+ $observer::observe($this);
+ }
+ }
+ }
+
+ /**
+ * Return Zend\XmlRpc\Server\Fault instance
+ *
+ * @param \Exception $e
+ * @return Fault
+ */
+ public static function getInstance(\Exception $e)
+ {
+ return new static($e);
+ }
+
+ /**
+ * Attach valid exceptions that can be used to define xmlrpc faults
+ *
+ * @param string|array $classes Class name or array of class names
+ * @return void
+ */
+ public static function attachFaultException($classes)
+ {
+ if (!is_array($classes)) {
+ $classes = (array) $classes;
+ }
+
+ foreach ($classes as $class) {
+ if (is_string($class) && class_exists($class)) {
+ static::$faultExceptionClasses[$class] = true;
+ }
+ }
+ }
+
+ /**
+ * Detach fault exception classes
+ *
+ * @param string|array $classes Class name or array of class names
+ * @return void
+ */
+ public static function detachFaultException($classes)
+ {
+ if (!is_array($classes)) {
+ $classes = (array) $classes;
+ }
+
+ foreach ($classes as $class) {
+ if (is_string($class) && isset(static::$faultExceptionClasses[$class])) {
+ unset(static::$faultExceptionClasses[$class]);
+ }
+ }
+ }
+
+ /**
+ * Attach an observer class
+ *
+ * Allows observation of xmlrpc server faults, thus allowing logging or mail
+ * notification of fault responses on the xmlrpc server.
+ *
+ * Expects a valid class name; that class must have a public static method
+ * 'observe' that accepts an exception as its sole argument.
+ *
+ * @param string $class
+ * @return bool
+ */
+ public static function attachObserver($class)
+ {
+ if (!is_string($class)
+ || !class_exists($class)
+ || !is_callable(array($class, 'observe'))
+ ) {
+ return false;
+ }
+
+ if (!isset(static::$observers[$class])) {
+ static::$observers[$class] = true;
+ }
+
+ return true;
+ }
+
+ /**
+ * Detach an observer
+ *
+ * @param string $class
+ * @return bool
+ */
+ public static function detachObserver($class)
+ {
+ if (!isset(static::$observers[$class])) {
+ return false;
+ }
+
+ unset(static::$observers[$class]);
+ return true;
+ }
+
+ /**
+ * Retrieve the exception
+ *
+ * @access public
+ * @return \Exception
+ */
+ public function getException()
+ {
+ return $this->exception;
+ }
+}
diff --git a/library/Zend/XmlRpc/Server/System.php b/library/Zend/XmlRpc/Server/System.php
new file mode 100755
index 0000000000..8a57838b1c
--- /dev/null
+++ b/library/Zend/XmlRpc/Server/System.php
@@ -0,0 +1,144 @@
+server = $server;
+ }
+
+ /**
+ * List all available XMLRPC methods
+ *
+ * Returns an array of methods.
+ *
+ * @return array
+ */
+ public function listMethods()
+ {
+ $table = $this->server->getDispatchTable()->getMethods();
+ return array_keys($table);
+ }
+
+ /**
+ * Display help message for an XMLRPC method
+ *
+ * @param string $method
+ * @throws Exception\InvalidArgumentException
+ * @return string
+ */
+ public function methodHelp($method)
+ {
+ $table = $this->server->getDispatchTable();
+ if (!$table->hasMethod($method)) {
+ throw new Exception\InvalidArgumentException('Method "' . $method . '" does not exist', 640);
+ }
+
+ return $table->getMethod($method)->getMethodHelp();
+ }
+
+ /**
+ * Return a method signature
+ *
+ * @param string $method
+ * @throws Exception\InvalidArgumentException
+ * @return array
+ */
+ public function methodSignature($method)
+ {
+ $table = $this->server->getDispatchTable();
+ if (!$table->hasMethod($method)) {
+ throw new Exception\InvalidArgumentException('Method "' . $method . '" does not exist', 640);
+ }
+ $method = $table->getMethod($method)->toArray();
+ return $method['prototypes'];
+ }
+
+ /**
+ * Multicall - boxcar feature of XML-RPC for calling multiple methods
+ * in a single request.
+ *
+ * Expects an array of structs representing method calls, each element
+ * having the keys:
+ * - methodName
+ * - params
+ *
+ * Returns an array of responses, one for each method called, with the value
+ * returned by the method. If an error occurs for a given method, returns a
+ * struct with a fault response.
+ *
+ * @see http://www.xmlrpc.com/discuss/msgReader$1208
+ * @param array $methods
+ * @return array
+ */
+ public function multicall($methods)
+ {
+ $responses = array();
+ foreach ($methods as $method) {
+ $fault = false;
+ if (!is_array($method)) {
+ $fault = $this->server->fault('system.multicall expects each method to be a struct', 601);
+ } elseif (!isset($method['methodName'])) {
+ $fault = $this->server->fault('Missing methodName: ' . var_export($methods, 1), 602);
+ } elseif (!isset($method['params'])) {
+ $fault = $this->server->fault('Missing params', 603);
+ } elseif (!is_array($method['params'])) {
+ $fault = $this->server->fault('Params must be an array', 604);
+ } else {
+ if ('system.multicall' == $method['methodName']) {
+ // don't allow recursive calls to multicall
+ $fault = $this->server->fault('Recursive system.multicall forbidden', 605);
+ }
+ }
+
+ if (!$fault) {
+ try {
+ $request = new \Zend\XmlRpc\Request();
+ $request->setMethod($method['methodName']);
+ $request->setParams($method['params']);
+ $response = $this->server->handle($request);
+ if ($response instanceof \Zend\XmlRpc\Fault
+ || $response->isFault()
+ ) {
+ $fault = $response;
+ } else {
+ $responses[] = $response->getReturnValue();
+ }
+ } catch (\Exception $e) {
+ $fault = $this->server->fault($e);
+ }
+ }
+
+ if ($fault) {
+ $responses[] = array(
+ 'faultCode' => $fault->getCode(),
+ 'faultString' => $fault->getMessage()
+ );
+ }
+ }
+
+ return $responses;
+ }
+}
diff --git a/library/Zend/XmlRpc/Value/AbstractCollection.php b/library/Zend/XmlRpc/Value/AbstractCollection.php
new file mode 100755
index 0000000000..ed4f8193f3
--- /dev/null
+++ b/library/Zend/XmlRpc/Value/AbstractCollection.php
@@ -0,0 +1,48 @@
+ $value) {
+ // If the elements of the given array are not Zend\XmlRpc\Value objects,
+ // we need to convert them as such (using auto-detection from PHP value)
+ if (!$value instanceof parent) {
+ $value = static::getXmlRpcValue($value, self::AUTO_DETECT_TYPE);
+ }
+ $this->value[$key] = $value;
+ }
+ }
+
+
+ /**
+ * Return the value of this object, convert the XML-RPC native collection values into a PHP array
+ *
+ * @return array
+ */
+ public function getValue()
+ {
+ $values = (array) $this->value;
+ foreach ($values as $key => $value) {
+ $values[$key] = $value->getValue();
+ }
+ return $values;
+ }
+}
diff --git a/library/Zend/XmlRpc/Value/AbstractScalar.php b/library/Zend/XmlRpc/Value/AbstractScalar.php
new file mode 100755
index 0000000000..3e7b8eacb8
--- /dev/null
+++ b/library/Zend/XmlRpc/Value/AbstractScalar.php
@@ -0,0 +1,30 @@
+getGenerator();
+
+ $generator->openElement('value')
+ ->openElement($this->type, $this->value)
+ ->closeElement($this->type)
+ ->closeElement('value');
+ }
+}
diff --git a/library/Zend/XmlRpc/Value/ArrayValue.php b/library/Zend/XmlRpc/Value/ArrayValue.php
new file mode 100755
index 0000000000..99d77437b3
--- /dev/null
+++ b/library/Zend/XmlRpc/Value/ArrayValue.php
@@ -0,0 +1,47 @@
+type = self::XMLRPC_TYPE_ARRAY;
+ parent::__construct($value);
+ }
+
+
+ /**
+ * Generate the XML code that represent an array native MXL-RPC value
+ *
+ * @return void
+ */
+ protected function _generateXml()
+ {
+ $generator = $this->getGenerator();
+ $generator->openElement('value')
+ ->openElement('array')
+ ->openElement('data');
+
+ if (is_array($this->value)) {
+ foreach ($this->value as $val) {
+ $val->generateXml();
+ }
+ }
+ $generator->closeElement('data')
+ ->closeElement('array')
+ ->closeElement('value');
+ }
+}
diff --git a/library/Zend/XmlRpc/Value/Base64.php b/library/Zend/XmlRpc/Value/Base64.php
new file mode 100755
index 0000000000..9ba44aceab
--- /dev/null
+++ b/library/Zend/XmlRpc/Value/Base64.php
@@ -0,0 +1,42 @@
+type = self::XMLRPC_TYPE_BASE64;
+
+ $value = (string) $value; // Make sure this value is string
+ if (!$alreadyEncoded) {
+ $value = base64_encode($value); // We encode it in base64
+ }
+ $this->value = $value;
+ }
+
+ /**
+ * Return the value of this object, convert the XML-RPC native base64 value into a PHP string
+ * We return this value decoded (a normal string)
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return base64_decode($this->value);
+ }
+}
diff --git a/library/Zend/XmlRpc/Value/BigInteger.php b/library/Zend/XmlRpc/Value/BigInteger.php
new file mode 100755
index 0000000000..e85ef95371
--- /dev/null
+++ b/library/Zend/XmlRpc/Value/BigInteger.php
@@ -0,0 +1,34 @@
+value = BigIntegerMath::factory()->init($value, 10);
+ $this->type = self::XMLRPC_TYPE_I8;
+ }
+
+ /**
+ * Return bigint value object
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+}
diff --git a/library/Zend/XmlRpc/Value/Boolean.php b/library/Zend/XmlRpc/Value/Boolean.php
new file mode 100755
index 0000000000..5ec6b7932b
--- /dev/null
+++ b/library/Zend/XmlRpc/Value/Boolean.php
@@ -0,0 +1,37 @@
+type = self::XMLRPC_TYPE_BOOLEAN;
+ // Make sure the value is boolean and then convert it into an integer
+ // The double conversion is because a bug in the ZendOptimizer in PHP version 5.0.4
+ $this->value = (int)(bool) $value;
+ }
+
+ /**
+ * Return the value of this object, convert the XML-RPC native boolean value into a PHP boolean
+ *
+ * @return bool
+ */
+ public function getValue()
+ {
+ return (bool) $this->value;
+ }
+}
diff --git a/library/Zend/XmlRpc/Value/DateTime.php b/library/Zend/XmlRpc/Value/DateTime.php
new file mode 100755
index 0000000000..9ec7253e6d
--- /dev/null
+++ b/library/Zend/XmlRpc/Value/DateTime.php
@@ -0,0 +1,67 @@
+type = self::XMLRPC_TYPE_DATETIME;
+
+ if ($value instanceof \DateTime) {
+ $this->value = $value->format($this->phpFormatString);
+ } elseif (is_numeric($value)) { // The value is numeric, we make sure it is an integer
+ $this->value = date($this->phpFormatString, (int) $value);
+ } else {
+ try {
+ $dateTime = new \DateTime($value);
+ } catch (\Exception $e) {
+ throw new Exception\ValueException($e->getMessage(), $e->getCode(), $e);
+ }
+
+ $this->value = $dateTime->format($this->phpFormatString); // Convert the DateTime to iso8601 format
+ }
+ }
+
+ /**
+ * Return the value of this object as iso8601 dateTime value
+ *
+ * @return int As a Unix timestamp
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+}
diff --git a/library/Zend/XmlRpc/Value/Double.php b/library/Zend/XmlRpc/Value/Double.php
new file mode 100755
index 0000000000..722012000f
--- /dev/null
+++ b/library/Zend/XmlRpc/Value/Double.php
@@ -0,0 +1,36 @@
+type = self::XMLRPC_TYPE_DOUBLE;
+ $precision = (int) ini_get('precision');
+ $formatString = '%1.' . $precision . 'F';
+ $this->value = rtrim(sprintf($formatString, (float) $value), '0');
+ }
+
+ /**
+ * Return the value of this object, convert the XML-RPC native double value into a PHP float
+ *
+ * @return float
+ */
+ public function getValue()
+ {
+ return (float) $this->value;
+ }
+}
diff --git a/library/Zend/XmlRpc/Value/Integer.php b/library/Zend/XmlRpc/Value/Integer.php
new file mode 100755
index 0000000000..40d9386649
--- /dev/null
+++ b/library/Zend/XmlRpc/Value/Integer.php
@@ -0,0 +1,41 @@
+ PHP_INT_MAX) {
+ throw new Exception\ValueException('Overlong integer given');
+ }
+
+ $this->type = self::XMLRPC_TYPE_INTEGER;
+ $this->value = (int) $value; // Make sure this value is integer
+ }
+
+ /**
+ * Return the value of this object, convert the XML-RPC native integer value into a PHP integer
+ *
+ * @return int
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+}
diff --git a/library/Zend/XmlRpc/Value/Nil.php b/library/Zend/XmlRpc/Value/Nil.php
new file mode 100755
index 0000000000..49f3c7511c
--- /dev/null
+++ b/library/Zend/XmlRpc/Value/Nil.php
@@ -0,0 +1,33 @@
+type = self::XMLRPC_TYPE_NIL;
+ $this->value = null;
+ }
+
+ /**
+ * Return the value of this object, convert the XML-RPC native nill value into a PHP NULL
+ *
+ * @return null
+ */
+ public function getValue()
+ {
+ return null;
+ }
+}
diff --git a/library/Zend/XmlRpc/Value/String.php b/library/Zend/XmlRpc/Value/String.php
new file mode 100755
index 0000000000..66fa441b00
--- /dev/null
+++ b/library/Zend/XmlRpc/Value/String.php
@@ -0,0 +1,36 @@
+type = self::XMLRPC_TYPE_STRING;
+
+ // Make sure this value is string and all XML characters are encoded
+ $this->value = (string) $value;
+ }
+
+ /**
+ * Return the value of this object, convert the XML-RPC native string value into a PHP string
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return (string) $this->value;
+ }
+}
diff --git a/library/Zend/XmlRpc/Value/Struct.php b/library/Zend/XmlRpc/Value/Struct.php
new file mode 100755
index 0000000000..99d55bb847
--- /dev/null
+++ b/library/Zend/XmlRpc/Value/Struct.php
@@ -0,0 +1,49 @@
+type = self::XMLRPC_TYPE_STRUCT;
+ parent::__construct($value);
+ }
+
+
+ /**
+ * Generate the XML code that represent struct native MXL-RPC value
+ *
+ * @return void
+ */
+ protected function _generateXML()
+ {
+ $generator = $this->getGenerator();
+ $generator->openElement('value')
+ ->openElement('struct');
+
+ if (is_array($this->value)) {
+ foreach ($this->value as $name => $val) {
+ $generator->openElement('member')
+ ->openElement('name', $name)
+ ->closeElement('name');
+ $val->generateXml();
+ $generator->closeElement('member');
+ }
+ }
+ $generator->closeElement('struct')
+ ->closeElement('value');
+ }
+}
diff --git a/library/Zend/XmlRpc/composer.json b/library/Zend/XmlRpc/composer.json
new file mode 100755
index 0000000000..1520a41e41
--- /dev/null
+++ b/library/Zend/XmlRpc/composer.json
@@ -0,0 +1,30 @@
+{
+ "name": "zendframework/zend-xmlrpc",
+ "description": " ",
+ "license": "BSD-3-Clause",
+ "keywords": [
+ "zf2",
+ "xmlrpc"
+ ],
+ "homepage": "https://github.com/zendframework/zf2",
+ "autoload": {
+ "psr-0": {
+ "Zend\\XmlRpc\\": ""
+ }
+ },
+ "target-dir": "Zend/XmlRpc",
+ "require": {
+ "php": ">=5.3.23",
+ "zendframework/zend-http": "self.version",
+ "zendframework/zend-math": "self.version",
+ "zendframework/zend-server": "self.version",
+ "zendframework/zend-stdlib": "self.version",
+ "zendframework/zendxml": "1.*"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev",
+ "dev-develop": "2.4-dev"
+ }
+ }
+}