diff --git a/Makefile b/Makefile
index 5fca707b98..917a515e4c 100644
--- a/Makefile
+++ b/Makefile
@@ -733,6 +733,7 @@ TEST_WEB_80 := \
test_web_symfony_52 \
test_web_wordpress_59 \
test_web_yii_2 \
+ test_web_zend_1_21 \
test_web_custom
TEST_INTEGRATIONS_81 := \
@@ -757,7 +758,8 @@ TEST_WEB_81 := \
test_web_slim_4 \
test_web_symfony_52 \
test_web_wordpress_59 \
- test_web_custom
+ test_web_custom \
+ test_web_zend_1_21
# test_web_yii_2 \
TEST_INTEGRATIONS_82 := \
@@ -785,7 +787,8 @@ TEST_WEB_82 := \
test_web_symfony_52 \
test_web_symfony_62 \
test_web_wordpress_59 \
- test_web_custom
+ test_web_custom \
+ test_web_zend_1_21
# test_web_yii_2 \
FILTER := .
@@ -1015,6 +1018,8 @@ test_web_nette_30: global_test_run_dependencies
$(call run_tests,tests/Integrations/Nette/V3_0)
test_web_zend_1: global_test_run_dependencies
$(call run_tests,tests/Integrations/ZendFramework/V1)
+test_web_zend_1_21: global_test_run_dependencies
+ $(call run_tests,tests/Integrations/ZendFramework/V1_21)
test_web_custom: global_test_run_dependencies
$(COMPOSER) --working-dir=tests/Frameworks/Custom/Version_Autoloaded update
$(call run_tests,--testsuite=custom-framework-autoloading-test)
diff --git a/ext/integrations/integrations.c b/ext/integrations/integrations.c
index 4cf3212f2f..7b3ec6d25d 100644
--- a/ext/integrations/integrations.c
+++ b/ext/integrations/integrations.c
@@ -212,6 +212,9 @@ void ddtrace_integrations_minit(void) {
DD_SET_UP_DEFERRED_LOADING_BY_METHOD(DDTRACE_INTEGRATION_YII, "yii\\di\\Container", "__construct",
"DDTrace\\Integrations\\Yii\\YiiIntegration");
+
+ DD_SET_UP_DEFERRED_LOADING_BY_METHOD(DDTRACE_INTEGRATION_ZENDFRAMEWORK, "Zend_Controller_Plugin_Broker", "preDispatch",
+ "DDTrace\\Integrations\\ZendFramework\\ZendFrameworkIntegration");
}
ddtrace_integration* ddtrace_get_integration_from_string(ddtrace_string integration) {
diff --git a/src/Integrations/Integrations/IntegrationsLoader.php b/src/Integrations/Integrations/IntegrationsLoader.php
index d234d51ea8..7452882781 100644
--- a/src/Integrations/Integrations/IntegrationsLoader.php
+++ b/src/Integrations/Integrations/IntegrationsLoader.php
@@ -82,8 +82,6 @@ public function __construct(array $integrations)
$this->integrations[MongoIntegration::NAME] =
'\DDTrace\Integrations\Mongo\MongoIntegration';
- $this->integrations[ZendFrameworkIntegration::NAME] =
- '\DDTrace\Integrations\ZendFramework\ZendFrameworkIntegration';
// For PHP 7.0+ use C level deferred integration loader
if (\PHP_MAJOR_VERSION < 7) {
@@ -113,6 +111,8 @@ public function __construct(array $integrations)
'\DDTrace\Integrations\Symfony\SymfonyIntegration';
$this->integrations[WordPressIntegration::NAME] =
'\DDTrace\Integrations\WordPress\WordPressIntegration';
+ $this->integrations[ZendFrameworkIntegration::NAME] =
+ '\DDTrace\Integrations\ZendFramework\ZendFrameworkIntegration';
}
}
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/.gitignore b/tests/Frameworks/ZendFramework/Version_1_21/.gitignore
new file mode 100644
index 0000000000..f07202265d
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/.gitignore
@@ -0,0 +1 @@
+public/error.log
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/.zfproject.xml b/tests/Frameworks/ZendFramework/Version_1_21/.zfproject.xml
new file mode 100644
index 0000000000..47144dba94
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/.zfproject.xml
@@ -0,0 +1,68 @@
+
+
+ Message: exception->getMessage() ?> +
+ +exception->getTraceAsString() ?> ++ +
escape(var_export($this->request->getParams(), true)) ?> ++ + + + + diff --git a/tests/Frameworks/ZendFramework/Version_1_21/application/views/scripts/index/index.phtml b/tests/Frameworks/ZendFramework/Version_1_21/application/views/scripts/index/index.phtml new file mode 100644 index 0000000000..4b38f0a8a9 --- /dev/null +++ b/tests/Frameworks/ZendFramework/Version_1_21/application/views/scripts/index/index.phtml @@ -0,0 +1,43 @@ + +
+ Helpful Links:
+ Zend Framework Website |
+ Zend Framework Manual
+
Zend Amf Endpoint
' ; + } + } + + /** + * Retrieve raw AMF Request + * + * @return string + */ + public function getRawRequest() + { + return $this->_rawRequest; + } +} diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Response.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Response.php new file mode 100644 index 0000000000..788c0da166 --- /dev/null +++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Response.php @@ -0,0 +1,205 @@ +_outputStream = new Zend_Amf_Parse_OutputStream(); + $this->writeMessage($this->_outputStream); + return $this; + } + + /** + * Serialize the PHP data types back into Actionscript and + * create and AMF stream. + * + * @param Zend_Amf_Parse_OutputStream $stream + * @return Zend_Amf_Response + */ + public function writeMessage(Zend_Amf_Parse_OutputStream $stream) + { + $objectEncoding = $this->_objectEncoding; + + //Write encoding to start of stream. Preamble byte is written of two byte Unsigned Short + $stream->writeByte(0x00); + $stream->writeByte($objectEncoding); + + // Loop through the AMF Headers that need to be returned. + $headerCount = count($this->_headers); + $stream->writeInt($headerCount); + foreach ($this->getAmfHeaders() as $header) { + $serializer = new Zend_Amf_Parse_Amf0_Serializer($stream); + $stream->writeUtf($header->name); + $stream->writeByte($header->mustRead); + $stream->writeLong(Zend_Amf_Constants::UNKNOWN_CONTENT_LENGTH); + if (is_object($header->data)) { + // Workaround for PHP5 with E_STRICT enabled complaining about + // "Only variables should be passed by reference" + $placeholder = null; + $serializer->writeTypeMarker($placeholder, null, $header->data); + } else { + $serializer->writeTypeMarker($header->data); + } + } + + // loop through the AMF bodies that need to be returned. + $bodyCount = count($this->_bodies); + $stream->writeInt($bodyCount); + foreach ($this->_bodies as $body) { + $serializer = new Zend_Amf_Parse_Amf0_Serializer($stream); + $stream->writeUtf($body->getTargetURI()); + $stream->writeUtf($body->getResponseURI()); + $stream->writeLong(Zend_Amf_Constants::UNKNOWN_CONTENT_LENGTH); + $bodyData = $body->getData(); + $markerType = ($this->_objectEncoding == Zend_Amf_Constants::AMF0_OBJECT_ENCODING) ? null : Zend_Amf_Constants::AMF0_AMF3; + if (is_object($bodyData)) { + // Workaround for PHP5 with E_STRICT enabled complaining about + // "Only variables should be passed by reference" + $placeholder = null; + $serializer->writeTypeMarker($placeholder, $markerType, $bodyData); + } else { + $serializer->writeTypeMarker($bodyData, $markerType); + } + } + + return $this; + } + + /** + * Return the output stream content + * + * @return string The contents of the output stream + */ + public function getResponse() + { + return $this->_outputStream->getStream(); + } + + /** + * Return the output stream content + * + * @return string + */ + public function __toString() + { + return $this->getResponse(); + } + + /** + * Add an AMF body to be sent to the Flash Player + * + * @param Zend_Amf_Value_MessageBody $body + * @return Zend_Amf_Response + */ + public function addAmfBody(Zend_Amf_Value_MessageBody $body) + { + $this->_bodies[] = $body; + return $this; + } + + /** + * Return an array of AMF bodies to be serialized + * + * @return array + */ + public function getAmfBodies() + { + return $this->_bodies; + } + + /** + * Add an AMF Header to be sent back to the flash player + * + * @param Zend_Amf_Value_MessageHeader $header + * @return Zend_Amf_Response + */ + public function addAmfHeader(Zend_Amf_Value_MessageHeader $header) + { + $this->_headers[] = $header; + return $this; + } + + /** + * Retrieve attached AMF message headers + * + * @return array Array of Zend_Amf_Value_MessageHeader objects + */ + public function getAmfHeaders() + { + return $this->_headers; + } + + /** + * Set the AMF encoding that will be used for serialization + * + * @param int $encoding + * @return Zend_Amf_Response + */ + public function setObjectEncoding($encoding) + { + $this->_objectEncoding = $encoding; + return $this; + } +} diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Response/Http.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Response/Http.php new file mode 100644 index 0000000000..4182f6aa8a --- /dev/null +++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Response/Http.php @@ -0,0 +1,73 @@ +isIeOverSsl()) { + header('Cache-Control: cache, must-revalidate'); + header('Pragma: public'); + } else { + header('Cache-Control: no-cache, must-revalidate'); + header('Pragma: no-cache'); + } + header('Expires: Thu, 19 Nov 1981 08:52:00 GMT'); + header('Content-Type: application/x-amf'); + } + return parent::getResponse(); + } + + protected function isIeOverSsl() + { + $ssl = isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : false; + if (!$ssl || ($ssl == 'off')) { + // IIS reports "off", whereas other browsers simply don't populate + return false; + } + + $ua = $_SERVER['HTTP_USER_AGENT']; + if (!preg_match('/; MSIE \d+\.\d+;/', $ua)) { + // Not MicroSoft Internet Explorer + return false; + } + + return true; + } +} diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Server.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Server.php new file mode 100644 index 0000000000..d3df95faae --- /dev/null +++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Server.php @@ -0,0 +1,1069 @@ + method pairs + * @var array + */ + protected $_table = []; + + /** + * + * @var bool session flag; whether or not to add a session to each response. + */ + protected $_session = false; + + /** + * Namespace allows all AMF calls to not clobber other PHP session variables + * @var Zend_Session_NameSpace default session namespace zend_amf + */ + protected $_sesionNamespace = 'zend_amf'; + + /** + * Set the default session.name if php_ + * @var string + */ + protected $_sessionName = 'PHPSESSID'; + + /** + * Authentication handler object + * + * @var Zend_Amf_Auth_Abstract + */ + protected $_auth; + /** + * ACL handler object + * + * @var Zend_Acl + */ + protected $_acl; + /** + * The server constructor + */ + public function __construct() + { + Zend_Amf_Parse_TypeLoader::setResourceLoader(new Zend_Loader_PluginLoader(["Zend_Amf_Parse_Resource" => "Zend/Amf/Parse/Resource"])); + } + + /** + * Set authentication adapter + * + * If the authentication adapter implements a "getAcl()" method, populate + * the ACL of this instance with it (if none exists already). + * + * @param Zend_Amf_Auth_Abstract $auth + * @return Zend_Amf_Server + */ + public function setAuth(Zend_Amf_Auth_Abstract $auth) + { + $this->_auth = $auth; + if ((null === $this->getAcl()) && method_exists($auth, 'getAcl')) { + $this->setAcl($auth->getAcl()); + } + return $this; + } + /** + * Get authentication adapter + * + * @return Zend_Amf_Auth_Abstract + */ + public function getAuth() + { + return $this->_auth; + } + + /** + * Set ACL adapter + * + * @param Zend_Acl $acl + * @return Zend_Amf_Server + */ + public function setAcl(Zend_Acl $acl) + { + $this->_acl = $acl; + return $this; + } + /** + * Get ACL adapter + * + * @return Zend_Acl + */ + public function getAcl() + { + return $this->_acl; + } + + /** + * Set production flag + * + * @param bool $flag + * @return Zend_Amf_Server + */ + public function setProduction($flag) + { + $this->_production = (bool) $flag; + return $this; + } + + /** + * Whether or not the server is in production + * + * @return bool + */ + public function isProduction() + { + return $this->_production; + } + + /** + * @param namespace of all incoming sessions defaults to Zend_Amf + * @return Zend_Amf_Server + * @throws Zend_Session_Exception + */ + public function setSession($namespace = 'Zend_Amf') + { + require_once 'Zend/Session.php'; + $this->_session = true; + $this->_sesionNamespace = new Zend_Session_Namespace($namespace); + return $this; + } + + /** + * Whether of not the server is using sessions + * @return bool + */ + public function isSession() + { + return $this->_session; + } + + /** + * Check if the ACL allows accessing the function or method + * + * @param string|object $object Object or class being accessed + * @param string $function Function or method being accessed + * @return bool + * @throws Zend_Acl_Exception + * @throws Zend_Amf_Server_Exception + */ + protected function _checkAcl($object, $function) + { + if(!$this->_acl) { + return true; + } + if($object) { + $class = is_object($object)?get_class($object):$object; + if(!$this->_acl->has($class)) { + require_once 'Zend/Acl/Resource.php'; + $this->_acl->add(new Zend_Acl_Resource($class)); + } + $call = [$object, "initAcl"]; + if(is_callable($call) && !call_user_func($call, $this->_acl)) { + // if initAcl returns false, no ACL check + return true; + } + } else { + $class = null; + } + + $auth = Zend_Auth::getInstance(); + if($auth->hasIdentity()) { + $role = $auth->getIdentity()->role; + } else { + if($this->_acl->hasRole(Zend_Amf_Constants::GUEST_ROLE)) { + $role = Zend_Amf_Constants::GUEST_ROLE; + } else { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception("Unauthenticated access not allowed"); + } + } + if($this->_acl->isAllowed($role, $class, $function)) { + return true; + } else { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception("Access not allowed"); + } + } + + /** + * Get PluginLoader for the Server + * + * @return Zend_Loader_PluginLoader + */ + protected function getLoader() + { + if(empty($this->_loader)) { + require_once 'Zend/Loader/PluginLoader.php'; + $this->_loader = new Zend_Loader_PluginLoader(); + } + return $this->_loader; + } + + /** + * Loads a remote class or method and executes the function and returns + * the result + * + * @param string $method Is the method to execute + * @param mixed $params values for the method + * @param string|null $source + * @return mixed $response the result of executing the method + * @throws Zend_Acl_Exception + * @throws Zend_Amf_Server_Exception + * @throws Zend_Server_Reflection_Exception + */ + protected function _dispatch($method, $params = null, $source = null) + { + if($source) { + if(($mapped = Zend_Amf_Parse_TypeLoader::getMappedClassName($source)) !== false) { + $source = $mapped; + } + } + $qualifiedName = empty($source) ? $method : $source . '.' . $method; + + if (!isset($this->_table[$qualifiedName])) { + // if source is null a method that was not defined was called. + if ($source) { + $className = str_replace('.', '_', $source); + if(class_exists($className, false) && !isset($this->_classAllowed[$className])) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Can not call "' . $className . '" - use setClass()'); + } + try { + $this->getLoader()->load($className); + } catch (Exception $e) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Class "' . $className . '" does not exist: '.$e->getMessage(), 0, $e); + } + // Add the new loaded class to the server. + require_once 'Zend/Amf/Server/Exception.php'; + $this->setClass($className, $source); + } + + if (!isset($this->_table[$qualifiedName])) { + // Source is null or doesn't contain specified method + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Method "' . $method . '" does not exist'); + } + } + + $info = $this->_table[$qualifiedName]; + $argv = $info->getInvokeArguments(); + + if (0 < count($argv)) { + $params = array_merge($params, $argv); + } + + $params = $this->_castParameters($info, $params); + + if ($info instanceof Zend_Server_Reflection_Function) { + $func = $info->getName(); + $this->_checkAcl(null, $func); + $return = call_user_func_array($func, $params); + } elseif ($info instanceof Zend_Server_Reflection_Method) { + // Get class + $class = $info->getDeclaringClass()->getName(); + if ('static' == $info->isStatic()) { + // for some reason, invokeArgs() does not work the same as + // invoke(), and expects the first argument to be an object. + // So, using a callback if the method is static. + $this->_checkAcl($class, $info->getName()); + $return = call_user_func_array([$class, $info->getName()], $params); + } else { + // Object methods + try { + $object = $info->getDeclaringClass()->newInstance(); + } catch (Exception $e) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Error instantiating class ' . $class . ' to invoke method ' . $info->getName() . ': '.$e->getMessage(), 621, $e); + } + $this->_checkAcl($object, $info->getName()); + $return = $info->invokeArgs($object, $params); + } + } else { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Method missing implementation ' . get_class($info)); + } + + return $return; + } + + /** + * Handles each of the 11 different command message types. + * + * A command message is a flex.messaging.messages.CommandMessage + * + * @param Zend_Amf_Value_Messaging_CommandMessage $message + * @return Zend_Amf_Value_Messaging_AcknowledgeMessage + * @throws Zend_Amf_Server_Exception + * @throws Zend_Session_Exception + * @see Zend_Amf_Value_Messaging_CommandMessage + */ + protected function _loadCommandMessage(Zend_Amf_Value_Messaging_CommandMessage $message) + { + require_once 'Zend/Amf/Value/Messaging/AcknowledgeMessage.php'; + switch($message->operation) { + case Zend_Amf_Value_Messaging_CommandMessage::DISCONNECT_OPERATION : + case Zend_Amf_Value_Messaging_CommandMessage::CLIENT_PING_OPERATION : + $return = new Zend_Amf_Value_Messaging_AcknowledgeMessage($message); + break; + case Zend_Amf_Value_Messaging_CommandMessage::LOGIN_OPERATION : + $data = explode(':', base64_decode($message->body)); + $userid = $data[0]; + $password = isset($data[1])?$data[1]:""; + if(empty($userid)) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Login failed: username not supplied'); + } + if(!$this->_handleAuth($userid, $password)) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Authentication failed'); + } + $return = new Zend_Amf_Value_Messaging_AcknowledgeMessage($message); + break; + case Zend_Amf_Value_Messaging_CommandMessage::LOGOUT_OPERATION : + if($this->_auth) { + Zend_Auth::getInstance()->clearIdentity(); + } + $return = new Zend_Amf_Value_Messaging_AcknowledgeMessage($message); + break; + default : + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('CommandMessage::' . $message->operation . ' not implemented'); + break; + } + return $return; + } + + /** + * Create appropriate error message + * + * @param int $objectEncoding Current AMF encoding + * @param string $message Message that was being processed when error happened + * @param string $description Error description + * @param mixed $detail Detailed data about the error + * @param int $code Error code + * @param int $line Error line + * @return Zend_Amf_Value_Messaging_ErrorMessage|array + */ + protected function _errorMessage($objectEncoding, $message, $description, $detail, $code, $line) + { + $return = null; + switch ($objectEncoding) { + case Zend_Amf_Constants::AMF0_OBJECT_ENCODING : + return [ + 'description' => ($this->isProduction ()) ? '' : $description, + 'detail' => ($this->isProduction ()) ? '' : $detail, + 'line' => ($this->isProduction ()) ? 0 : $line, + 'code' => $code + ]; + case Zend_Amf_Constants::AMF3_OBJECT_ENCODING : + require_once 'Zend/Amf/Value/Messaging/ErrorMessage.php'; + $return = new Zend_Amf_Value_Messaging_ErrorMessage ( $message ); + $return->faultString = $this->isProduction () ? '' : $description; + $return->faultCode = $code; + $return->faultDetail = $this->isProduction () ? '' : $detail; + break; + } + return $return; + } + + /** + * Handle AMF authentication + * + * @param string $userid + * @param string $password + * @return boolean + * @throws Zend_Amf_Server_Exception + * @throws Zend_Session_Exception + */ + protected function _handleAuth( $userid, $password) + { + if (!$this->_auth) { + return true; + } + $this->_auth->setCredentials($userid, $password); + $auth = Zend_Auth::getInstance(); + $result = $auth->authenticate($this->_auth); + if ($result->isValid()) { + if (!$this->isSession()) { + $this->setSession(); + } + return true; + } else { + // authentication failed, good bye + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception( + "Authentication failed: " . join("\n", + $result->getMessages()), $result->getCode()); + } + + } + + /** + * Takes the deserialized AMF request and performs any operations. + * + * @param Zend_Amf_Request $request + * @return void + * @throws Zend_Amf_Server_Exception + * @todo DescribeService support + * @todo should implement and SPL observer pattern for custom AMF headers + */ + protected function _handle(Zend_Amf_Request $request) + { + // Get the object encoding of the request. + $objectEncoding = $request->getObjectEncoding(); + + // create a response object to place the output from the services. + $response = $this->getResponse(); + + // set response encoding + $response->setObjectEncoding($objectEncoding); + + // Authenticate, if we have credential headers + $error = false; + $headers = $request->getAmfHeaders(); + if (isset($headers[Zend_Amf_Constants::CREDENTIALS_HEADER]) + && isset($headers[Zend_Amf_Constants::CREDENTIALS_HEADER]->userid) + && isset($headers[Zend_Amf_Constants::CREDENTIALS_HEADER]->password) + ) { + try { + if ($this->_handleAuth( + $headers[Zend_Amf_Constants::CREDENTIALS_HEADER]->userid, + $headers[Zend_Amf_Constants::CREDENTIALS_HEADER]->password + )) { + // use RequestPersistentHeader to clear credentials + $response->addAmfHeader( + new Zend_Amf_Value_MessageHeader( + Zend_Amf_Constants::PERSISTENT_HEADER, + false, + new Zend_Amf_Value_MessageHeader( + Zend_Amf_Constants::CREDENTIALS_HEADER, + false, null + ) + ) + ); + } + } catch (Exception $e) { + // Error during authentication; report it + $error = $this->_errorMessage( + $objectEncoding, + '', + $e->getMessage(), + $e->getTraceAsString(), + $e->getCode(), + $e->getLine() + ); + $responseType = Zend_Amf_Constants::STATUS_METHOD; + } + } + + // Iterate through each of the service calls in the AMF request + foreach($request->getAmfBodies() as $body) + { + if ($error) { + // Error during authentication; just report it and be done + $responseURI = $body->getResponseURI() . $responseType; + $newBody = new Zend_Amf_Value_MessageBody($responseURI, null, $error); + $response->addAmfBody($newBody); + continue; + } + try { + switch ($objectEncoding) { + case Zend_Amf_Constants::AMF0_OBJECT_ENCODING: + // AMF0 Object Encoding + $targetURI = $body->getTargetURI(); + $message = ''; + + // Split the target string into its values. + $source = substr($targetURI, 0, strrpos($targetURI, '.')); + + if ($source) { + // Break off method name from namespace into source + $method = substr(strrchr($targetURI, '.'), 1); + $return = $this->_dispatch($method, $body->getData(), $source); + } else { + // Just have a method name. + $return = $this->_dispatch($targetURI, $body->getData()); + } + break; + case Zend_Amf_Constants::AMF3_OBJECT_ENCODING: + default: + // AMF3 read message type + $message = $body->getData(); + if ($message instanceof Zend_Amf_Value_Messaging_CommandMessage) { + // async call with command message + $return = $this->_loadCommandMessage($message); + } elseif ($message instanceof Zend_Amf_Value_Messaging_RemotingMessage) { + require_once 'Zend/Amf/Value/Messaging/AcknowledgeMessage.php'; + $return = new Zend_Amf_Value_Messaging_AcknowledgeMessage($message); + $return->body = $this->_dispatch($message->operation, $message->body, $message->source); + } else { + // Amf3 message sent with netConnection + $targetURI = $body->getTargetURI(); + + // Split the target string into its values. + $source = substr($targetURI, 0, strrpos($targetURI, '.')); + + if ($source) { + // Break off method name from namespace into source + $method = substr(strrchr($targetURI, '.'), 1); + $return = $this->_dispatch($method, $body->getData(), $source); + } else { + // Just have a method name. + $return = $this->_dispatch($targetURI, $body->getData()); + } + } + break; + } + $responseType = Zend_Amf_Constants::RESULT_METHOD; + } catch (Exception $e) { + $return = $this->_errorMessage($objectEncoding, $message, + $e->getMessage(), $e->getTraceAsString(),$e->getCode(), $e->getLine()); + $responseType = Zend_Amf_Constants::STATUS_METHOD; + } + + $responseURI = $body->getResponseURI() . $responseType; + $newBody = new Zend_Amf_Value_MessageBody($responseURI, null, $return); + $response->addAmfBody($newBody); + } + // Add a session header to the body if session is requested. + if($this->isSession()) { + $currentID = session_id(); + $joint = "?"; + if(isset($_SERVER['QUERY_STRING'])) { + if(!strpos($_SERVER['QUERY_STRING'], $currentID) !== FALSE) { + if(strrpos($_SERVER['QUERY_STRING'], "?") !== FALSE) { + $joint = "&"; + } + } + } + + // create a new AMF message header with the session id as a variable. + $sessionValue = $joint . $this->_sessionName . "=" . $currentID; + $sessionHeader = new Zend_Amf_Value_MessageHeader(Zend_Amf_Constants::URL_APPEND_HEADER, false, $sessionValue); + $response->addAmfHeader($sessionHeader); + } + + // serialize the response and return serialized body. + $response->finalize(); + } + + /** + * Handle an AMF call from the gateway. + * + * @param null|Zend_Amf_Request $request Optional + * @return Zend_Amf_Response + * @throws Zend_Amf_Server_Exception + */ + public function handle($request = null) + { + // Check if request was passed otherwise get it from the server + if ($request === null || !$request instanceof Zend_Amf_Request) { + $request = $this->getRequest(); + } else { + $this->setRequest($request); + } + if ($this->isSession()) { + // Check if a session is being sent from the amf call + if (isset($_COOKIE[$this->_sessionName])) { + session_id($_COOKIE[$this->_sessionName]); + } + } + + // Check for errors that may have happend in deserialization of Request. + try { + // Take converted PHP objects and handle service call. + // Serialize to Zend_Amf_response for output stream + $this->_handle($request); + $response = $this->getResponse(); + } catch (Exception $e) { + // Handle any errors in the serialization and service calls. + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Handle error: ' . $e->getMessage() . ' ' . $e->getLine(), 0, $e); + } + + // Return the Amf serialized output string + return $response; + } + + /** + * Set request object + * + * @param string|Zend_Amf_Request $request + * @return Zend_Amf_Server + * @throws Zend_Amf_Server_Exception + */ + public function setRequest($request) + { + if (is_string($request) && class_exists($request)) { + $request = new $request(); + if (!$request instanceof Zend_Amf_Request) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Invalid request class'); + } + } elseif (!$request instanceof Zend_Amf_Request) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Invalid request object'); + } + $this->_request = $request; + return $this; + } + + /** + * Return currently registered request object + * + * @return null|Zend_Amf_Request + * @throws Zend_Amf_Server_Exception + */ + public function getRequest() + { + if (null === $this->_request) { + require_once 'Zend/Amf/Request/Http.php'; + $this->setRequest(new Zend_Amf_Request_Http()); + } + + return $this->_request; + } + + /** + * Public access method to private Zend_Amf_Server_Response reference + * + * @param string|Zend_Amf_Server_Response $response + * @return Zend_Amf_Server + * @throws Zend_Amf_Server_Exception + */ + public function setResponse($response) + { + if (is_string($response) && class_exists($response)) { + $response = new $response(); + if (!$response instanceof Zend_Amf_Response) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Invalid response class'); + } + } elseif (!$response instanceof Zend_Amf_Response) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Invalid response object'); + } + $this->_response = $response; + return $this; + } + + /** + * get a reference to the Zend_Amf_response instance + * + * @return Zend_Amf_Response|null + * @throws Zend_Amf_Server_Exception + */ + public function getResponse() + { + if (null === ($response = $this->_response)) { + require_once 'Zend/Amf/Response/Http.php'; + $this->setResponse(new Zend_Amf_Response_Http()); + } + return $this->_response; + } + + /** + * Attach a class or object to the server + * + * Class may be either a class name or an instantiated object. Reflection + * is done on the class or object to determine the available public + * methods, and each is attached to the server as and available method. If + * a $namespace has been provided, that namespace is used to prefix + * AMF service call. + * + * @param string|object $class + * @param string $namespace Optional + * @param mixed $arg Optional arguments to pass to a method + * @return Zend_Amf_Server + * @throws Zend_Amf_Server_Exception on invalid input + * @throws Zend_Server_Reflection_Exception + */ + public function setClass($class, $namespace = '', $argv = null) + { + if (is_string($class) && !class_exists($class)) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Invalid method or class'); + } + + if (!is_string($class) && !is_object($class)) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Invalid method or class; must be a classname or object'); + } + + $args = null; + if (2 < func_num_args()) { + $args = array_slice(func_get_args(), 2); + } + + // Use the class name as the name space by default. + + if ($namespace == '') { + $namespace = is_object($class) ? get_class($class) : $class; + } + + $this->_classAllowed[is_object($class) ? get_class($class) : $class] = true; + + $this->_methods[] = Zend_Server_Reflection::reflectClass($class, $args, $namespace); + $this->_buildDispatchTable(); + + return $this; + } + + /** + * Attach a function to the server + * + * 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 $function Valid callback + * @param string $namespace Optional namespace prefix + * @return Zend_Amf_Server + * @throws Zend_Amf_Server_Exception + * @throws Zend_Server_Reflection_Exception + */ + public function addFunction($function, $namespace = '') + { + if (!is_string($function) && !is_array($function)) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Unable to attach function'); + } + + $argv = null; + if (2 < func_num_args()) { + $argv = array_slice(func_get_args(), 2); + } + + $function = (array) $function; + foreach ($function as $func) { + if (!is_string($func) || !function_exists($func)) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Unable to attach function'); + } + $this->_methods[] = Zend_Server_Reflection::reflectFunction($func, $argv, $namespace); + } + + $this->_buildDispatchTable(); + return $this; + } + + + /** + * Creates an array of directories in which services can reside. + * TODO: add support for prefixes? + * + * @param string $dir + * @throws Zend_Loader_PluginLoader_Exception + */ + public function addDirectory($dir) + { + $this->getLoader()->addPrefixPath("", $dir); + } + + /** + * Returns an array of directories that can hold services. + * + * @return array + */ + public function getDirectory() + { + return $this->getLoader()->getPaths(""); + } + + /** + * (Re)Build the dispatch table + * + * The dispatch table consists of a an array of method name => + * Zend_Server_Reflection_Function_Abstract pairs + * + * @return void + * @throws Zend_Amf_Server_Exception + */ + protected function _buildDispatchTable() + { + $table = []; + foreach ($this->_methods as $key => $dispatchable) { + if ($dispatchable instanceof Zend_Server_Reflection_Function_Abstract) { + $ns = $dispatchable->getNamespace(); + $name = $dispatchable->getName(); + $name = empty($ns) ? $name : $ns . '.' . $name; + + if (isset($table[$name])) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Duplicate method registered: ' . $name); + } + $table[$name] = $dispatchable; + continue; + } + + if ($dispatchable instanceof Zend_Server_Reflection_Class) { + foreach ($dispatchable->getMethods() as $method) { + $ns = $method->getNamespace(); + $name = $method->getName(); + $name = empty($ns) ? $name : $ns . '.' . $name; + + if (isset($table[$name])) { + require_once 'Zend/Amf/Server/Exception.php'; + throw new Zend_Amf_Server_Exception('Duplicate method registered: ' . $name); + } + $table[$name] = $method; + continue; + } + } + } + $this->_table = $table; + } + + + + /** + * Raise a server fault + * + * Unimplemented + * + * @param string|Exception $fault + * @return void + */ + public function fault($fault = null, $code = 404) + { + } + + /** + * Returns a list of registered methods + * + * Returns an array of dispatchables (Zend_Server_Reflection_Function, + * _Method, and _Class items). + * + * @return array + */ + public function getFunctions() + { + return $this->_table; + } + + /** + * Set server persistence + * + * Unimplemented + * + * @param mixed $mode + * @return void + */ + public function setPersistence($mode) + { + } + + /** + * Load server definition + * + * Unimplemented + * + * @param array $definition + * @return void + */ + public function loadFunctions($definition) + { + } + + /** + * Map ActionScript classes to PHP classes + * + * @param string $asClass + * @param string $phpClass + * @return Zend_Amf_Server + */ + public function setClassMap($asClass, $phpClass) + { + require_once 'Zend/Amf/Parse/TypeLoader.php'; + Zend_Amf_Parse_TypeLoader::setMapping($asClass, $phpClass); + return $this; + } + + /** + * List all available methods + * + * Returns an array of method names. + * + * @return array + */ + public function listMethods() + { + return array_keys($this->_table); + } + + /** + * Cast parameters + * + * Takes the provided parameters from the request, and attempts to cast them + * to objects, if the prototype defines any as explicit object types + * + * @param Reflection $reflectionMethod + * @param array $params + * @return array + */ + protected function _castParameters($reflectionMethod, array $params) + { + $prototypes = $reflectionMethod->getPrototypes(); + $nonObjectTypes = [ + 'null', + 'mixed', + 'void', + 'unknown', + 'bool', + 'boolean', + 'number', + 'int', + 'integer', + 'double', + 'float', + 'string', + 'array', + 'object', + 'stdclass', + ]; + $types = []; + foreach ($prototypes as $prototype) { + foreach ($prototype->getParameters() as $parameter) { + $type = $parameter->getType(); + if (in_array(strtolower($type), $nonObjectTypes)) { + continue; + } + $position = $parameter->getPosition(); + $types[$position] = $type; + } + } + + if (empty($types)) { + return $params; + } + + foreach ($params as $position => $value) { + if (!isset($types[$position])) { + // No specific type to cast to? done + continue; + } + + $type = $types[$position]; + + if (!class_exists($type)) { + // Not a class, apparently. done + continue; + } + + if ($value instanceof $type) { + // Already of the right type? done + continue; + } + + if (!is_array($value) && !is_object($value)) { + // Can't cast scalars to objects easily; done + continue; + } + + // Create instance, and loop through value to set + $object = new $type; + foreach ($value as $property => $defined) { + $object->{$property} = $defined; + } + + $params[$position] = $object; + } + + return $params; + } +} diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Server/Exception.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Server/Exception.php new file mode 100644 index 0000000000..d9532ac112 --- /dev/null +++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Server/Exception.php @@ -0,0 +1,37 @@ +_stream = $stream; + $this->_needle = 0; + $this->_mbStringFunctionsOverloaded = function_exists('mb_strlen') && (ini_get('mbstring.func_overload') !== '') && ((int)ini_get('mbstring.func_overload') & 2); + $this->_streamLength = $this->_mbStringFunctionsOverloaded ? mb_strlen($stream, '8bit') : strlen($stream); + $this->_bigEndian = (pack('l', 1) === "\x00\x00\x00\x01"); + } + + /** + * Returns the current stream + * + * @return string + */ + public function getStream() + { + return $this->_stream; + } + + /** + * Read the number of bytes in a row for the length supplied. + * + * @todo Should check that there are enough bytes left in the stream we are about to read. + * @param int $length + * @return string + * @throws Zend_Amf_Exception for buffer underrun + */ + public function readBytes($length) + { + if (($length + $this->_needle) > $this->_streamLength) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Buffer underrun at needle position: ' . $this->_needle . ' while requesting length: ' . $length); + } + $bytes = $this->_mbStringFunctionsOverloaded ? mb_substr($this->_stream, $this->_needle, $length, '8bit') : substr($this->_stream, $this->_needle, $length); + $this->_needle+= $length; + return $bytes; + } + + /** + * Write any length of bytes to the stream + * + * Usually a string. + * + * @param string $bytes + * @return Zend_Amf_Util_BinaryStream + */ + public function writeBytes($bytes) + { + $this->_stream.= $bytes; + return $this; + } + + /** + * Reads a signed byte + * + * @return int Value is in the range of -128 to 127. + * @throws Zend_Amf_Exception + */ + public function readByte() + { + if (($this->_needle + 1) > $this->_streamLength) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception( + 'Buffer underrun at needle position: ' + . $this->_needle + . ' while requesting length: ' + . $this->_streamLength + ); + } + + return ord($this->_stream[$this->_needle++]); + } + + /** + * Writes the passed string into a signed byte on the stream. + * + * @param string $stream + * @return Zend_Amf_Util_BinaryStream + */ + public function writeByte($stream) + { + $this->_stream.= pack('c', $stream); + return $this; + } + + /** + * Reads a signed 32-bit integer from the data stream. + * + * @return int Value is in the range of -2147483648 to 2147483647 + */ + public function readInt() + { + return ($this->readByte() << 8) + $this->readByte(); + } + + /** + * Write an the integer to the output stream as a 32 bit signed integer + * + * @param int $stream + * @return Zend_Amf_Util_BinaryStream + */ + public function writeInt($stream) + { + $this->_stream.= pack('n', $stream); + return $this; + } + + /** + * Reads a UTF-8 string from the data stream + * + * @return string A UTF-8 string produced by the byte representation of characters + */ + public function readUtf() + { + $length = $this->readInt(); + return $this->readBytes($length); + } + + /** + * Wite a UTF-8 string to the outputstream + * + * @param string $stream + * @return Zend_Amf_Util_BinaryStream + */ + public function writeUtf($stream) + { + $this->writeInt($this->_mbStringFunctionsOverloaded ? mb_strlen($stream, '8bit') : strlen($stream)); + $this->_stream.= $stream; + return $this; + } + + + /** + * Read a long UTF string + * + * @return string + */ + public function readLongUtf() + { + $length = $this->readLong(); + return $this->readBytes($length); + } + + /** + * Write a long UTF string to the buffer + * + * @param string $stream + * @return void + */ + public function writeLongUtf($stream) + { + $this->writeLong($this->_mbStringFunctionsOverloaded ? mb_strlen($stream, '8bit') : strlen($stream)); + $this->_stream.= $stream; + } + + /** + * Read a long numeric value + * + * @return double + */ + public function readLong() + { + return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte(); + } + + /** + * Write long numeric value to output stream + * + * @param int|string $stream + * @return Zend_Amf_Util_BinaryStream + */ + public function writeLong($stream) + { + $this->_stream.= pack('N', $stream); + return $this; + } + + /** + * Read a 16 bit unsigned short. + * + * @todo This could use the unpack() w/ S,n, or v + * @return double + */ + public function readUnsignedShort() + { + $byte1 = $this->readByte(); + $byte2 = $this->readByte(); + return (($byte1 << 8) | $byte2); + } + + /** + * Reads an IEEE 754 double-precision floating point number from the data stream. + * + * @return double Floating point number + */ + public function readDouble() + { + $bytes = $this->_mbStringFunctionsOverloaded ? mb_substr($this->_stream, $this->_needle, 8, '8bit') : substr($this->_stream, $this->_needle, 8); + $this->_needle+= 8; + + if (!$this->_bigEndian) { + $bytes = strrev($bytes); + } + + $double = unpack('dflt', $bytes); + return $double['flt']; + } + + /** + * Writes an IEEE 754 double-precision floating point number from the data stream. + * + * @param string|double $stream + * @return Zend_Amf_Util_BinaryStream + */ + public function writeDouble($stream) + { + $stream = pack('d', $stream); + if (!$this->_bigEndian) { + $stream = strrev($stream); + } + $this->_stream.= $stream; + return $this; + } + +} diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/ByteArray.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/ByteArray.php new file mode 100644 index 0000000000..32ebf268b8 --- /dev/null +++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/ByteArray.php @@ -0,0 +1,58 @@ +_data = $data; + } + + /** + * Return the byte stream + * + * @return string + */ + public function getData() + { + return $this->_data; + } +} diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/MessageBody.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/MessageBody.php new file mode 100644 index 0000000000..59cacba52b --- /dev/null +++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/MessageBody.php @@ -0,0 +1,182 @@ + + * This Message structure defines how a local client would + * invoke a method/operation on a remote server. Additionally, + * the response from the Server is structured identically. + * + * @package Zend_Amf + * @subpackage Value + * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Amf_Value_MessageBody +{ + /** + * A string describing which operation, function, or method + * is to be remotley invoked. + * @var string + */ + protected $_targetUri = ""; + + /** + * Universal Resource Identifier that uniquely targets the originator's + * Object that should receive the server's response. The server will + * use this path specification to target the "OnResult()" or "onStatus()" + * handlers within the client. For Flash, it specifies an ActionScript + * Object path only. The NetResponse object pointed to by the Response Uri + * contains the connection state information. Passing/specifying this + * provides a convenient mechanism for the client/server to share access + * to an object that is managing the state of the shared connection. + * + * Since the server will use this field in the event of an error, + * this field is required even if a successful server request would + * not be expected to return a value to the client. + * + * @var string + */ + protected $_responseUri = ""; + + /** + * Contains the actual data associated with the operation. It contains + * the client's parameter data that is passed to the server's operation/method. + * When serializing a root level data type or a parameter list array, no + * name field is included. That is, the data is anonomously represented + * as "Type Marker"/"Value" pairs. When serializing member data, the data is + * represented as a series of "Name"/"Type"/"Value" combinations. + * + * For server generated responses, it may contain any ActionScript + * data/objects that the server was expected to provide. + * + * @var string + */ + protected $_data; + + /** + * Constructor + * + * @param string $targetUri + * @param string $responseUri + * @param string $data + * @return void + */ + public function __construct($targetUri, $responseUri, $data) + { + $this->setTargetUri($targetUri); + $this->setResponseUri($responseUri); + $this->setData($data); + } + + /** + * Retrieve target Uri + * + * @return string + */ + public function getTargetUri() + { + return $this->_targetUri; + } + + /** + * Set target Uri + * + * @param string $targetUri + * @return Zend_Amf_Value_MessageBody + */ + public function setTargetUri($targetUri) + { + if (null === $targetUri) { + $targetUri = ''; + } + $this->_targetUri = (string) $targetUri; + return $this; + } + + /** + * Get target Uri + * + * @return string + */ + public function getResponseUri() + { + return $this->_responseUri; + } + + /** + * Set response Uri + * + * @param string $responseUri + * @return Zend_Amf_Value_MessageBody + */ + public function setResponseUri($responseUri) + { + if (null === $responseUri) { + $responseUri = ''; + } + $this->_responseUri = $responseUri; + return $this; + } + + /** + * Retrieve response data + * + * @return string + */ + public function getData() + { + return $this->_data; + } + + /** + * Set response data + * + * @param mixed $data + * @return Zend_Amf_Value_MessageBody + */ + public function setData($data) + { + $this->_data = $data; + return $this; + } + + /** + * Set reply method + * + * @param string $methodName + * @return Zend_Amf_Value_MessageBody + */ + public function setReplyMethod($methodName) + { + if (!preg_match('#^[/?]#', $methodName)) { + $this->_targetUri = rtrim($this->_targetUri, '/') . '/'; + } + $this->_targetUri = $this->_targetUri . $methodName; + return $this; + } +} diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/MessageHeader.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/MessageHeader.php new file mode 100644 index 0000000000..bdbbba2a7d --- /dev/null +++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/MessageHeader.php @@ -0,0 +1,81 @@ +name = $name; + $this->mustRead = (bool) $mustRead; + $this->data = $data; + if (null !== $length) { + $this->length = (int) $length; + } + } +} diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/Messaging/AbstractMessage.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/Messaging/AbstractMessage.php new file mode 100644 index 0000000000..acb5a0c6c8 --- /dev/null +++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/Messaging/AbstractMessage.php @@ -0,0 +1,92 @@ +clientId = $this->generateId(); + $this->destination = null; + $this->messageId = $this->generateId(); + $this->timestamp = time().'00'; + $this->timeToLive = 0; + $this->headers = new stdClass(); + $this->body = null; + + // correleate the two messages + if ($message && isset($message->messageId)) { + $this->correlationId = $message->messageId; + } + } +} diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/Messaging/ArrayCollection.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/Messaging/ArrayCollection.php new file mode 100755 index 0000000000..a759f58e53 --- /dev/null +++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/Messaging/ArrayCollection.php @@ -0,0 +1,35 @@ +body + * of the message. + */ + const LOGIN_OPERATION = 8; + + /** + * This operation is used to log the user out of the current channel, and + * will invalidate the server session if the channel is HTTP based. + */ + const LOGOUT_OPERATION = 9; + + /** + * This operation is used to indicate that the client's subscription to a + * remote destination has been invalidated. + */ + const SESSION_INVALIDATE_OPERATION = 10; + + /** + * This operation is used by the MultiTopicConsumer to subscribe/unsubscribe + * from multiple subtopics/selectors in the same message. + */ + const MULTI_SUBSCRIBE_OPERATION = 11; + + /** + * This operation is used to indicate that a channel has disconnected + */ + const DISCONNECT_OPERATION = 12; + + /** + * This is the default operation for new CommandMessage instances. + */ + const UNKNOWN_OPERATION = 10000; + + /** + * The operation to execute for messages of this type + * @var int + */ + public $operation = self::UNKNOWN_OPERATION; +} diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/Messaging/ErrorMessage.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/Messaging/ErrorMessage.php new file mode 100644 index 0000000000..a6eaf166fb --- /dev/null +++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/Messaging/ErrorMessage.php @@ -0,0 +1,67 @@ +clientId = $this->generateId(); + $this->destination = null; + $this->messageId = $this->generateId(); + $this->timestamp = time().'00'; + $this->timeToLive = 0; + $this->headers = new stdClass(); + $this->body = null; + } +} diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/TraitsInfo.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/TraitsInfo.php new file mode 100644 index 0000000000..64d3c990ea --- /dev/null +++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Amf/Value/TraitsInfo.php @@ -0,0 +1,154 @@ +_className = $className; + $this->_dynamic = $dynamic; + $this->_externalizable = $externalizable; + $this->_properties = $properties; + } + + /** + * Test if the class is a dynamic class + * + * @return boolean + */ + public function isDynamic() + { + return $this->_dynamic; + } + + /** + * Test if class is externalizable + * + * @return boolean + */ + public function isExternalizable() + { + return $this->_externalizable; + } + + /** + * Return the number of properties in the class + * + * @return int + */ + public function length() + { + return count($this->_properties); + } + + /** + * Return the class name + * + * @return string + */ + public function getClassName() + { + return $this->_className; + } + + /** + * Add an additional property + * + * @param string $name + * @return Zend_Amf_Value_TraitsInfo + */ + public function addProperty($name) + { + $this->_properties[] = $name; + return $this; + } + + /** + * Add all properties of the class. + * + * @param array $props + * @return Zend_Amf_Value_TraitsInfo + */ + public function addAllProperties(array $props) + { + $this->_properties = $props; + return $this; + } + + /** + * Get the property at a given index + * + * @param int $index + * @return string + */ + public function getProperty($index) + { + return $this->_properties[(int) $index]; + } + + /** + * Return all properties of the class. + * + * @return array + */ + public function getAllProperties() + { + return $this->_properties; + } +} diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Application.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Application.php new file mode 100644 index 0000000000..29846f8819 --- /dev/null +++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Application.php @@ -0,0 +1,457 @@ +_environment = (string) $environment; + + require_once 'Zend/Loader/Autoloader.php'; + $this->_autoloader = Zend_Loader_Autoloader::getInstance(); + $this->_autoloader->suppressNotFoundWarnings($suppressNotFoundWarnings); + + if (null !== $options) { + if (is_string($options)) { + $options = $this->_loadConfig($options); + } elseif ($options instanceof Zend_Config) { + $options = $options->toArray(); + } elseif (!is_array($options)) { + throw new Zend_Application_Exception( + 'Invalid options provided; must be location of config file,' + . ' a config object, or an array' + ); + } + + $this->setOptions($options); + } + } + + /** + * Retrieve current environment + * + * @return string + */ + public function getEnvironment() + { + return $this->_environment; + } + + /** + * Retrieve autoloader instance + * + * @return Zend_Loader_Autoloader + */ + public function getAutoloader() + { + return $this->_autoloader; + } + + /** + * Set application options + * + * @param array $options + * @throws Zend_Application_Exception When no bootstrap path is provided + * @throws Zend_Application_Exception When invalid bootstrap information are provided + * @return Zend_Application + */ + public function setOptions(array $options) + { + if (!empty($options['config'])) { + if (is_array($options['config'])) { + $_options = []; + foreach ($options['config'] as $tmp) { + $_options = $this->mergeOptions( + $_options, $this->_loadConfig($tmp) + ); + } + $options = $this->mergeOptions($_options, $options); + } else { + $options = $this->mergeOptions( + $this->_loadConfig($options['config']), $options + ); + } + } + + $this->_options = $options; + + $options = array_change_key_case($options, CASE_LOWER); + + $this->_optionKeys = array_keys($options); + + if (!empty($options['phpsettings'])) { + $this->setPhpSettings($options['phpsettings']); + } + + if (!empty($options['includepaths'])) { + $this->setIncludePaths($options['includepaths']); + } + + if (!empty($options['autoloadernamespaces'])) { + $this->setAutoloaderNamespaces($options['autoloadernamespaces']); + } + + if (!empty($options['autoloaderzfpath'])) { + $autoloader = $this->getAutoloader(); + if (method_exists($autoloader, 'setZfPath')) { + $zfPath = $options['autoloaderzfpath']; + $zfVersion = !empty($options['autoloaderzfversion']) + ? $options['autoloaderzfversion'] + : 'latest'; + $autoloader->setZfPath($zfPath, $zfVersion); + } + } + + if (!empty($options['bootstrap'])) { + $bootstrap = $options['bootstrap']; + + if (is_string($bootstrap)) { + $this->setBootstrap($bootstrap); + } elseif (is_array($bootstrap)) { + if (empty($bootstrap['path'])) { + throw new Zend_Application_Exception( + 'No bootstrap path provided' + ); + } + + $path = $bootstrap['path']; + $class = null; + + if (!empty($bootstrap['class'])) { + $class = $bootstrap['class']; + } + + $this->setBootstrap($path, $class); + } else { + throw new Zend_Application_Exception( + 'Invalid bootstrap information provided' + ); + } + } + + return $this; + } + + /** + * Retrieve application options (for caching) + * + * @return array + */ + public function getOptions() + { + return $this->_options; + } + + /** + * Is an option present? + * + * @param string $key + * @return bool + */ + public function hasOption($key) + { + return in_array(strtolower($key), $this->_optionKeys); + } + + /** + * Retrieve a single option + * + * @param string $key + * @return mixed + */ + public function getOption($key) + { + if ($this->hasOption($key)) { + $options = $this->getOptions(); + $options = array_change_key_case($options, CASE_LOWER); + return $options[strtolower($key)]; + } + return null; + } + + /** + * Merge options recursively + * + * @param array $array1 + * @param mixed $array2 + * @return array + */ + public function mergeOptions(array $array1, $array2 = null) + { + if (is_array($array2)) { + foreach ($array2 as $key => $val) { + if (is_array($val)) { + $array1[$key] = (array_key_exists($key, $array1) && is_array($array1[$key])) + ? $this->mergeOptions($array1[$key], $val) + : $val; + } else { + $array1[$key] = $val; + } + } + } + return $array1; + } + + /** + * Set PHP configuration settings + * + * @param array $settings + * @param string $prefix Key prefix to prepend to array values (used to map . separated INI values) + * @return Zend_Application + */ + public function setPhpSettings(array $settings, $prefix = '') + { + foreach ($settings as $key => $value) { + $key = empty($prefix) ? $key : $prefix . $key; + if (is_scalar($value)) { + ini_set($key, $value); + } elseif (is_array($value)) { + $this->setPhpSettings($value, $key . '.'); + } + } + + return $this; + } + + /** + * Set include path + * + * @param array $paths + * @return Zend_Application + */ + public function setIncludePaths(array $paths) + { + $path = implode(PATH_SEPARATOR, $paths); + set_include_path($path . PATH_SEPARATOR . get_include_path()); + return $this; + } + + /** + * Set autoloader namespaces + * + * @param array $namespaces + * @return Zend_Application + */ + public function setAutoloaderNamespaces(array $namespaces) + { + $autoloader = $this->getAutoloader(); + + foreach ($namespaces as $namespace) { + $autoloader->registerNamespace($namespace); + } + + return $this; + } + + /** + * Set bootstrap path/class + * + * @param string $path + * @param string $class + * @return Zend_Application + */ + public function setBootstrap($path, $class = null) + { + // setOptions() can potentially send a null value; specify default + // here + if (null === $class) { + $class = 'Bootstrap'; + } + + if (!class_exists($class, false)) { + require_once $path; + if (!class_exists($class, false)) { + throw new Zend_Application_Exception( + 'Bootstrap class not found' + ); + } + } + $this->_bootstrap = new $class($this); + + if (!$this->_bootstrap instanceof Zend_Application_Bootstrap_Bootstrapper) { + throw new Zend_Application_Exception( + 'Bootstrap class does not implement' + . ' Zend_Application_Bootstrap_Bootstrapper' + ); + } + + return $this; + } + + /** + * Get bootstrap object + * + * @return Zend_Application_Bootstrap_BootstrapAbstract + */ + public function getBootstrap() + { + if (null === $this->_bootstrap) { + $this->_bootstrap = new Zend_Application_Bootstrap_Bootstrap($this); + } + return $this->_bootstrap; + } + + /** + * Bootstrap application + * + * @param null|string|array $resource + * @return Zend_Application + */ + public function bootstrap($resource = null) + { + $this->getBootstrap()->bootstrap($resource); + return $this; + } + + /** + * Run the application + * + * @return void + */ + public function run() + { + $this->getBootstrap()->run(); + } + + /** + * Load configuration file of options + * + * @param string $file + * @throws Zend_Application_Exception When invalid configuration file is provided + * @return array + */ + protected function _loadConfig($file) + { + $environment = $this->getEnvironment(); + $suffix = pathinfo($file, PATHINFO_EXTENSION); + $suffix = ($suffix === 'dist') + ? pathinfo(basename($file, ".$suffix"), PATHINFO_EXTENSION) + : $suffix; + $configParserOptions = self::$configParserOptions ?? false; + + switch (strtolower($suffix)) { + case 'ini': + $config = new Zend_Config_Ini($file, $environment, $configParserOptions); + break; + + case 'xml': + $config = new Zend_Config_Xml($file, $environment, $configParserOptions); + break; + + case 'json': + $config = new Zend_Config_Json($file, $environment, $configParserOptions); + break; + + case 'yaml': + case 'yml': + $config = new Zend_Config_Yaml($file, $environment, $configParserOptions); + break; + + case 'php': + case 'inc': + $config = include $file; + if (!is_array($config)) { + throw new Zend_Application_Exception( + 'Invalid configuration file provided; PHP file does not' + . ' return array value' + ); + } + return $config; + break; + + default: + throw new Zend_Application_Exception( + 'Invalid configuration file provided; unknown config type' + ); + } + + return $config->toArray(); + } +} diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Application/Bootstrap/Bootstrap.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Application/Bootstrap/Bootstrap.php new file mode 100644 index 0000000000..9e3f4e31d1 --- /dev/null +++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Application/Bootstrap/Bootstrap.php @@ -0,0 +1,168 @@ +hasOption('resourceloader')) { + $this->setOptions( + [ + 'resourceloader' => $application->getOption( + 'resourceloader' + ) + ] + ); + } + $this->getResourceLoader(); + + if (!$this->hasPluginResource('FrontController')) { + $this->registerPluginResource('FrontController'); + } + } + + /** + * Run the application + * + * Checks to see that we have a default controller directory. If not, an + * exception is thrown. + * + * If so, it registers the bootstrap with the 'bootstrap' parameter of + * the front controller, and dispatches the front controller. + * + * @return mixed + * @throws Zend_Application_Bootstrap_Exception + */ + public function run() + { + $front = $this->getResource('FrontController'); + $default = $front->getDefaultModule(); + if (null === $front->getControllerDirectory($default)) { + throw new Zend_Application_Bootstrap_Exception( + 'No default controller directory registered with front controller' + ); + } + + $front->setParam('bootstrap', $this); + $response = $front->dispatch(); + if ($front->returnResponse()) { + return $response; + } + } + + /** + * Set module resource loader + * + * @param Zend_Loader_Autoloader_Resource $loader + * @return Zend_Application_Bootstrap_Bootstrap + */ + public function setResourceLoader(Zend_Loader_Autoloader_Resource $loader) + { + $this->_resourceLoader = $loader; + return $this; + } + + /** + * Retrieve module resource loader + * + * @return Zend_Loader_Autoloader_Resource + */ + public function getResourceLoader() + { + if ((null === $this->_resourceLoader) + && (false !== ($namespace = $this->getAppNamespace())) + ) { + $r = new ReflectionClass($this); + $path = $r->getFileName(); + $this->setResourceLoader( + new Zend_Application_Module_Autoloader( + [ + 'namespace' => $namespace, + 'basePath' => dirname($path), + ] + ) + ); + } + return $this->_resourceLoader; + } + + /** + * Get application namespace (used for module autoloading) + * + * @return string + */ + public function getAppNamespace() + { + return $this->_appNamespace; + } + + /** + * Set application namespace (for module autoloading) + * + * @param string + * @return Zend_Application_Bootstrap_Bootstrap + */ + public function setAppNamespace($value) + { + $this->_appNamespace = (string) $value; + return $this; + } +} diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Application/Bootstrap/BootstrapAbstract.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Application/Bootstrap/BootstrapAbstract.php new file mode 100644 index 0000000000..3393cea185 --- /dev/null +++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Application/Bootstrap/BootstrapAbstract.php @@ -0,0 +1,784 @@ +setApplication($application); + $options = $application->getOptions(); + $this->setOptions($options); + } + + /** + * Set class state + * + * @param array $options + * @return Zend_Application_Bootstrap_BootstrapAbstract + */ + public function setOptions(array $options) + { + $this->_options = $this->mergeOptions($this->_options, $options); + + $options = array_change_key_case($options, CASE_LOWER); + $this->_optionKeys = array_merge($this->_optionKeys, array_keys($options)); + + $methods = get_class_methods($this); + foreach ($methods as $key => $method) { + $methods[$key] = strtolower($method); + } + + if (array_key_exists('pluginpaths', $options)) { + $pluginLoader = $this->getPluginLoader(); + + foreach ($options['pluginpaths'] as $prefix => $path) { + $pluginLoader->addPrefixPath($prefix, $path); + } + unset($options['pluginpaths']); + } + + foreach ($options as $key => $value) { + $method = 'set' . strtolower($key); + + if (in_array($method, $methods)) { + $this->$method($value); + } elseif ('resources' == $key) { + foreach ($value as $resource => $resourceOptions) { + $this->registerPluginResource($resource, $resourceOptions); + } + } + } + return $this; + } + + /** + * Get current options from bootstrap + * + * @return array + */ + public function getOptions() + { + return $this->_options; + } + + /** + * Is an option present? + * + * @param string $key + * @return bool + */ + public function hasOption($key) + { + return in_array(strtolower($key), $this->_optionKeys); + } + + /** + * Retrieve a single option + * + * @param string $key + * @return mixed + */ + public function getOption($key) + { + if ($this->hasOption($key)) { + $options = $this->getOptions(); + $options = array_change_key_case($options, CASE_LOWER); + return $options[strtolower($key)]; + } + return null; + } + + /** + * Merge options recursively + * + * @param array $array1 + * @param mixed $array2 + * @return array + */ + public function mergeOptions(array $array1, $array2 = null) + { + if (is_array($array2)) { + foreach ($array2 as $key => $val) { + if (is_array($val)) { + $array1[$key] = (array_key_exists($key, $array1) && is_array($array1[$key])) + ? $this->mergeOptions($array1[$key], $val) + : $val; + } else { + $array1[$key] = $val; + } + } + } + return $array1; + } + + /** + * Get class resources (as resource/method pairs) + * + * Uses get_class_methods() by default, reflection on prior to 5.2.6, + * as a bug prevents the usage of get_class_methods() there. + * + * @return array + */ + public function getClassResources() + { + if (null === $this->_classResources) { + if (version_compare(PHP_VERSION, '5.2.6') === -1) { + $class = new ReflectionObject($this); + $classMethods = $class->getMethods(); + $methodNames = []; + + foreach ($classMethods as $method) { + $methodNames[] = $method->getName(); + } + } else { + $methodNames = get_class_methods($this); + } + + $this->_classResources = []; + foreach ($methodNames as $method) { + if (5 < strlen($method) && '_init' === substr($method, 0, 5)) { + $this->_classResources[strtolower(substr($method, 5))] = $method; + } + } + } + + return $this->_classResources; + } + + /** + * Get class resource names + * + * @return array + */ + public function getClassResourceNames() + { + $resources = $this->getClassResources(); + return array_keys($resources); + } + + /** + * Register a new resource plugin + * + * @param string|Zend_Application_Resource_Resource $resource + * @param mixed $options + * @return Zend_Application_Bootstrap_BootstrapAbstract + * @throws Zend_Application_Bootstrap_Exception When invalid resource is provided + */ + public function registerPluginResource($resource, $options = null) + { + if ($resource instanceof Zend_Application_Resource_Resource) { + $resource->setBootstrap($this); + $pluginName = $this->_resolvePluginResourceName($resource); + $this->_pluginResources[$pluginName] = $resource; + return $this; + } + + if (!is_string($resource)) { + throw new Zend_Application_Bootstrap_Exception('Invalid resource provided to ' . __METHOD__); + } + + $this->_pluginResources[$resource] = $options; + return $this; + } + + /** + * Unregister a resource from the bootstrap + * + * @param string|Zend_Application_Resource_Resource $resource + * @return Zend_Application_Bootstrap_BootstrapAbstract + * @throws Zend_Application_Bootstrap_Exception When unknown resource type is provided + */ + public function unregisterPluginResource($resource) + { + if ($resource instanceof Zend_Application_Resource_Resource) { + if ($index = array_search($resource, $this->_pluginResources, true)) { + unset($this->_pluginResources[$index]); + } + return $this; + } + + if (!is_string($resource)) { + throw new Zend_Application_Bootstrap_Exception('Unknown resource type provided to ' . __METHOD__); + } + + $resource = strtolower($resource); + if (array_key_exists($resource, $this->_pluginResources)) { + unset($this->_pluginResources[$resource]); + } + + return $this; + } + + /** + * Is the requested plugin resource registered? + * + * @param string $resource + * @return bool + */ + public function hasPluginResource($resource) + { + return (null !== $this->getPluginResource($resource)); + } + + /** + * Get a registered plugin resource + * + * @param string $resource + * @return Zend_Application_Resource_Resource|null + * @throws Zend_Application_Bootstrap_Exception + */ + public function getPluginResource($resource) + { + if (array_key_exists(strtolower($resource), $this->_pluginResources)) { + $resource = strtolower($resource); + if (!$this->_pluginResources[$resource] instanceof Zend_Application_Resource_Resource) { + $resourceName = $this->_loadPluginResource($resource, $this->_pluginResources[$resource]); + if (!$resourceName) { + throw new Zend_Application_Bootstrap_Exception(sprintf('Unable to resolve plugin "%s"; no corresponding plugin with that name', $resource)); + } + $resource = $resourceName; + } + return $this->_pluginResources[$resource]; + } + + foreach ($this->_pluginResources as $plugin => $spec) { + if ($spec instanceof Zend_Application_Resource_Resource) { + $pluginName = $this->_resolvePluginResourceName($spec); + if (0 === strcasecmp($resource, $pluginName)) { + unset($this->_pluginResources[$plugin]); + $this->_pluginResources[$pluginName] = $spec; + return $spec; + } + continue; + } + + if (false !== $pluginName = $this->_loadPluginResource($plugin, $spec)) { + if (0 === strcasecmp($resource, $pluginName)) { + return $this->_pluginResources[$pluginName]; + } + continue; + } + + if (class_exists($plugin) + && is_subclass_of($plugin, 'Zend_Application_Resource_Resource') + ) { //@SEE ZF-7550 + $spec = (array) $spec; + $spec['bootstrap'] = $this; + $instance = new $plugin($spec); + $pluginName = $this->_resolvePluginResourceName($instance); + unset($this->_pluginResources[$plugin]); + $this->_pluginResources[$pluginName] = $instance; + + if (0 === strcasecmp($resource, $pluginName)) { + return $instance; + } + } + } + + return null; + } + + /** + * Retrieve all plugin resources + * + * @return array + */ + public function getPluginResources() + { + foreach (array_keys($this->_pluginResources) as $resource) { + $this->getPluginResource($resource); + } + return $this->_pluginResources; + } + + /** + * Retrieve plugin resource names + * + * @return array + */ + public function getPluginResourceNames() + { + $this->getPluginResources(); + return array_keys($this->_pluginResources); + } + + /** + * Set plugin loader for loading resources + * + * @param Zend_Loader_PluginLoader_Interface $loader + * @return Zend_Application_Bootstrap_BootstrapAbstract + */ + public function setPluginLoader(Zend_Loader_PluginLoader_Interface $loader) + { + $this->_pluginLoader = $loader; + return $this; + } + + /** + * Get the plugin loader for resources + * + * @return Zend_Loader_PluginLoader_Interface + */ + public function getPluginLoader() + { + if ($this->_pluginLoader === null) { + $options = [ + 'Zend_Application_Resource' => 'Zend/Application/Resource', + 'ZendX_Application_Resource' => 'ZendX/Application/Resource' + ]; + + $this->_pluginLoader = new Zend_Loader_PluginLoader($options); + } + + return $this->_pluginLoader; + } + + /** + * Set application/parent bootstrap + * + * @param Zend_Application|Zend_Application_Bootstrap_Bootstrapper $application + * @return Zend_Application_Bootstrap_BootstrapAbstract + * @throws Zend_Application_Bootstrap_Exception + */ + public function setApplication($application) + { + if (($application instanceof Zend_Application) + || ($application instanceof Zend_Application_Bootstrap_Bootstrapper) + ) { + if ($application === $this) { + throw new Zend_Application_Bootstrap_Exception('Cannot set application to same object; creates recursion'); + } + $this->_application = $application; + } else { + throw new Zend_Application_Bootstrap_Exception('Invalid application provided to bootstrap constructor (received "' . get_class($application) . '" instance)'); + } + return $this; + } + + /** + * Retrieve parent application instance + * + * @return Zend_Application|Zend_Application_Bootstrap_Bootstrapper + */ + public function getApplication() + { + return $this->_application; + } + + /** + * Retrieve application environment + * + * @return string + */ + public function getEnvironment() + { + if (null === $this->_environment) { + $this->_environment = $this->getApplication()->getEnvironment(); + } + return $this->_environment; + } + + /** + * Set resource container + * + * By default, if a resource callback has a non-null return value, this + * value will be stored in a container using the resource name as the + * key. + * + * Containers must be objects, and must allow setting public properties. + * + * @param object $container + * @return Zend_Application_Bootstrap_BootstrapAbstract + * @throws Zend_Application_Bootstrap_Exception + */ + public function setContainer($container) + { + if (!is_object($container)) { + throw new Zend_Application_Bootstrap_Exception('Resource containers must be objects'); + } + $this->_container = $container; + return $this; + } + + /** + * Retrieve resource container + * + * @return object + */ + public function getContainer() + { + if (null === $this->_container) { + $this->setContainer(new Zend_Registry()); + } + return $this->_container; + } + + /** + * Determine if a resource has been stored in the container + * + * During bootstrap resource initialization, you may return a value. If + * you do, it will be stored in the {@link setContainer() container}. + * You can use this method to determine if a value was stored. + * + * @param string $name + * @return bool + */ + public function hasResource($name) + { + $resource = strtolower($name); + $container = $this->getContainer(); + return isset($container->{$resource}); + } + + /** + * Retrieve a resource from the container + * + * During bootstrap resource initialization, you may return a value. If + * you do, it will be stored in the {@link setContainer() container}. + * You can use this method to retrieve that value. + * + * If no value was returned, this will return a null value. + * + * @param string $name + * @return null|mixed + */ + public function getResource($name) + { + $resource = strtolower($name); + $container = $this->getContainer(); + if ($this->hasResource($resource)) { + return $container->{$resource}; + } + return null; + } + + /** + * Implement PHP's magic to retrieve a resource + * in the bootstrap + * + * @param string $prop + * @return null|mixed + */ + public function __get($prop) + { + return $this->getResource($prop); + } + + /** + * Implement PHP's magic to ask for the + * existence of a resource in the bootstrap + * + * @param string $prop + * @return bool + */ + public function __isset($prop) + { + return $this->hasResource($prop); + } + + /** + * Bootstrap individual, all, or multiple resources + * + * Marked as final to prevent issues when subclassing and naming the + * child class 'Bootstrap' (in which case, overriding this method + * would result in it being treated as a constructor). + * + * If you need to override this functionality, override the + * {@link _bootstrap()} method. + * + * @param null|string|array $resource + * @return Zend_Application_Bootstrap_BootstrapAbstract + * @throws Zend_Application_Bootstrap_Exception When invalid argument was passed + */ + final public function bootstrap($resource = null) + { + $this->_bootstrap($resource); + return $this; + } + + /** + * Overloading: intercept calls to bootstrap' + . $this->_figlet->render($this->getWord()) + . "\n"; + } +} diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Captcha/Image.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Captcha/Image.php new file mode 100644 index 0000000000..be58cc860b --- /dev/null +++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Captcha/Image.php @@ -0,0 +1,630 @@ +_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; + } + + /** + * Set start image + * + * @param string $startImage + * @return Zend_Captcha_Image + */ + public function setStartImage ($startImage) + { + $this->_startImage = $startImage; + return $this; + } + + /** + * Set dot noise level + * + * @param int $dotNoiseLevel + * @return Zend_Captcha_Image + */ + public function setDotNoiseLevel ($dotNoiseLevel) + { + $this->_dotNoiseLevel = $dotNoiseLevel; + return $this; + } + + /** + * Set line noise level + * + * @param int $lineNoiseLevel + * @return Zend_Captcha_Image + */ + public function setLineNoiseLevel ($lineNoiseLevel) + { + $this->_lineNoiseLevel = $lineNoiseLevel; + return $this; + } + + /** + * Set captcha expiration + * + * @param int $expiration + * @return Zend_Captcha_Image + */ + public function setExpiration($expiration) + { + $this->_expiration = $expiration; + return $this; + } + + /** + * Set garbage collection frequency + * + * @param int $gcFreq + * @return Zend_Captcha_Image + */ + public function setGcFreq($gcFreq) + { + $this->_gcFreq = $gcFreq; + return $this; + } + + /** + * Set captcha font + * + * @param string $font + * @return Zend_Captcha_Image + */ + public function setFont($font) + { + $this->_font = $font; + return $this; + } + + /** + * Set captcha font size + * + * @param int $fsize + * @return Zend_Captcha_Image + */ + public function setFontSize($fsize) + { + $this->_fsize = $fsize; + return $this; + } + + /** + * Set captcha image height + * + * @param int $height + * @return Zend_Captcha_Image + */ + public function setHeight($height) + { + $this->_height = $height; + return $this; + } + + /** + * Set captcha image storage directory + * + * @param string $imgDir + * @return Zend_Captcha_Image + */ + public function setImgDir($imgDir) + { + $this->_imgDir = rtrim($imgDir, "/\\") . '/'; + return $this; + } + + /** + * Set captcha image base URL + * + * @param string $imgUrl + * @return Zend_Captcha_Image + */ + public function setImgUrl($imgUrl) + { + $this->_imgUrl = rtrim($imgUrl, "/\\") . '/'; + return $this; + } + + /** + * Set image alternative text + * + * @param string $imgAlt + * @return Zend_Captcha_Image + */ + public function setImgAlt ($imgAlt) + { + $this->_imgAlt = $imgAlt; + return $this; + } + + /** + * Set captch image filename suffix + * + * @param string $suffix + * @return Zend_Captcha_Image + */ + public function setSuffix($suffix) + { + $this->_suffix = $suffix; + return $this; + } + + /** + * Set captcha image width + * + * @param int $width + * @return Zend_Captcha_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 Zend_Captcha_Exception + */ + protected function _generateImage($id, $word) + { + if (!extension_loaded("gd")) { + require_once 'Zend/Captcha/Exception.php'; + throw new Zend_Captcha_Exception("Image CAPTCHA requires GD extension"); + } + + if (!function_exists("imagepng")) { + require_once 'Zend/Captcha/Exception.php'; + throw new Zend_Captcha_Exception("Image CAPTCHA requires PNG support"); + } + + if (!function_exists("imageftbbox")) { + require_once 'Zend/Captcha/Exception.php'; + throw new Zend_Captcha_Exception("Image CAPTCHA requires FT fonts support"); + } + + $font = $this->getFont(); + + if (empty($font)) { + require_once 'Zend/Captcha/Exception.php'; + throw new Zend_Captcha_Exception("Image CAPTCHA requires font"); + } + + $w = $this->getWidth(); + $h = $this->getHeight(); + $fsize = $this->getFontSize(); + + $img_file = $this->getImgDir() . $id . $this->getSuffix(); + if(empty($this->_startImage)) { + $img = imagecreatetruecolor($w, $h); + } else { + $img = imagecreatefrompng($this->_startImage); + if(!$img) { + require_once 'Zend/Captcha/Exception.php'; + throw new Zend_Captcha_Exception("Can not load start image"); + } + $w = imagesx($img); + $h = imagesy($img); + } + $text_color = imagecolorallocate($img, 0, 0, 0); + $bg_color = imagecolorallocate($img, 255, 255, 255); + imagefilledrectangle($img, 0, 0, $w-1, $h-1, $bg_color); + $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, $text_color, $font, $word); + + // generate noise + for ($i=0; $i<$this->_dotNoiseLevel; $i++) { + imagefilledellipse($img, mt_rand(0,$w), mt_rand(0,$h), 2, 2, $text_color); + } + 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), $text_color); + } + + // transformed image + $img2 = imagecreatetruecolor($w, $h); + $bg_color = imagecolorallocate($img2, 255, 255, 255); + imagefilledrectangle($img2, 0, 0, $w-1, $h-1, $bg_color); + // 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; + } + + $color = (imagecolorat($img, (int)$sx, (int)$sy) >> 16) & 0xFF; + $color_x = (imagecolorat($img, (int)$sx + 1, (int)$sy) >> 16) & 0xFF; + $color_y = (imagecolorat($img, (int)$sx, (int)$sy + 1) >> 16) & 0xFF; + $color_xy = (imagecolorat($img, (int)$sx + 1, (int)$sy + 1) >> 16) & 0xFF; + + if ($color == 255 && $color_x == 255 && $color_y == 255 && $color_xy == 255) { + // ignore background + continue; + } + + if ($color == 0 && $color_x == 0 && $color_y == 0 && $color_xy == 0) { + // transfer inside of the image as-is + $newcolor = 0; + } else { + // do antialiasing for border items + $frac_x = $sx-floor($sx); + $frac_y = $sy-floor($sy); + $frac_x1 = 1-$frac_x; + $frac_y1 = 1-$frac_y; + + $newcolor = $color * $frac_x1 * $frac_y1 + + $color_x * $frac_x * $frac_y1 + + $color_y * $frac_x1 * $frac_y + + $color_xy * $frac_x * $frac_y; + } + imagesetpixel($img2, $x, $y, imagecolorallocate($img2, (int)$newcolor, (int)$newcolor, (int)$newcolor)); + } + } + + // generate noise + for ($i=0; $i<$this->_dotNoiseLevel; $i++) { + imagefilledellipse($img2, mt_rand(0,$w), mt_rand(0,$h), 2, 2, $text_color); + } + 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), $text_color); + } + + imagepng($img2, $img_file); + 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()); + } + } + } + } + } + + /** + * Display the captcha + * + * @param Zend_View_Interface $view + * @param mixed $element + * @return string + */ + public function render(Zend_View_Interface $view = null, $element = null) + { + $endTag = ' />'; + if (($view instanceof Zend_View_Abstract) && !$view->doctype()->isXhtml()) { + $endTag = '>'; + } + return '
+ * $text = "WHERE date < ?";
+ * $date = "2005-01-02";
+ * $safe = $sql->quoteInto($text, $date);
+ * // $safe = "WHERE date < '2005-01-02'"
+ *
+ *
+ * @param string $text The text with a placeholder.
+ * @param mixed $value The value to quote.
+ * @param string $type OPTIONAL SQL datatype
+ * @param integer $count OPTIONAL count of placeholders to replace
+ * @return string An SQL-safe quoted value placed into the original text.
+ */
+ public function quoteInto($text, $value, $type = null, $count = null)
+ {
+ if ($count === null) {
+ return str_replace('?', $this->quote($value, $type), $text);
+ } else {
+ return implode($this->quote($value, $type), explode('?', $text, $count + 1));
+ }
+ }
+
+ /**
+ * Quotes an identifier.
+ *
+ * Accepts a string representing a qualified indentifier. For Example:
+ *
+ * $adapter->quoteIdentifier('myschema.mytable')
+ *
+ * Returns: "myschema"."mytable"
+ *
+ * Or, an array of one or more identifiers that may form a qualified identifier:
+ *
+ * $adapter->quoteIdentifier(array('myschema','my.table'))
+ *
+ * Returns: "myschema"."my.table"
+ *
+ * The actual quote character surrounding the identifiers may vary depending on
+ * the adapter.
+ *
+ * @param string|array|Zend_Db_Expr $ident The identifier.
+ * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
+ * @return string The quoted identifier.
+ */
+ public function quoteIdentifier($ident, $auto=false)
+ {
+ return $this->_quoteIdentifierAs($ident, null, $auto);
+ }
+
+ /**
+ * Quote a column identifier and alias.
+ *
+ * @param string|array|Zend_Db_Expr $ident The identifier or expression.
+ * @param string $alias An alias for the column.
+ * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
+ * @return string The quoted identifier and alias.
+ */
+ public function quoteColumnAs($ident, $alias, $auto=false)
+ {
+ return $this->_quoteIdentifierAs($ident, $alias, $auto);
+ }
+
+ /**
+ * Quote a table identifier and alias.
+ *
+ * @param string|array|Zend_Db_Expr $ident The identifier or expression.
+ * @param string $alias An alias for the table.
+ * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
+ * @return string The quoted identifier and alias.
+ */
+ public function quoteTableAs($ident, $alias = null, $auto = false)
+ {
+ return $this->_quoteIdentifierAs($ident, $alias, $auto);
+ }
+
+ /**
+ * Quote an identifier and an optional alias.
+ *
+ * @param string|array|Zend_Db_Expr $ident The identifier or expression.
+ * @param string $alias An optional alias.
+ * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
+ * @param string $as The string to add between the identifier/expression and the alias.
+ * @return string The quoted identifier and alias.
+ */
+ protected function _quoteIdentifierAs($ident, $alias = null, $auto = false, $as = ' AS ')
+ {
+ if ($ident instanceof Zend_Db_Expr) {
+ $quoted = $ident->__toString();
+ } elseif ($ident instanceof Zend_Db_Select) {
+ $quoted = '(' . $ident->assemble() . ')';
+ } else {
+ if (is_string($ident)) {
+ $ident = explode('.', $ident);
+ }
+ if (is_array($ident)) {
+ $segments = [];
+ foreach ($ident as $segment) {
+ if ($segment instanceof Zend_Db_Expr) {
+ $segments[] = $segment->__toString();
+ } else {
+ $segments[] = $this->_quoteIdentifier($segment, $auto);
+ }
+ }
+ if ($alias !== null && end($ident) == $alias) {
+ $alias = null;
+ }
+ $quoted = implode('.', $segments);
+ } else {
+ $quoted = $this->_quoteIdentifier($ident, $auto);
+ }
+ }
+ if ($alias !== null) {
+ $quoted .= $as . $this->_quoteIdentifier($alias, $auto);
+ }
+ return $quoted;
+ }
+
+ /**
+ * Quote an identifier.
+ *
+ * @param string $value The identifier or expression.
+ * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
+ * @return string The quoted identifier and alias.
+ */
+ protected function _quoteIdentifier($value, $auto=false)
+ {
+ if ($auto === false || $this->_autoQuoteIdentifiers === true) {
+ $q = $this->getQuoteIdentifierSymbol();
+ return ($q . str_replace("$q", "$q$q", $value) . $q);
+ }
+ return $value;
+ }
+
+ /**
+ * Returns the symbol the adapter uses for delimited identifiers.
+ *
+ * @return string
+ */
+ public function getQuoteIdentifierSymbol()
+ {
+ return '"';
+ }
+
+ /**
+ * Return the most recent value from the specified sequence in the database.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ return null;
+ }
+
+ /**
+ * Generate a new value from the specified sequence in the database, and return it.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ return null;
+ }
+
+ /**
+ * Helper method to change the case of the strings used
+ * when returning result sets in FETCH_ASSOC and FETCH_BOTH
+ * modes.
+ *
+ * This is not intended to be used by application code,
+ * but the method must be public so the Statement class
+ * can invoke it.
+ *
+ * @param string $key
+ * @return string
+ */
+ public function foldCase($key)
+ {
+ switch ($this->_caseFolding) {
+ case Zend_Db::CASE_LOWER:
+ $value = strtolower((string) $key);
+ break;
+ case Zend_Db::CASE_UPPER:
+ $value = strtoupper((string) $key);
+ break;
+ case Zend_Db::CASE_NATURAL:
+ default:
+ $value = (string) $key;
+ }
+ return $value;
+ }
+
+ /**
+ * called when object is getting serialized
+ * This disconnects the DB object that cant be serialized
+ *
+ * @throws Zend_Db_Adapter_Exception
+ * @return array
+ */
+ public function __sleep()
+ {
+ if ($this->_allowSerialization == false) {
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception(
+ get_class($this) . ' is not allowed to be serialized'
+ );
+ }
+ $this->_connection = null;
+
+ return array_keys(
+ array_diff_key(get_object_vars($this), ['_connection' => null])
+ );
+ }
+
+ /**
+ * called when object is getting unserialized
+ *
+ * @return void
+ */
+ public function __wakeup()
+ {
+ if ($this->_autoReconnectOnUnserialize == true) {
+ $this->getConnection();
+ }
+ }
+
+ /**
+ * Abstract Methods
+ */
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ abstract public function listTables();
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ abstract public function describeTable($tableName, $schemaName = null);
+
+ /**
+ * Creates a connection to the database.
+ *
+ * @return void
+ */
+ abstract protected function _connect();
+
+ /**
+ * Test if a connection is active
+ *
+ * @return boolean
+ */
+ abstract public function isConnected();
+
+ /**
+ * Force the connection to close.
+ *
+ * @return void
+ */
+ abstract public function closeConnection();
+
+ /**
+ * Prepare a statement and return a PDOStatement-like object.
+ *
+ * @param string|Zend_Db_Select $sql SQL query
+ * @return Zend_Db_Statement|PDOStatement
+ */
+ abstract public function prepare($sql);
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ */
+ abstract public function lastInsertId($tableName = null, $primaryKey = null);
+
+ /**
+ * Begin a transaction.
+ */
+ abstract protected function _beginTransaction();
+
+ /**
+ * Commit a transaction.
+ */
+ abstract protected function _commit();
+
+ /**
+ * Roll-back a transaction.
+ */
+ abstract protected function _rollBack();
+
+ /**
+ * Set the fetch mode.
+ *
+ * @param integer $mode
+ * @return void
+ * @throws Zend_Db_Adapter_Exception
+ */
+ abstract public function setFetchMode($mode);
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param mixed $sql
+ * @param integer $count
+ * @param integer $offset
+ * @return string
+ */
+ abstract public function limit($sql, $count, $offset = 0);
+
+ /**
+ * Check if the adapter supports real SQL parameters.
+ *
+ * @param string $type 'positional' or 'named'
+ * @return bool
+ */
+ abstract public function supportsParameters($type);
+
+ /**
+ * Retrieve server version in PHP style
+ *
+ * @return string
+ */
+ abstract public function getServerVersion();
+}
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Db2.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Db2.php
new file mode 100644
index 0000000000..a79e7201e2
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Db2.php
@@ -0,0 +1,839 @@
+ (string) Connect to the database as this username.
+ * password => (string) Password associated with the username.
+ * host => (string) What host to connect to (default 127.0.0.1)
+ * dbname => (string) The name of the database to user
+ * protocol => (string) Protocol to use, defaults to "TCPIP"
+ * port => (integer) Port number to use for TCP/IP if protocol is "TCPIP"
+ * persistent => (boolean) Set TRUE to use a persistent connection (db2_pconnect)
+ * os => (string) This should be set to 'i5' if the db is on an os400/i5
+ * schema => (string) The default schema the connection should use
+ *
+ * @var array
+ */
+ protected $_config = [
+ 'dbname' => null,
+ 'username' => null,
+ 'password' => null,
+ 'host' => 'localhost',
+ 'port' => '50000',
+ 'protocol' => 'TCPIP',
+ 'persistent' => false,
+ 'os' => null,
+ 'schema' => null
+ ];
+
+ /**
+ * Execution mode
+ *
+ * @var int execution flag (DB2_AUTOCOMMIT_ON or DB2_AUTOCOMMIT_OFF)
+ */
+ protected $_execute_mode = DB2_AUTOCOMMIT_ON;
+
+ /**
+ * Default class name for a DB statement.
+ *
+ * @var string
+ */
+ protected $_defaultStmtClass = 'Zend_Db_Statement_Db2';
+ protected $_isI5 = false;
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = [
+ Zend_Db::INT_TYPE => Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'INTEGER' => Zend_Db::INT_TYPE,
+ 'SMALLINT' => Zend_Db::INT_TYPE,
+ 'BIGINT' => Zend_Db::BIGINT_TYPE,
+ 'DECIMAL' => Zend_Db::FLOAT_TYPE,
+ 'NUMERIC' => Zend_Db::FLOAT_TYPE
+ ];
+
+ /**
+ * Creates a connection resource.
+ *
+ * @return void
+ */
+ protected function _connect()
+ {
+ if (is_resource($this->_connection)) {
+ // connection already exists
+ return;
+ }
+
+ if (!extension_loaded('ibm_db2')) {
+ /**
+ * @see Zend_Db_Adapter_Db2_Exception
+ */
+ require_once 'Zend/Db/Adapter/Db2/Exception.php';
+ throw new Zend_Db_Adapter_Db2_Exception('The IBM DB2 extension is required for this adapter but the extension is not loaded');
+ }
+
+ $this->_determineI5();
+ if ($this->_config['persistent']) {
+ // use persistent connection
+ $conn_func_name = 'db2_pconnect';
+ } else {
+ // use "normal" connection
+ $conn_func_name = 'db2_connect';
+ }
+
+ if (!isset($this->_config['driver_options']['autocommit'])) {
+ // set execution mode
+ $this->_config['driver_options']['autocommit'] = &$this->_execute_mode;
+ }
+
+ if (isset($this->_config['options'][Zend_Db::CASE_FOLDING])) {
+ $caseAttrMap = [
+ Zend_Db::CASE_NATURAL => DB2_CASE_NATURAL,
+ Zend_Db::CASE_UPPER => DB2_CASE_UPPER,
+ Zend_Db::CASE_LOWER => DB2_CASE_LOWER
+ ];
+ $this->_config['driver_options']['DB2_ATTR_CASE'] = $caseAttrMap[$this->_config['options'][Zend_Db::CASE_FOLDING]];
+ }
+
+ if ($this->_isI5 && isset($this->_config['driver_options']['i5_naming'])) {
+ if ($this->_config['driver_options']['i5_naming']) {
+ $this->_config['driver_options']['i5_naming'] = DB2_I5_NAMING_ON;
+ } else {
+ $this->_config['driver_options']['i5_naming'] = DB2_I5_NAMING_OFF;
+ }
+ }
+
+ if ($this->_config['host'] !== 'localhost' && !$this->_isI5) {
+ // if the host isn't localhost, use extended connection params
+ $dbname = 'DRIVER={IBM DB2 ODBC DRIVER}' .
+ ';DATABASE=' . $this->_config['dbname'] .
+ ';HOSTNAME=' . $this->_config['host'] .
+ ';PORT=' . $this->_config['port'] .
+ ';PROTOCOL=' . $this->_config['protocol'] .
+ ';UID=' . $this->_config['username'] .
+ ';PWD=' . $this->_config['password'] .';';
+ $this->_connection = $conn_func_name(
+ $dbname,
+ null,
+ null,
+ $this->_config['driver_options']
+ );
+ } else {
+ // host is localhost, so use standard connection params
+ $this->_connection = $conn_func_name(
+ $this->_config['dbname'],
+ $this->_config['username'],
+ $this->_config['password'],
+ $this->_config['driver_options']
+ );
+ }
+
+ // check the connection
+ if (!$this->_connection) {
+ /**
+ * @see Zend_Db_Adapter_Db2_Exception
+ */
+ require_once 'Zend/Db/Adapter/Db2/Exception.php';
+ throw new Zend_Db_Adapter_Db2_Exception(db2_conn_errormsg(), db2_conn_error());
+ }
+ }
+
+ /**
+ * Test if a connection is active
+ *
+ * @return boolean
+ */
+ public function isConnected()
+ {
+ return ((bool) (is_resource($this->_connection)
+ && get_resource_type($this->_connection) == 'DB2 Connection'));
+ }
+
+ /**
+ * Force the connection to close.
+ *
+ * @return void
+ */
+ public function closeConnection()
+ {
+ if ($this->isConnected()) {
+ db2_close($this->_connection);
+ }
+ $this->_connection = null;
+ }
+
+ /**
+ * Returns an SQL statement for preparation.
+ *
+ * @param string $sql The SQL statement with placeholders.
+ * @return Zend_Db_Statement_Db2
+ */
+ public function prepare($sql)
+ {
+ $this->_connect();
+ $stmtClass = $this->_defaultStmtClass;
+ if (!class_exists($stmtClass)) {
+ require_once 'Zend/Loader.php';
+ Zend_Loader::loadClass($stmtClass);
+ }
+ $stmt = new $stmtClass($this, $sql);
+ $stmt->setFetchMode($this->_fetchMode);
+ return $stmt;
+ }
+
+ /**
+ * Gets the execution mode
+ *
+ * @return int the execution mode (DB2_AUTOCOMMIT_ON or DB2_AUTOCOMMIT_OFF)
+ */
+ public function _getExecuteMode()
+ {
+ return $this->_execute_mode;
+ }
+
+ /**
+ * @param integer $mode
+ * @return void
+ */
+ public function _setExecuteMode($mode)
+ {
+ switch ($mode) {
+ case DB2_AUTOCOMMIT_OFF:
+ case DB2_AUTOCOMMIT_ON:
+ $this->_execute_mode = $mode;
+ db2_autocommit($this->_connection, $mode);
+ break;
+ default:
+ /**
+ * @see Zend_Db_Adapter_Db2_Exception
+ */
+ require_once 'Zend/Db/Adapter/Db2/Exception.php';
+ throw new Zend_Db_Adapter_Db2_Exception("execution mode not supported");
+ break;
+ }
+ }
+
+ /**
+ * Quote a raw string.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (is_int($value) || is_float($value)) {
+ return $value;
+ }
+ /**
+ * Use db2_escape_string() if it is present in the IBM DB2 extension.
+ * But some supported versions of PHP do not include this function,
+ * so fall back to default quoting in the parent class.
+ */
+ if (function_exists('db2_escape_string')) {
+ return "'" . db2_escape_string($value) . "'";
+ }
+ return parent::_quote($value);
+ }
+
+ /**
+ * @return string
+ */
+ public function getQuoteIdentifierSymbol()
+ {
+ $this->_connect();
+ $info = db2_server_info($this->_connection);
+ if ($info) {
+ $identQuote = $info->IDENTIFIER_QUOTE_CHAR;
+ } else {
+ // db2_server_info() does not return result on some i5 OS version
+ if ($this->_isI5) {
+ $identQuote ="'";
+ }
+ }
+ return $identQuote;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ * @param string $schema OPTIONAL
+ * @return array
+ */
+ public function listTables($schema = null)
+ {
+ $this->_connect();
+
+ if ($schema === null && $this->_config['schema'] != null) {
+ $schema = $this->_config['schema'];
+ }
+
+ $tables = [];
+
+ if (!$this->_isI5) {
+ if ($schema) {
+ $stmt = db2_tables($this->_connection, null, $schema);
+ } else {
+ $stmt = db2_tables($this->_connection);
+ }
+ while ($row = db2_fetch_assoc($stmt)) {
+ $tables[] = $row['TABLE_NAME'];
+ }
+ } else {
+ $tables = $this->_i5listTables($schema);
+ }
+
+ return $tables;
+ }
+
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * DB2 not supports UNSIGNED integer.
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ // Ensure the connection is made so that _isI5 is set
+ $this->_connect();
+
+ if ($schemaName === null && $this->_config['schema'] != null) {
+ $schemaName = $this->_config['schema'];
+ }
+
+ if (!$this->_isI5) {
+
+ $sql = "SELECT DISTINCT c.tabschema, c.tabname, c.colname, c.colno,
+ c.typename, c.default, c.nulls, c.length, c.scale,
+ c.identity, tc.type AS tabconsttype, k.colseq
+ FROM syscat.columns c
+ LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc
+ ON (k.tabschema = tc.tabschema
+ AND k.tabname = tc.tabname
+ AND tc.type = 'P'))
+ ON (c.tabschema = k.tabschema
+ AND c.tabname = k.tabname
+ AND c.colname = k.colname)
+ WHERE "
+ . $this->quoteInto('UPPER(c.tabname) = UPPER(?)', $tableName);
+
+ if ($schemaName) {
+ $sql .= $this->quoteInto(' AND UPPER(c.tabschema) = UPPER(?)', $schemaName);
+ }
+
+ $sql .= " ORDER BY c.colno";
+
+ } else {
+
+ // DB2 On I5 specific query
+ $sql = "SELECT DISTINCT C.TABLE_SCHEMA, C.TABLE_NAME, C.COLUMN_NAME, C.ORDINAL_POSITION,
+ C.DATA_TYPE, C.COLUMN_DEFAULT, C.NULLS ,C.LENGTH, C.SCALE, LEFT(C.IDENTITY,1),
+ LEFT(tc.TYPE, 1) AS tabconsttype, k.COLSEQ
+ FROM QSYS2.SYSCOLUMNS C
+ LEFT JOIN (QSYS2.syskeycst k JOIN QSYS2.SYSCST tc
+ ON (k.TABLE_SCHEMA = tc.TABLE_SCHEMA
+ AND k.TABLE_NAME = tc.TABLE_NAME
+ AND LEFT(tc.type,1) = 'P'))
+ ON (C.TABLE_SCHEMA = k.TABLE_SCHEMA
+ AND C.TABLE_NAME = k.TABLE_NAME
+ AND C.COLUMN_NAME = k.COLUMN_NAME)
+ WHERE "
+ . $this->quoteInto('UPPER(C.TABLE_NAME) = UPPER(?)', $tableName);
+
+ if ($schemaName) {
+ $sql .= $this->quoteInto(' AND UPPER(C.TABLE_SCHEMA) = UPPER(?)', $schemaName);
+ }
+
+ $sql .= " ORDER BY C.ORDINAL_POSITION FOR FETCH ONLY";
+ }
+
+ $desc = [];
+ $stmt = $this->query($sql);
+
+ /**
+ * To avoid case issues, fetch using FETCH_NUM
+ */
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ /**
+ * The ordering of columns is defined by the query so we can map
+ * to variables to improve readability
+ */
+ $tabschema = 0;
+ $tabname = 1;
+ $colname = 2;
+ $colno = 3;
+ $typename = 4;
+ $default = 5;
+ $nulls = 6;
+ $length = 7;
+ $scale = 8;
+ $identityCol = 9;
+ $tabconstType = 10;
+ $colseq = 11;
+
+ foreach ($result as $key => $row) {
+ list ($primary, $primaryPosition, $identity) = [false, null, false];
+ if ($row[$tabconstType] == 'P') {
+ $primary = true;
+ $primaryPosition = $row[$colseq];
+ }
+ /**
+ * In IBM DB2, an column can be IDENTITY
+ * even if it is not part of the PRIMARY KEY.
+ */
+ if ($row[$identityCol] == 'Y') {
+ $identity = true;
+ }
+
+ // only colname needs to be case adjusted
+ $desc[$this->foldCase($row[$colname])] = [
+ 'SCHEMA_NAME' => $this->foldCase($row[$tabschema]),
+ 'TABLE_NAME' => $this->foldCase($row[$tabname]),
+ 'COLUMN_NAME' => $this->foldCase($row[$colname]),
+ 'COLUMN_POSITION' => (!$this->_isI5) ? $row[$colno]+1 : $row[$colno],
+ 'DATA_TYPE' => $row[$typename],
+ 'DEFAULT' => $row[$default],
+ 'NULLABLE' => (bool) ($row[$nulls] == 'Y'),
+ 'LENGTH' => $row[$length],
+ 'SCALE' => $row[$scale],
+ 'PRECISION' => ($row[$typename] == 'DECIMAL' ? $row[$length] : 0),
+ 'UNSIGNED' => false,
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ ];
+ }
+
+ return $desc;
+ }
+
+ /**
+ * Return the most recent value from the specified sequence in the database.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $this->_connect();
+
+ if (!$this->_isI5) {
+ $quotedSequenceName = $this->quoteIdentifier($sequenceName, true);
+ $sql = 'SELECT PREVVAL FOR ' . $quotedSequenceName . ' AS VAL FROM SYSIBM.SYSDUMMY1';
+ } else {
+ $quotedSequenceName = $sequenceName;
+ $sql = 'SELECT PREVVAL FOR ' . $this->quoteIdentifier($sequenceName, true) . ' AS VAL FROM QSYS2.QSQPTABL';
+ }
+
+ $value = $this->fetchOne($sql);
+ return (string) $value;
+ }
+
+ /**
+ * Generate a new value from the specified sequence in the database, and return it.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $sql = 'SELECT NEXTVAL FOR '.$this->quoteIdentifier($sequenceName, true).' AS VAL FROM SYSIBM.SYSDUMMY1';
+ $value = $this->fetchOne($sql);
+ return (string) $value;
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * The IDENTITY_VAL_LOCAL() function gives the last generated identity value
+ * in the current process, even if it was for a GENERATED column.
+ *
+ * @param string $tableName OPTIONAL
+ * @param string $primaryKey OPTIONAL
+ * @param string $idType OPTIONAL used for i5 platform to define sequence/idenity unique value
+ * @return string
+ */
+
+ public function lastInsertId($tableName = null, $primaryKey = null, $idType = null)
+ {
+ $this->_connect();
+
+ if ($this->_isI5) {
+ return (string) $this->_i5LastInsertId($tableName, $idType);
+ }
+
+ if ($tableName !== null) {
+ $sequenceName = $tableName;
+ if ($primaryKey) {
+ $sequenceName .= "_$primaryKey";
+ }
+ $sequenceName .= '_seq';
+ return $this->lastSequenceId($sequenceName);
+ }
+
+ $sql = 'SELECT IDENTITY_VAL_LOCAL() AS VAL FROM SYSIBM.SYSDUMMY1';
+ $value = $this->fetchOne($sql);
+ return (string) $value;
+ }
+
+ /**
+ * Begin a transaction.
+ *
+ * @return void
+ */
+ protected function _beginTransaction()
+ {
+ $this->_setExecuteMode(DB2_AUTOCOMMIT_OFF);
+ }
+
+ /**
+ * Commit a transaction.
+ *
+ * @return void
+ */
+ protected function _commit()
+ {
+ if (!db2_commit($this->_connection)) {
+ /**
+ * @see Zend_Db_Adapter_Db2_Exception
+ */
+ require_once 'Zend/Db/Adapter/Db2/Exception.php';
+ throw new Zend_Db_Adapter_Db2_Exception(
+ db2_conn_errormsg($this->_connection),
+ db2_conn_error($this->_connection));
+ }
+
+ $this->_setExecuteMode(DB2_AUTOCOMMIT_ON);
+ }
+
+ /**
+ * Rollback a transaction.
+ *
+ * @return void
+ */
+ protected function _rollBack()
+ {
+ if (!db2_rollback($this->_connection)) {
+ /**
+ * @see Zend_Db_Adapter_Db2_Exception
+ */
+ require_once 'Zend/Db/Adapter/Db2/Exception.php';
+ throw new Zend_Db_Adapter_Db2_Exception(
+ db2_conn_errormsg($this->_connection),
+ db2_conn_error($this->_connection));
+ }
+ $this->_setExecuteMode(DB2_AUTOCOMMIT_ON);
+ }
+
+ /**
+ * Set the fetch mode.
+ *
+ * @param integer $mode
+ * @return void
+ * @throws Zend_Db_Adapter_Db2_Exception
+ */
+ public function setFetchMode($mode)
+ {
+ switch ($mode) {
+ case Zend_Db::FETCH_NUM: // seq array
+ case Zend_Db::FETCH_ASSOC: // assoc array
+ case Zend_Db::FETCH_BOTH: // seq+assoc array
+ case Zend_Db::FETCH_OBJ: // object
+ $this->_fetchMode = $mode;
+ break;
+ case Zend_Db::FETCH_BOUND: // bound to PHP variable
+ /**
+ * @see Zend_Db_Adapter_Db2_Exception
+ */
+ require_once 'Zend/Db/Adapter/Db2/Exception.php';
+ throw new Zend_Db_Adapter_Db2_Exception('FETCH_BOUND is not supported yet');
+ break;
+ default:
+ /**
+ * @see Zend_Db_Adapter_Db2_Exception
+ */
+ require_once 'Zend/Db/Adapter/Db2/Exception.php';
+ throw new Zend_Db_Adapter_Db2_Exception("Invalid fetch mode '$mode' specified");
+ break;
+ }
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = (int)$count;
+ if ($count <= 0) {
+ /**
+ * @see Zend_Db_Adapter_Db2_Exception
+ */
+ require_once 'Zend/Db/Adapter/Db2/Exception.php';
+ throw new Zend_Db_Adapter_Db2_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = (int)$offset;
+
+ if ($offset < 0) {
+ /**
+ * @see Zend_Db_Adapter_Db2_Exception
+ */
+ require_once 'Zend/Db/Adapter/Db2/Exception.php';
+ throw new Zend_Db_Adapter_Db2_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ if ($offset === 0) {
+ return $sql . " FETCH FIRST $count ROWS ONLY";
+ }
+
+ /**
+ * DB2 does not implement the LIMIT clause as some RDBMS do.
+ * We have to simulate it with subqueries and ROWNUM.
+ * Unfortunately because we use the column wildcard "*",
+ * this puts an extra column into the query result set.
+ */
+ return "SELECT z2.*
+ FROM (
+ SELECT ROW_NUMBER() OVER() AS \"ZEND_DB_ROWNUM\", z1.*
+ FROM (
+ " . $sql . "
+ ) z1
+ ) z2
+ WHERE z2.zend_db_rownum BETWEEN " . ($offset+1) . " AND " . ($offset+$count);
+ }
+
+ /**
+ * Check if the adapter supports real SQL parameters.
+ *
+ * @param string $type 'positional' or 'named'
+ * @return bool
+ */
+ public function supportsParameters($type)
+ {
+ if ($type == 'positional') {
+ return true;
+ }
+
+ // if its 'named' or anything else
+ return false;
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ *
+ * @return string
+ */
+ public function getServerVersion()
+ {
+ $this->_connect();
+ $server_info = db2_server_info($this->_connection);
+ if ($server_info !== false) {
+ $version = $server_info->DBMS_VER;
+ if ($this->_isI5) {
+ $version = (int) substr($version, 0, 2) . '.' . (int) substr($version, 2, 2) . '.' . (int) substr($version, 4);
+ }
+ return $version;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return whether or not this is running on i5
+ *
+ * @return bool
+ */
+ public function isI5()
+ {
+ if ($this->_isI5 === null) {
+ $this->_determineI5();
+ }
+
+ return (bool) $this->_isI5;
+ }
+
+ /**
+ * Check the connection parameters according to verify
+ * type of used OS
+ *
+ * @return void
+ */
+ protected function _determineI5()
+ {
+ // first us the compiled flag.
+ $this->_isI5 = (php_uname('s') == 'OS400') ? true : false;
+
+ // if this is set, then us it
+ if (isset($this->_config['os'])){
+ if (strtolower($this->_config['os']) === 'i5') {
+ $this->_isI5 = true;
+ } else {
+ // any other value passed in, its null
+ $this->_isI5 = false;
+ }
+ }
+
+ }
+
+ /**
+ * Db2 On I5 specific method
+ *
+ * Returns a list of the tables in the database .
+ * Used only for DB2/400.
+ *
+ * @return array
+ */
+ protected function _i5listTables($schema = null)
+ {
+ //list of i5 libraries.
+ $tables = [];
+ if ($schema) {
+ $tablesStatement = db2_tables($this->_connection, null, $schema);
+ while ($rowTables = db2_fetch_assoc($tablesStatement) ) {
+ if ($rowTables['TABLE_NAME'] !== null) {
+ $tables[] = $rowTables['TABLE_NAME'];
+ }
+ }
+ } else {
+ $schemaStatement = db2_tables($this->_connection);
+ while ($schema = db2_fetch_assoc($schemaStatement)) {
+ if ($schema['TABLE_SCHEM'] !== null) {
+ // list of the tables which belongs to the selected library
+ $tablesStatement = db2_tables($this->_connection, NULL, $schema['TABLE_SCHEM']);
+ if (is_resource($tablesStatement)) {
+ while ($rowTables = db2_fetch_assoc($tablesStatement) ) {
+ if ($rowTables['TABLE_NAME'] !== null) {
+ $tables[] = $rowTables['TABLE_NAME'];
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return $tables;
+ }
+
+ protected function _i5LastInsertId($objectName = null, $idType = null)
+ {
+
+ if ($objectName === null) {
+ $sql = 'SELECT IDENTITY_VAL_LOCAL() AS VAL FROM QSYS2.QSQPTABL';
+
+ return $this->fetchOne($sql);
+ }
+
+ if (strtoupper($idType) === 'S'){
+ //check i5_lib option
+ $sequenceName = $objectName;
+ return $this->lastSequenceId($sequenceName);
+ }
+
+ //returns last identity value for the specified table
+ //if (strtoupper($idType) === 'I') {
+ $tableName = $objectName;
+ return $this->fetchOne('SELECT IDENTITY_VAL_LOCAL() from ' . $this->quoteIdentifier($tableName));
+ }
+
+}
+
+
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Db2/Exception.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Db2/Exception.php
new file mode 100644
index 0000000000..a033a321b1
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Db2/Exception.php
@@ -0,0 +1,45 @@
+getCode();
+ }
+ parent::__construct($message, $code, $e);
+ }
+
+ public function hasChainedException()
+ {
+ return ($this->getPrevious() !== null);
+ }
+
+ public function getChainedException()
+ {
+ return $this->getPrevious();
+ }
+
+}
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Mysqli.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Mysqli.php
new file mode 100644
index 0000000000..b3cc5013ca
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Mysqli.php
@@ -0,0 +1,556 @@
+ Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'INT' => Zend_Db::INT_TYPE,
+ 'INTEGER' => Zend_Db::INT_TYPE,
+ 'MEDIUMINT' => Zend_Db::INT_TYPE,
+ 'SMALLINT' => Zend_Db::INT_TYPE,
+ 'TINYINT' => Zend_Db::INT_TYPE,
+ 'BIGINT' => Zend_Db::BIGINT_TYPE,
+ 'SERIAL' => Zend_Db::BIGINT_TYPE,
+ 'DEC' => Zend_Db::FLOAT_TYPE,
+ 'DECIMAL' => Zend_Db::FLOAT_TYPE,
+ 'DOUBLE' => Zend_Db::FLOAT_TYPE,
+ 'DOUBLE PRECISION' => Zend_Db::FLOAT_TYPE,
+ 'FIXED' => Zend_Db::FLOAT_TYPE,
+ 'FLOAT' => Zend_Db::FLOAT_TYPE
+ ];
+
+ /**
+ * @var Zend_Db_Statement_Mysqli
+ */
+ protected $_stmt = null;
+
+ /**
+ * Default class name for a DB statement.
+ *
+ * @var string
+ */
+ protected $_defaultStmtClass = 'Zend_Db_Statement_Mysqli';
+
+ /**
+ * Quote a raw string.
+ *
+ * @param mixed $value Raw string
+ *
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (is_int($value) || is_float($value)) {
+ return $value;
+ }
+ $this->_connect();
+ return "'" . $this->_connection->real_escape_string($value) . "'";
+ }
+
+ /**
+ * Returns the symbol the adapter uses for delimiting identifiers.
+ *
+ * @return string
+ */
+ public function getQuoteIdentifierSymbol()
+ {
+ return "`";
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $result = [];
+ // Use mysqli extension API, because SHOW doesn't work
+ // well as a prepared statement on MySQL 4.1.
+ $sql = 'SHOW TABLES';
+ if ($queryResult = $this->getConnection()->query($sql)) {
+ while ($row = $queryResult->fetch_row()) {
+ $result[] = $row[0];
+ }
+ $queryResult->close();
+ } else {
+ /**
+ * @see Zend_Db_Adapter_Mysqli_Exception
+ */
+ require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
+ throw new Zend_Db_Adapter_Mysqli_Exception($this->getConnection()->error);
+ }
+ return $result;
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ /**
+ * @todo use INFORMATION_SCHEMA someday when
+ * MySQL's implementation isn't too slow.
+ */
+
+ if ($schemaName) {
+ $sql = 'DESCRIBE ' . $this->quoteIdentifier("$schemaName.$tableName", true);
+ } else {
+ $sql = 'DESCRIBE ' . $this->quoteIdentifier($tableName, true);
+ }
+
+ /**
+ * Use mysqli extension API, because DESCRIBE doesn't work
+ * well as a prepared statement on MySQL 4.1.
+ */
+ if ($queryResult = $this->getConnection()->query($sql)) {
+ while ($row = $queryResult->fetch_assoc()) {
+ $result[] = $row;
+ }
+ $queryResult->close();
+ } else {
+ /**
+ * @see Zend_Db_Adapter_Mysqli_Exception
+ */
+ require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
+ throw new Zend_Db_Adapter_Mysqli_Exception($this->getConnection()->error);
+ }
+
+ $desc = [];
+
+ $row_defaults = [
+ 'Length' => null,
+ 'Scale' => null,
+ 'Precision' => null,
+ 'Unsigned' => null,
+ 'Primary' => false,
+ 'PrimaryPosition' => null,
+ 'Identity' => false
+ ];
+ $i = 1;
+ $p = 1;
+ foreach ($result as $key => $row) {
+ $row = array_merge($row_defaults, $row);
+ if (preg_match('/unsigned/', $row['Type'])) {
+ $row['Unsigned'] = true;
+ }
+ if (preg_match('/^((?:var)?char)\((\d+)\)/', $row['Type'], $matches)) {
+ $row['Type'] = $matches[1];
+ $row['Length'] = $matches[2];
+ } else if (preg_match('/^decimal\((\d+),(\d+)\)/', $row['Type'], $matches)) {
+ $row['Type'] = 'decimal';
+ $row['Precision'] = $matches[1];
+ $row['Scale'] = $matches[2];
+ } else if (preg_match('/^float\((\d+),(\d+)\)/', $row['Type'], $matches)) {
+ $row['Type'] = 'float';
+ $row['Precision'] = $matches[1];
+ $row['Scale'] = $matches[2];
+ } else if (preg_match('/^((?:big|medium|small|tiny)?int)\((\d+)\)/', $row['Type'], $matches)) {
+ $row['Type'] = $matches[1];
+ /**
+ * The optional argument of a MySQL int type is not precision
+ * or length; it is only a hint for display width.
+ */
+ }
+ if (strtoupper($row['Key']) == 'PRI') {
+ $row['Primary'] = true;
+ $row['PrimaryPosition'] = $p;
+ if ($row['Extra'] == 'auto_increment') {
+ $row['Identity'] = true;
+ } else {
+ $row['Identity'] = false;
+ }
+ ++$p;
+ }
+ $desc[$this->foldCase($row['Field'])] = [
+ 'SCHEMA_NAME' => null, // @todo
+ 'TABLE_NAME' => $this->foldCase($tableName),
+ 'COLUMN_NAME' => $this->foldCase($row['Field']),
+ 'COLUMN_POSITION' => $i,
+ 'DATA_TYPE' => $row['Type'],
+ 'DEFAULT' => $row['Default'],
+ 'NULLABLE' => (bool) ($row['Null'] == 'YES'),
+ 'LENGTH' => $row['Length'],
+ 'SCALE' => $row['Scale'],
+ 'PRECISION' => $row['Precision'],
+ 'UNSIGNED' => $row['Unsigned'],
+ 'PRIMARY' => $row['Primary'],
+ 'PRIMARY_POSITION' => $row['PrimaryPosition'],
+ 'IDENTITY' => $row['Identity']
+ ];
+ ++$i;
+ }
+ return $desc;
+ }
+
+ /**
+ * Creates a connection to the database.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Mysqli_Exception
+ */
+ protected function _connect()
+ {
+ if ($this->_connection) {
+ return;
+ }
+
+ if (!extension_loaded('mysqli')) {
+ /**
+ * @see Zend_Db_Adapter_Mysqli_Exception
+ */
+ require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
+ throw new Zend_Db_Adapter_Mysqli_Exception('The Mysqli extension is required for this adapter but the extension is not loaded');
+ }
+
+ if (isset($this->_config['port'])) {
+ $port = (integer) $this->_config['port'];
+ } else {
+ $port = null;
+ }
+
+ if (isset($this->_config['socket'])) {
+ $socket = $this->_config['socket'];
+ } else {
+ $socket = null;
+ }
+
+ $this->_connection = mysqli_init();
+
+ if(!empty($this->_config['driver_options'])) {
+ foreach($this->_config['driver_options'] as $option=>$value) {
+ if(is_string($option)) {
+ // Suppress warnings here
+ // Ignore it if it's not a valid constant
+ $option = @constant(strtoupper($option));
+ if($option === null)
+ continue;
+ }
+ mysqli_options($this->_connection, $option, $value);
+ }
+ }
+
+ // Suppress connection warnings here.
+ // Throw an exception instead.
+ $_isConnected = @mysqli_real_connect(
+ $this->_connection,
+ $this->_config['host'],
+ $this->_config['username'],
+ $this->_config['password'],
+ $this->_config['dbname'],
+ $port,
+ $socket
+ );
+
+ if ($_isConnected === false || mysqli_connect_errno()) {
+
+ $this->closeConnection();
+ /**
+ * @see Zend_Db_Adapter_Mysqli_Exception
+ */
+ require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
+ throw new Zend_Db_Adapter_Mysqli_Exception(mysqli_connect_error());
+ }
+
+ if (!empty($this->_config['charset'])) {
+ mysqli_set_charset($this->_connection, $this->_config['charset']);
+ }
+ }
+
+ /**
+ * Test if a connection is active
+ *
+ * @return boolean
+ */
+ public function isConnected()
+ {
+ return ((bool) ($this->_connection instanceof mysqli));
+ }
+
+ /**
+ * Force the connection to close.
+ *
+ * @return void
+ */
+ public function closeConnection()
+ {
+ if ($this->isConnected()) {
+ $this->_connection->close();
+ }
+ $this->_connection = null;
+ }
+
+ /**
+ * Prepare a statement and return a PDOStatement-like object.
+ *
+ * @param string $sql SQL query
+ * @return Zend_Db_Statement_Mysqli
+ */
+ public function prepare($sql)
+ {
+ $this->_connect();
+ if ($this->_stmt) {
+ $this->_stmt->close();
+ }
+ $stmtClass = $this->_defaultStmtClass;
+ if (!class_exists($stmtClass)) {
+ require_once 'Zend/Loader.php';
+ Zend_Loader::loadClass($stmtClass);
+ }
+ $stmt = new $stmtClass($this, $sql);
+ if ($stmt === false) {
+ return false;
+ }
+ $stmt->setFetchMode($this->_fetchMode);
+ $this->_stmt = $stmt;
+ return $stmt;
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * MySQL does not support sequences, so $tableName and $primaryKey are ignored.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ * @todo Return value should be int?
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ $mysqli = $this->_connection;
+ return (string) $mysqli->insert_id;
+ }
+
+ /**
+ * Begin a transaction.
+ *
+ * @return void
+ */
+ protected function _beginTransaction()
+ {
+ $this->_connect();
+ $this->_connection->autocommit(false);
+ }
+
+ /**
+ * Commit a transaction.
+ *
+ * @return void
+ */
+ protected function _commit()
+ {
+ $this->_connect();
+ $this->_connection->commit();
+ $this->_connection->autocommit(true);
+ }
+
+ /**
+ * Roll-back a transaction.
+ *
+ * @return void
+ */
+ protected function _rollBack()
+ {
+ $this->_connect();
+ $this->_connection->rollback();
+ $this->_connection->autocommit(true);
+ }
+
+ /**
+ * Set the fetch mode.
+ *
+ * @param int $mode
+ * @return void
+ * @throws Zend_Db_Adapter_Mysqli_Exception
+ */
+ public function setFetchMode($mode)
+ {
+ switch ($mode) {
+ case Zend_Db::FETCH_LAZY:
+ case Zend_Db::FETCH_ASSOC:
+ case Zend_Db::FETCH_NUM:
+ case Zend_Db::FETCH_BOTH:
+ case Zend_Db::FETCH_NAMED:
+ case Zend_Db::FETCH_OBJ:
+ $this->_fetchMode = $mode;
+ break;
+ case Zend_Db::FETCH_BOUND: // bound to PHP variable
+ /**
+ * @see Zend_Db_Adapter_Mysqli_Exception
+ */
+ require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
+ throw new Zend_Db_Adapter_Mysqli_Exception('FETCH_BOUND is not supported yet');
+ break;
+ default:
+ /**
+ * @see Zend_Db_Adapter_Mysqli_Exception
+ */
+ require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
+ throw new Zend_Db_Adapter_Mysqli_Exception("Invalid fetch mode '$mode' specified");
+ }
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param int $count
+ * @param int $offset OPTIONAL
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = (int)$count;
+ if ($count <= 0) {
+ /**
+ * @see Zend_Db_Adapter_Mysqli_Exception
+ */
+ require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
+ throw new Zend_Db_Adapter_Mysqli_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = (int)$offset;
+ if ($offset < 0) {
+ /**
+ * @see Zend_Db_Adapter_Mysqli_Exception
+ */
+ require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
+ throw new Zend_Db_Adapter_Mysqli_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ $sql .= " LIMIT $count";
+ if ($offset > 0) {
+ $sql .= " OFFSET $offset";
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Check if the adapter supports real SQL parameters.
+ *
+ * @param string $type 'positional' or 'named'
+ * @return bool
+ */
+ public function supportsParameters($type)
+ {
+ switch ($type) {
+ case 'positional':
+ return true;
+ case 'named':
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ *
+ *@return string
+ */
+ public function getServerVersion()
+ {
+ $this->_connect();
+ $version = $this->_connection->server_version;
+ $major = (int) ($version / 10000);
+ $minor = (int) ($version % 10000 / 100);
+ $revision = (int) ($version % 100);
+ return $major . '.' . $minor . '.' . $revision;
+ }
+}
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Mysqli/Exception.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Mysqli/Exception.php
new file mode 100644
index 0000000000..818f5050ad
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Mysqli/Exception.php
@@ -0,0 +1,40 @@
+ (string) Connect to the database as this username.
+ * password => (string) Password associated with the username.
+ * dbname => Either the name of the local Oracle instance, or the
+ * name of the entry in tnsnames.ora to which you want to connect.
+ * persistent => (boolean) Set TRUE to use a persistent connection
+ * @var array
+ */
+ protected $_config = [
+ 'dbname' => null,
+ 'username' => null,
+ 'password' => null,
+ 'persistent' => false
+ ];
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = [
+ Zend_Db::INT_TYPE => Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'BINARY_DOUBLE' => Zend_Db::FLOAT_TYPE,
+ 'BINARY_FLOAT' => Zend_Db::FLOAT_TYPE,
+ 'NUMBER' => Zend_Db::FLOAT_TYPE,
+ ];
+
+ /**
+ * @var integer
+ */
+ protected $_execute_mode = null;
+
+ /**
+ * Default class name for a DB statement.
+ *
+ * @var string
+ */
+ protected $_defaultStmtClass = 'Zend_Db_Statement_Oracle';
+
+ /**
+ * Check if LOB field are returned as string
+ * instead of OCI-Lob object
+ *
+ * @var boolean
+ */
+ protected $_lobAsString = null;
+
+ /**
+ * Creates a connection resource.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Oracle_Exception
+ */
+ protected function _connect()
+ {
+ if (is_resource($this->_connection)) {
+ // connection already exists
+ return;
+ }
+
+ if (!extension_loaded('oci8')) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ require_once 'Zend/Db/Adapter/Oracle/Exception.php';
+ throw new Zend_Db_Adapter_Oracle_Exception('The OCI8 extension is required for this adapter but the extension is not loaded');
+ }
+
+ $this->_setExecuteMode(OCI_COMMIT_ON_SUCCESS);
+
+ $connectionFuncName = ($this->_config['persistent'] == true) ? 'oci_pconnect' : 'oci_connect';
+
+ $this->_connection = @$connectionFuncName(
+ $this->_config['username'],
+ $this->_config['password'],
+ $this->_config['dbname'],
+ $this->_config['charset']);
+
+ // check the connection
+ if (!$this->_connection) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ require_once 'Zend/Db/Adapter/Oracle/Exception.php';
+ throw new Zend_Db_Adapter_Oracle_Exception(oci_error());
+ }
+ }
+
+ /**
+ * Test if a connection is active
+ *
+ * @return boolean
+ */
+ public function isConnected()
+ {
+ return ((bool) (is_resource($this->_connection)
+ && (get_resource_type($this->_connection) == 'oci8 connection'
+ || get_resource_type($this->_connection) == 'oci8 persistent connection')));
+ }
+
+ /**
+ * Force the connection to close.
+ *
+ * @return void
+ */
+ public function closeConnection()
+ {
+ if ($this->isConnected()) {
+ oci_close($this->_connection);
+ }
+ $this->_connection = null;
+ }
+
+ /**
+ * Activate/deactivate return of LOB as string
+ *
+ * @param string $lob_as_string
+ * @return Zend_Db_Adapter_Oracle
+ */
+ public function setLobAsString($lobAsString)
+ {
+ $this->_lobAsString = (bool) $lobAsString;
+ return $this;
+ }
+
+ /**
+ * Return whether or not LOB are returned as string
+ *
+ * @return boolean
+ */
+ public function getLobAsString()
+ {
+ if ($this->_lobAsString === null) {
+ // if never set by user, we use driver option if it exists otherwise false
+ if (isset($this->_config['driver_options']) &&
+ isset($this->_config['driver_options']['lob_as_string'])) {
+ $this->_lobAsString = (bool) $this->_config['driver_options']['lob_as_string'];
+ } else {
+ $this->_lobAsString = false;
+ }
+ }
+ return $this->_lobAsString;
+ }
+
+ /**
+ * Returns an SQL statement for preparation.
+ *
+ * @param string $sql The SQL statement with placeholders.
+ * @return Zend_Db_Statement_Oracle
+ */
+ public function prepare($sql)
+ {
+ $this->_connect();
+ $stmtClass = $this->_defaultStmtClass;
+ if (!class_exists($stmtClass)) {
+ require_once 'Zend/Loader.php';
+ Zend_Loader::loadClass($stmtClass);
+ }
+ $stmt = new $stmtClass($this, $sql);
+ if ($stmt instanceof Zend_Db_Statement_Oracle) {
+ $stmt->setLobAsString($this->getLobAsString());
+ }
+ $stmt->setFetchMode($this->_fetchMode);
+ return $stmt;
+ }
+
+ /**
+ * Quote a raw string.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (is_int($value) || is_float($value)) {
+ return $value;
+ }
+ $value = str_replace("'", "''", $value);
+ return "'" . addcslashes($value, "\000\n\r\\\032") . "'";
+ }
+
+ /**
+ * Quote a table identifier and alias.
+ *
+ * @param string|array|Zend_Db_Expr $ident The identifier or expression.
+ * @param string $alias An alias for the table.
+ * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
+ * @return string The quoted identifier and alias.
+ */
+ public function quoteTableAs($ident, $alias = null, $auto = false)
+ {
+ // Oracle doesn't allow the 'AS' keyword between the table identifier/expression and alias.
+ return $this->_quoteIdentifierAs($ident, $alias, $auto, ' ');
+ }
+
+ /**
+ * Return the most recent value from the specified sequence in the database.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $sql = 'SELECT '.$this->quoteIdentifier($sequenceName, true).'.CURRVAL FROM dual';
+
+ return $this->fetchOne($sql);
+ }
+
+ /**
+ * Generate a new value from the specified sequence in the database, and return it.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $sql = 'SELECT '.$this->quoteIdentifier($sequenceName, true).'.NEXTVAL FROM dual';
+
+ return $this->fetchOne($sql);
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * Oracle does not support IDENTITY columns, so if the sequence is not
+ * specified, this method returns null.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ if ($tableName !== null) {
+ $sequenceName = $tableName;
+ if ($primaryKey) {
+ $sequenceName .= "_$primaryKey";
+ }
+ $sequenceName .= '_seq';
+ return $this->lastSequenceId($sequenceName);
+ }
+
+ // No support for IDENTITY columns; return null
+ return null;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $this->_connect();
+
+ return $this->fetchCol('SELECT table_name FROM all_tables');
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @todo Discover integer unsigned property.
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ $version = $this->getServerVersion();
+ if (($version === null) || version_compare($version, '9.0.0', '>=')) {
+ $sql = "SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE,
+ TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH,
+ TC.DATA_SCALE, TC.DATA_PRECISION, C.CONSTRAINT_TYPE, CC.POSITION
+ FROM ALL_TAB_COLUMNS TC
+ LEFT JOIN (ALL_CONS_COLUMNS CC JOIN ALL_CONSTRAINTS C
+ ON (CC.CONSTRAINT_NAME = C.CONSTRAINT_NAME AND CC.TABLE_NAME = C.TABLE_NAME AND CC.OWNER = C.OWNER AND C.CONSTRAINT_TYPE = 'P'))
+ ON TC.TABLE_NAME = CC.TABLE_NAME AND TC.COLUMN_NAME = CC.COLUMN_NAME
+ WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME)";
+ $bind[':TBNAME'] = $tableName;
+ if ($schemaName) {
+ $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)';
+ $bind[':SCNAME'] = $schemaName;
+ }
+ $sql .= ' ORDER BY TC.COLUMN_ID';
+ } else {
+ $subSql="SELECT AC.OWNER, AC.TABLE_NAME, ACC.COLUMN_NAME, AC.CONSTRAINT_TYPE, ACC.POSITION
+ from ALL_CONSTRAINTS AC, ALL_CONS_COLUMNS ACC
+ WHERE ACC.CONSTRAINT_NAME = AC.CONSTRAINT_NAME
+ AND ACC.TABLE_NAME = AC.TABLE_NAME
+ AND ACC.OWNER = AC.OWNER
+ AND AC.CONSTRAINT_TYPE = 'P'
+ AND UPPER(AC.TABLE_NAME) = UPPER(:TBNAME)";
+ $bind[':TBNAME'] = $tableName;
+ if ($schemaName) {
+ $subSql .= ' AND UPPER(ACC.OWNER) = UPPER(:SCNAME)';
+ $bind[':SCNAME'] = $schemaName;
+ }
+ $sql="SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE,
+ TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH,
+ TC.DATA_SCALE, TC.DATA_PRECISION, CC.CONSTRAINT_TYPE, CC.POSITION
+ FROM ALL_TAB_COLUMNS TC, ($subSql) CC
+ WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME)
+ AND TC.OWNER = CC.OWNER(+) AND TC.TABLE_NAME = CC.TABLE_NAME(+) AND TC.COLUMN_NAME = CC.COLUMN_NAME(+)";
+ if ($schemaName) {
+ $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)';
+ }
+ $sql .= ' ORDER BY TC.COLUMN_ID';
+ }
+
+ $stmt = $this->query($sql, $bind);
+
+ /**
+ * Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
+ */
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ $table_name = 0;
+ $owner = 1;
+ $column_name = 2;
+ $data_type = 3;
+ $data_default = 4;
+ $nullable = 5;
+ $column_id = 6;
+ $data_length = 7;
+ $data_scale = 8;
+ $data_precision = 9;
+ $constraint_type = 10;
+ $position = 11;
+
+ $desc = [];
+ foreach ($result as $key => $row) {
+ list ($primary, $primaryPosition, $identity) = [false, null, false];
+ if ($row[$constraint_type] == 'P') {
+ $primary = true;
+ $primaryPosition = $row[$position];
+ /**
+ * Oracle does not support auto-increment keys.
+ */
+ $identity = false;
+ }
+ $desc[$this->foldCase($row[$column_name])] = [
+ 'SCHEMA_NAME' => $this->foldCase($row[$owner]),
+ 'TABLE_NAME' => $this->foldCase($row[$table_name]),
+ 'COLUMN_NAME' => $this->foldCase($row[$column_name]),
+ 'COLUMN_POSITION' => $row[$column_id],
+ 'DATA_TYPE' => $row[$data_type],
+ 'DEFAULT' => $row[$data_default],
+ 'NULLABLE' => (bool) ($row[$nullable] == 'Y'),
+ 'LENGTH' => $row[$data_length],
+ 'SCALE' => $row[$data_scale],
+ 'PRECISION' => $row[$data_precision],
+ 'UNSIGNED' => null, // @todo
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ ];
+ }
+ return $desc;
+ }
+
+ /**
+ * Leave autocommit mode and begin a transaction.
+ *
+ * @return void
+ */
+ protected function _beginTransaction()
+ {
+ $this->_setExecuteMode(OCI_DEFAULT);
+ }
+
+ /**
+ * Commit a transaction and return to autocommit mode.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Oracle_Exception
+ */
+ protected function _commit()
+ {
+ if (!oci_commit($this->_connection)) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ require_once 'Zend/Db/Adapter/Oracle/Exception.php';
+ throw new Zend_Db_Adapter_Oracle_Exception(oci_error($this->_connection));
+ }
+ $this->_setExecuteMode(OCI_COMMIT_ON_SUCCESS);
+ }
+
+ /**
+ * Roll back a transaction and return to autocommit mode.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Oracle_Exception
+ */
+ protected function _rollBack()
+ {
+ if (!oci_rollback($this->_connection)) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ require_once 'Zend/Db/Adapter/Oracle/Exception.php';
+ throw new Zend_Db_Adapter_Oracle_Exception(oci_error($this->_connection));
+ }
+ $this->_setExecuteMode(OCI_COMMIT_ON_SUCCESS);
+ }
+
+ /**
+ * Set the fetch mode.
+ *
+ * @todo Support FETCH_CLASS and FETCH_INTO.
+ *
+ * @param integer $mode A fetch mode.
+ * @return void
+ * @throws Zend_Db_Adapter_Oracle_Exception
+ */
+ public function setFetchMode($mode)
+ {
+ switch ($mode) {
+ case Zend_Db::FETCH_NUM: // seq array
+ case Zend_Db::FETCH_ASSOC: // assoc array
+ case Zend_Db::FETCH_BOTH: // seq+assoc array
+ case Zend_Db::FETCH_OBJ: // object
+ $this->_fetchMode = $mode;
+ break;
+ case Zend_Db::FETCH_BOUND: // bound to PHP variable
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ require_once 'Zend/Db/Adapter/Oracle/Exception.php';
+ throw new Zend_Db_Adapter_Oracle_Exception('FETCH_BOUND is not supported yet');
+ break;
+ default:
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ require_once 'Zend/Db/Adapter/Oracle/Exception.php';
+ throw new Zend_Db_Adapter_Oracle_Exception("Invalid fetch mode '$mode' specified");
+ break;
+ }
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @return string
+ * @throws Zend_Db_Adapter_Oracle_Exception
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = (int)$count;
+ if ($count <= 0) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ require_once 'Zend/Db/Adapter/Oracle/Exception.php';
+ throw new Zend_Db_Adapter_Oracle_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = (int)$offset;
+ if ($offset < 0) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ require_once 'Zend/Db/Adapter/Oracle/Exception.php';
+ throw new Zend_Db_Adapter_Oracle_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ /**
+ * Oracle does not implement the LIMIT clause as some RDBMS do.
+ * We have to simulate it with subqueries and ROWNUM.
+ * Unfortunately because we use the column wildcard "*",
+ * this puts an extra column into the query result set.
+ */
+ return "SELECT z2.*
+ FROM (
+ SELECT z1.*, ROWNUM AS \"zend_db_rownum\"
+ FROM (
+ " . $sql . "
+ ) z1
+ ) z2
+ WHERE z2.\"zend_db_rownum\" BETWEEN " . ($offset+1) . " AND " . ($offset+$count);
+ }
+
+ /**
+ * @param integer $mode
+ * @throws Zend_Db_Adapter_Oracle_Exception
+ */
+ private function _setExecuteMode($mode)
+ {
+ switch($mode) {
+ case OCI_COMMIT_ON_SUCCESS:
+ case OCI_DEFAULT:
+ case OCI_DESCRIBE_ONLY:
+ $this->_execute_mode = $mode;
+ break;
+ default:
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ require_once 'Zend/Db/Adapter/Oracle/Exception.php';
+ throw new Zend_Db_Adapter_Oracle_Exception("Invalid execution mode '$mode' specified");
+ break;
+ }
+ }
+
+ /**
+ * @return int
+ */
+ public function _getExecuteMode()
+ {
+ return $this->_execute_mode;
+ }
+
+ /**
+ * Check if the adapter supports real SQL parameters.
+ *
+ * @param string $type 'positional' or 'named'
+ * @return bool
+ */
+ public function supportsParameters($type)
+ {
+ switch ($type) {
+ case 'named':
+ return true;
+ case 'positional':
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ *
+ * @return string
+ */
+ public function getServerVersion()
+ {
+ $this->_connect();
+ $version = oci_server_version($this->_connection);
+ if ($version !== false) {
+ $matches = null;
+ if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $version, $matches)) {
+ return $matches[1];
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Oracle/Exception.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Oracle/Exception.php
new file mode 100644
index 0000000000..bf19dd8096
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Oracle/Exception.php
@@ -0,0 +1,60 @@
+message = $error['code'] .' '. $error['message'];
+ } else {
+ $this->message = $error['code'] .' '. $error['message']." "
+ . substr($error['sqltext'], 0, $error['offset'])
+ . "*"
+ . substr($error['sqltext'], $error['offset']);
+ }
+ $this->code = $error['code'];
+ } else if (is_string($error)) {
+ $this->message = $error;
+ }
+ if (!$this->code && $code) {
+ $this->code = $code;
+ }
+ }
+}
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Abstract.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Abstract.php
new file mode 100644
index 0000000000..dcc1ef6979
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Abstract.php
@@ -0,0 +1,400 @@
+_config settings.
+ *
+ * @return string
+ */
+ protected function _dsn()
+ {
+ // baseline of DSN parts
+ $dsn = $this->_config;
+
+ // don't pass the username, password, charset, persistent and driver_options in the DSN
+ unset($dsn['username']);
+ unset($dsn['password']);
+ unset($dsn['options']);
+ unset($dsn['charset']);
+ unset($dsn['persistent']);
+ unset($dsn['driver_options']);
+
+ // use all remaining parts in the DSN
+ foreach ($dsn as $key => $val) {
+ $dsn[$key] = "$key=$val";
+ }
+
+ return $this->_pdoType . ':' . implode(';', $dsn);
+ }
+
+ /**
+ * Creates a PDO object and connects to the database.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Exception
+ */
+ protected function _connect()
+ {
+ // if we already have a PDO object, no need to re-connect.
+ if ($this->_connection) {
+ return;
+ }
+
+ // get the dsn first, because some adapters alter the $_pdoType
+ $dsn = $this->_dsn();
+
+ // check for PDO extension
+ if (!extension_loaded('pdo')) {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception('The PDO extension is required for this adapter but the extension is not loaded');
+ }
+
+ // check the PDO driver is available
+ if (!in_array($this->_pdoType, PDO::getAvailableDrivers())) {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception('The ' . $this->_pdoType . ' driver is not currently installed');
+ }
+
+ // create PDO connection
+ $q = $this->_profiler->queryStart('connect', Zend_Db_Profiler::CONNECT);
+
+ // add the persistence flag if we find it in our config array
+ if (isset($this->_config['persistent']) && ($this->_config['persistent'] == true)) {
+ $this->_config['driver_options'][PDO::ATTR_PERSISTENT] = true;
+ }
+
+ try {
+ $this->_connection = new PDO(
+ $dsn,
+ $this->_config['username'],
+ $this->_config['password'],
+ $this->_config['driver_options']
+ );
+
+ $this->_profiler->queryEnd($q);
+
+ // set the PDO connection to perform case-folding on array keys, or not
+ $this->_connection->setAttribute(PDO::ATTR_CASE, $this->_caseFolding);
+
+ // always use exceptions.
+ $this->_connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+ } catch (PDOException $e) {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+
+ }
+
+ /**
+ * Test if a connection is active
+ *
+ * @return boolean
+ */
+ public function isConnected()
+ {
+ return ((bool) ($this->_connection instanceof PDO));
+ }
+
+ /**
+ * Force the connection to close.
+ *
+ * @return void
+ */
+ public function closeConnection()
+ {
+ $this->_connection = null;
+ }
+
+ /**
+ * Prepares an SQL statement.
+ *
+ * @param string $sql The SQL statement with placeholders.
+ * @param array $bind An array of data to bind to the placeholders.
+ * @return PDOStatement
+ */
+ public function prepare($sql)
+ {
+ $this->_connect();
+ $stmtClass = $this->_defaultStmtClass;
+ if (!class_exists($stmtClass)) {
+ require_once 'Zend/Loader.php';
+ Zend_Loader::loadClass($stmtClass);
+ }
+ $stmt = new $stmtClass($this, $sql);
+ $stmt->setFetchMode($this->_fetchMode);
+ return $stmt;
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * On RDBMS brands that don't support sequences, $tableName and $primaryKey
+ * are ignored.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ $this->_connect();
+ return $this->_connection->lastInsertId();
+ }
+
+ /**
+ * Special handling for PDO query().
+ * All bind parameter names must begin with ':'
+ *
+ * @param string|Zend_Db_Select $sql The SQL statement with placeholders.
+ * @param array $bind An array of data to bind to the placeholders.
+ * @return PDOStatement|Zend_Db_Statement|Zend_Db_Statement_Interface
+ * @throws Zend_Db_Adapter_Exception To re-throw PDOException.
+ */
+ public function query($sql, $bind = [])
+ {
+ if (empty($bind) && $sql instanceof Zend_Db_Select) {
+ $bind = $sql->getBind();
+ }
+
+ if (is_array($bind)) {
+ foreach ($bind as $name => $value) {
+ if (!is_int($name) && !preg_match('/^:/', $name)) {
+ $newName = ":$name";
+ unset($bind[$name]);
+ $bind[$newName] = $value;
+ }
+ }
+ }
+
+ try {
+ return parent::query($sql, $bind);
+ } catch (PDOException $e) {
+ /**
+ * @see Zend_Db_Statement_Exception
+ */
+ require_once 'Zend/Db/Statement/Exception.php';
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Executes an SQL statement and return the number of affected rows
+ *
+ * @param mixed $sql The SQL statement with placeholders.
+ * May be a string or Zend_Db_Select.
+ * @return integer Number of rows that were modified
+ * or deleted by the SQL statement
+ */
+ public function exec($sql)
+ {
+ if ($sql instanceof Zend_Db_Select) {
+ $sql = $sql->assemble();
+ }
+
+ try {
+ $affected = $this->getConnection()->exec($sql);
+
+ if ($affected === false) {
+ $errorInfo = $this->getConnection()->errorInfo();
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception($errorInfo[2]);
+ }
+
+ return $affected;
+ } catch (PDOException $e) {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Quote a raw string.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (is_int($value) || is_float($value)) {
+ return $value;
+ }
+ $this->_connect();
+ return $this->_connection->quote((string) $value);
+ }
+
+ /**
+ * Begin a transaction.
+ */
+ protected function _beginTransaction()
+ {
+ $this->_connect();
+ $this->_connection->beginTransaction();
+ }
+
+ /**
+ * Commit a transaction.
+ */
+ protected function _commit()
+ {
+ $this->_connect();
+ $this->_connection->commit();
+ }
+
+ /**
+ * Roll-back a transaction.
+ */
+ protected function _rollBack() {
+ $this->_connect();
+ $this->_connection->rollBack();
+ }
+
+ /**
+ * Set the PDO fetch mode.
+ *
+ * @todo Support FETCH_CLASS and FETCH_INTO.
+ *
+ * @param int $mode A PDO fetch mode.
+ * @return void
+ * @throws Zend_Db_Adapter_Exception
+ */
+ public function setFetchMode($mode)
+ {
+ //check for PDO extension
+ if (!extension_loaded('pdo')) {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception('The PDO extension is required for this adapter but the extension is not loaded');
+ }
+ switch ($mode) {
+ case PDO::FETCH_LAZY:
+ case PDO::FETCH_ASSOC:
+ case PDO::FETCH_NUM:
+ case PDO::FETCH_BOTH:
+ case PDO::FETCH_NAMED:
+ case PDO::FETCH_OBJ:
+ $this->_fetchMode = $mode;
+ break;
+ default:
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("Invalid fetch mode '$mode' specified");
+ break;
+ }
+ }
+
+ /**
+ * Check if the adapter supports real SQL parameters.
+ *
+ * @param string $type 'positional' or 'named'
+ * @return bool
+ */
+ public function supportsParameters($type)
+ {
+ switch ($type) {
+ case 'positional':
+ case 'named':
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ *
+ * @return string
+ */
+ public function getServerVersion()
+ {
+ $this->_connect();
+ try {
+ $version = $this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
+ } catch (PDOException $e) {
+ // In case of the driver doesn't support getting attributes
+ return null;
+ }
+ $matches = null;
+ if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $version, $matches)) {
+ return $matches[1];
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Ibm.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Ibm.php
new file mode 100644
index 0000000000..80caa53481
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Ibm.php
@@ -0,0 +1,368 @@
+ Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'INTEGER' => Zend_Db::INT_TYPE,
+ 'SMALLINT' => Zend_Db::INT_TYPE,
+ 'BIGINT' => Zend_Db::BIGINT_TYPE,
+ 'DECIMAL' => Zend_Db::FLOAT_TYPE,
+ 'DEC' => Zend_Db::FLOAT_TYPE,
+ 'REAL' => Zend_Db::FLOAT_TYPE,
+ 'NUMERIC' => Zend_Db::FLOAT_TYPE,
+ 'DOUBLE PRECISION' => Zend_Db::FLOAT_TYPE,
+ 'FLOAT' => Zend_Db::FLOAT_TYPE
+ ];
+
+ /**
+ * Creates a PDO object and connects to the database.
+ *
+ * The IBM data server is set.
+ * Current options are DB2 or IDS
+ * @todo also differentiate between z/OS and i/5
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Exception
+ */
+ public function _connect()
+ {
+ if ($this->_connection) {
+ return;
+ }
+ parent::_connect();
+
+ $this->getConnection()->setAttribute(Zend_Db::ATTR_STRINGIFY_FETCHES, true);
+
+ try {
+ if ($this->_serverType === null) {
+ $server = substr($this->getConnection()->getAttribute(PDO::ATTR_SERVER_INFO), 0, 3);
+
+ switch ($server) {
+ case 'DB2':
+ $this->_serverType = new Zend_Db_Adapter_Pdo_Ibm_Db2($this);
+
+ // Add DB2-specific numeric types
+ $this->_numericDataTypes['DECFLOAT'] = Zend_Db::FLOAT_TYPE;
+ $this->_numericDataTypes['DOUBLE'] = Zend_Db::FLOAT_TYPE;
+ $this->_numericDataTypes['NUM'] = Zend_Db::FLOAT_TYPE;
+
+ break;
+ case 'IDS':
+ $this->_serverType = new Zend_Db_Adapter_Pdo_Ibm_Ids($this);
+
+ // Add IDS-specific numeric types
+ $this->_numericDataTypes['SERIAL'] = Zend_Db::INT_TYPE;
+ $this->_numericDataTypes['SERIAL8'] = Zend_Db::BIGINT_TYPE;
+ $this->_numericDataTypes['INT8'] = Zend_Db::BIGINT_TYPE;
+ $this->_numericDataTypes['SMALLFLOAT'] = Zend_Db::FLOAT_TYPE;
+ $this->_numericDataTypes['MONEY'] = Zend_Db::FLOAT_TYPE;
+
+ break;
+ }
+ }
+ } catch (PDOException $e) {
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ $error = strpos($e->getMessage(), 'driver does not support that attribute');
+ if ($error) {
+ throw new Zend_Db_Adapter_Exception("PDO_IBM driver extension is downlevel. Please use driver release version 1.2.1 or later", 0, $e);
+ }
+
+ throw new Zend_Db_Adapter_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Creates a PDO DSN for the adapter from $this->_config settings.
+ *
+ * @return string
+ * @throws Zend_Db_Adapter_Exception
+ */
+ protected function _dsn()
+ {
+ $this->_checkRequiredOptions($this->_config);
+
+ // check if using full connection string
+ if (array_key_exists('host', $this->_config)) {
+ $dsn = ';DATABASE=' . $this->_config['dbname']
+ . ';HOSTNAME=' . $this->_config['host']
+ . ';PORT=' . $this->_config['port']
+ // PDO_IBM supports only DB2 TCPIP protocol
+ . ';PROTOCOL=' . 'TCPIP;';
+ } else {
+ // catalogued connection
+ $dsn = $this->_config['dbname'];
+ }
+ return $this->_pdoType . ': ' . $dsn;
+ }
+
+ /**
+ * Checks required options
+ *
+ * @param array $config
+ * @throws Zend_Db_Adapter_Exception
+ * @return void
+ */
+ protected function _checkRequiredOptions(array $config)
+ {
+ parent::_checkRequiredOptions($config);
+
+ if (array_key_exists('host', $this->_config) &&
+ !array_key_exists('port', $config)) {
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("Configuration must have a key for 'port' when 'host' is specified");
+ }
+ }
+
+ /**
+ * Prepares an SQL statement.
+ *
+ * @param string $sql The SQL statement with placeholders.
+ * @param array $bind An array of data to bind to the placeholders.
+ * @return PDOStatement
+ * @throws Zend_Db_Adapter_Exception
+ */
+ public function prepare($sql)
+ {
+ $this->_connect();
+ $stmtClass = $this->_defaultStmtClass;
+ $stmt = new $stmtClass($this, $sql);
+ $stmt->setFetchMode($this->_fetchMode);
+ return $stmt;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ * @throws Zend_Db_Adapter_Exception
+ */
+ public function listTables()
+ {
+ $this->_connect();
+ return $this->_serverType->listTables();
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ * @throws Zend_Db_Adapter_Exception
+ * @todo Discover integer unsigned property.
+ *
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ $this->_connect();
+ return $this->_serverType->describeTable($tableName, $schemaName);
+ }
+
+ /**
+ * Inserts a table row with specified data.
+ * Special handling for PDO_IBM
+ * remove empty slots
+ *
+ * @param mixed $table The table to insert data into.
+ * @param array $bind Column-value pairs.
+ * @return int The number of affected rows.
+ * @throws Zend_Db_Adapter_Exception
+ */
+ public function insert($table, array $bind)
+ {
+ $this->_connect();
+ $newbind = [];
+ if (is_array($bind)) {
+ foreach ($bind as $name => $value) {
+ if($value !== null) {
+ $newbind[$name] = $value;
+ }
+ }
+ }
+
+ return parent::insert($table, $newbind);
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @return string
+ * @throws Zend_Db_Adapter_Exception
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $this->_connect();
+ return $this->_serverType->limit($sql, $count, $offset);
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT
+ * column.
+ *
+ * @param string $tableName OPTIONAL
+ * @param string $primaryKey OPTIONAL
+ * @return integer
+ * @throws Zend_Db_Adapter_Exception
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ $this->_connect();
+
+ if ($tableName !== null) {
+ $sequenceName = $tableName;
+ if ($primaryKey) {
+ $sequenceName .= "_$primaryKey";
+ }
+ $sequenceName .= '_seq';
+ return $this->lastSequenceId($sequenceName);
+ }
+
+ return $this->getConnection()->lastInsertId();
+ }
+
+ /**
+ * Return the most recent value from the specified sequence in the database.
+ *
+ * @param string $sequenceName
+ * @return integer
+ * @throws Zend_Db_Adapter_Exception
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $this->_connect();
+ return $this->_serverType->lastSequenceId($sequenceName);
+ }
+
+ /**
+ * Generate a new value from the specified sequence in the database,
+ * and return it.
+ *
+ * @param string $sequenceName
+ * @return integer
+ * @throws Zend_Db_Adapter_Exception
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $this->_connect();
+ return $this->_serverType->nextSequenceId($sequenceName);
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ * Pdo_Idm doesn't support getAttribute(PDO::ATTR_SERVER_VERSION)
+ *
+ * @return string
+ * @throws Zend_Db_Adapter_Exception
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function getServerVersion()
+ {
+ try {
+ $stmt = $this->query('SELECT service_level, fixpack_num FROM TABLE (sysproc.env_get_inst_info()) as INSTANCEINFO');
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+ if (count($result)) {
+ $matches = null;
+ if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $result[0][0], $matches)) {
+ return $matches[1];
+ } else {
+ return null;
+ }
+ }
+ return null;
+ } catch (PDOException $e) {
+ return null;
+ }
+ }
+}
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Ibm/Db2.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Ibm/Db2.php
new file mode 100644
index 0000000000..75ad810958
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Ibm/Db2.php
@@ -0,0 +1,230 @@
+_adapter = $adapter;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $sql = "SELECT tabname "
+ . "FROM SYSCAT.TABLES ";
+ return $this->_adapter->fetchCol($sql);
+ }
+
+ /**
+ * DB2 catalog lookup for describe table
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ $sql = "SELECT DISTINCT c.tabschema, c.tabname, c.colname, c.colno,
+ c.typename, c.default, c.nulls, c.length, c.scale,
+ c.identity, tc.type AS tabconsttype, k.colseq
+ FROM syscat.columns c
+ LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc
+ ON (k.tabschema = tc.tabschema
+ AND k.tabname = tc.tabname
+ AND tc.type = 'P'))
+ ON (c.tabschema = k.tabschema
+ AND c.tabname = k.tabname
+ AND c.colname = k.colname)
+ WHERE "
+ . $this->_adapter->quoteInto('UPPER(c.tabname) = UPPER(?)', $tableName);
+ if ($schemaName) {
+ $sql .= $this->_adapter->quoteInto(' AND UPPER(c.tabschema) = UPPER(?)', $schemaName);
+ }
+ $sql .= " ORDER BY c.colno";
+
+ $desc = [];
+ $stmt = $this->_adapter->query($sql);
+
+ /**
+ * To avoid case issues, fetch using FETCH_NUM
+ */
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ /**
+ * The ordering of columns is defined by the query so we can map
+ * to variables to improve readability
+ */
+ $tabschema = 0;
+ $tabname = 1;
+ $colname = 2;
+ $colno = 3;
+ $typename = 4;
+ $default = 5;
+ $nulls = 6;
+ $length = 7;
+ $scale = 8;
+ $identityCol = 9;
+ $tabconstype = 10;
+ $colseq = 11;
+
+ foreach ($result as $key => $row) {
+ [$primary, $primaryPosition, $identity] = [false, null, false];
+ if ($row[$tabconstype] == 'P') {
+ $primary = true;
+ $primaryPosition = $row[$colseq];
+ }
+ /**
+ * In IBM DB2, an column can be IDENTITY
+ * even if it is not part of the PRIMARY KEY.
+ */
+ if ($row[$identityCol] == 'Y') {
+ $identity = true;
+ }
+
+ $desc[$this->_adapter->foldCase($row[$colname])] = [
+ 'SCHEMA_NAME' => $this->_adapter->foldCase($row[$tabschema]),
+ 'TABLE_NAME' => $this->_adapter->foldCase($row[$tabname]),
+ 'COLUMN_NAME' => $this->_adapter->foldCase($row[$colname]),
+ 'COLUMN_POSITION' => $row[$colno]+1,
+ 'DATA_TYPE' => $row[$typename],
+ 'DEFAULT' => $row[$default],
+ 'NULLABLE' => (bool) ($row[$nulls] == 'Y'),
+ 'LENGTH' => $row[$length],
+ 'SCALE' => $row[$scale],
+ 'PRECISION' => ($row[$typename] == 'DECIMAL' ? $row[$length] : 0),
+ 'UNSIGNED' => false,
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ ];
+ }
+
+ return $desc;
+ }
+
+ /**
+ * Adds a DB2-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @throws Zend_Db_Adapter_Exception
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = (int)$count;
+ if ($count < 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = (int)$offset;
+
+ if ($offset < 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ if ($offset === 0 && $count > 0) {
+ return $sql . " FETCH FIRST $count ROWS ONLY";
+ }
+
+ /**
+ * DB2 does not implement the LIMIT clause as some RDBMS do.
+ * We have to simulate it with subqueries and ROWNUM.
+ * Unfortunately because we use the column wildcard "*",
+ * this puts an extra column into the query result set.
+ */
+ $limit_sql = "SELECT z2.*
+ FROM (
+ SELECT ROW_NUMBER() OVER() AS \"ZEND_DB_ROWNUM\", z1.*
+ FROM (
+ " . $sql . "
+ ) z1
+ ) z2
+ WHERE z2.zend_db_rownum BETWEEN " . ($offset+1) . " AND " . ($offset+$count);
+
+ return $limit_sql;
+ }
+
+ /**
+ * DB2-specific last sequence id
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $sql = 'SELECT PREVVAL FOR '.$this->_adapter->quoteIdentifier($sequenceName).' AS VAL FROM SYSIBM.SYSDUMMY1';
+
+ return $this->_adapter->fetchOne($sql);
+ }
+
+ /**
+ * DB2-specific sequence id value
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $sql = 'SELECT NEXTVAL FOR '.$this->_adapter->quoteIdentifier($sequenceName).' AS VAL FROM SYSIBM.SYSDUMMY1';
+
+ return $this->_adapter->fetchOne($sql);
+ }
+}
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Ibm/Ids.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Ibm/Ids.php
new file mode 100644
index 0000000000..210c7db0c3
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Ibm/Ids.php
@@ -0,0 +1,310 @@
+_adapter = $adapter;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $sql = "SELECT tabname "
+ . "FROM systables ";
+
+ return $this->_adapter->fetchCol($sql);
+ }
+
+ /**
+ * IDS catalog lookup for describe table
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ // this is still a work in progress
+
+ $sql= "SELECT DISTINCT t.owner, t.tabname, c.colname, c.colno, c.coltype,
+ d.default, c.collength, t.tabid
+ FROM syscolumns c
+ JOIN systables t ON c.tabid = t.tabid
+ LEFT JOIN sysdefaults d ON c.tabid = d.tabid AND c.colno = d.colno
+ WHERE "
+ . $this->_adapter->quoteInto('UPPER(t.tabname) = UPPER(?)', $tableName);
+ if ($schemaName) {
+ $sql .= $this->_adapter->quoteInto(' AND UPPER(t.owner) = UPPER(?)', $schemaName);
+ }
+ $sql .= " ORDER BY c.colno";
+
+ $desc = [];
+ $stmt = $this->_adapter->query($sql);
+
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ /**
+ * The ordering of columns is defined by the query so we can map
+ * to variables to improve readability
+ */
+ $tabschema = 0;
+ $tabname = 1;
+ $colname = 2;
+ $colno = 3;
+ $typename = 4;
+ $default = 5;
+ $length = 6;
+ $tabid = 7;
+
+ $primaryCols = null;
+
+ foreach ($result as $key => $row) {
+ $primary = false;
+ $primaryPosition = null;
+
+ if (!$primaryCols) {
+ $primaryCols = $this->_getPrimaryInfo($row[$tabid]);
+ }
+
+ if (array_key_exists($row[$colno], $primaryCols)) {
+ $primary = true;
+ $primaryPosition = $primaryCols[$row[$colno]];
+ }
+
+ $identity = false;
+
+ if ($row[$typename] == 6 + 256 ||
+ $row[$typename] == 18 + 256) {
+ $identity = true;
+ }
+
+ $desc[$this->_adapter->foldCase($row[$colname])] = [
+ 'SCHEMA_NAME' => $this->_adapter->foldCase($row[$tabschema]),
+ 'TABLE_NAME' => $this->_adapter->foldCase($row[$tabname]),
+ 'COLUMN_NAME' => $this->_adapter->foldCase($row[$colname]),
+ 'COLUMN_POSITION' => $row[$colno],
+ 'DATA_TYPE' => $this->_getDataType($row[$typename]),
+ 'DEFAULT' => $row[$default],
+ 'NULLABLE' => (bool) !($row[$typename] - 256 >= 0),
+ 'LENGTH' => $row[$length],
+ 'SCALE' => ($row[$typename] == 5 ? $row[$length]&255 : 0),
+ 'PRECISION' => ($row[$typename] == 5 ? (int)($row[$length]/256) : 0),
+ 'UNSIGNED' => false,
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ ];
+ }
+
+ return $desc;
+ }
+
+ /**
+ * Map number representation of a data type
+ * to a string
+ *
+ * @param int $typeNo
+ * @return string
+ */
+ protected function _getDataType($typeNo)
+ {
+ $typemap = [
+ 0 => "CHAR",
+ 1 => "SMALLINT",
+ 2 => "INTEGER",
+ 3 => "FLOAT",
+ 4 => "SMALLFLOAT",
+ 5 => "DECIMAL",
+ 6 => "SERIAL",
+ 7 => "DATE",
+ 8 => "MONEY",
+ 9 => "NULL",
+ 10 => "DATETIME",
+ 11 => "BYTE",
+ 12 => "TEXT",
+ 13 => "VARCHAR",
+ 14 => "INTERVAL",
+ 15 => "NCHAR",
+ 16 => "NVARCHAR",
+ 17 => "INT8",
+ 18 => "SERIAL8",
+ 19 => "SET",
+ 20 => "MULTISET",
+ 21 => "LIST",
+ 22 => "Unnamed ROW",
+ 40 => "Variable-length opaque type",
+ 4118 => "Named ROW"
+ ];
+
+ if ($typeNo - 256 >= 0) {
+ $typeNo = $typeNo - 256;
+ }
+
+ return $typemap[$typeNo];
+ }
+
+ /**
+ * Helper method to retrieve primary key column
+ * and column location
+ *
+ * @param int $tabid
+ * @return array
+ */
+ protected function _getPrimaryInfo($tabid)
+ {
+ $sql = "SELECT i.part1, i.part2, i.part3, i.part4, i.part5, i.part6,
+ i.part7, i.part8, i.part9, i.part10, i.part11, i.part12,
+ i.part13, i.part14, i.part15, i.part16
+ FROM sysindexes i
+ JOIN sysconstraints c ON c.idxname = i.idxname
+ WHERE i.tabid = " . $tabid . " AND c.constrtype = 'P'";
+
+ $stmt = $this->_adapter->query($sql);
+ $results = $stmt->fetchAll();
+
+ $cols = [];
+
+ // this should return only 1 row
+ // unless there is no primary key,
+ // in which case, the empty array is returned
+ if ($results) {
+ $row = $results[0];
+ } else {
+ return $cols;
+ }
+
+ $position = 0;
+
+ foreach ($row as $key => $colno) {
+ $position++;
+
+ if ($colno == 0) {
+ return $cols;
+ }
+
+ $cols[$colno] = $position;
+ }
+ }
+
+ /**
+ * Adds an IDS-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @throws Zend_Db_Adapter_Exception
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = (int)$count;
+
+ if ($count < 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ if ($count === 0) {
+ $limit_sql = str_ireplace("SELECT", "SELECT * FROM (SELECT", $sql);
+ $limit_sql .= ") WHERE 0 = 1";
+ } else {
+ $offset = (int)$offset;
+
+ if ($offset < 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ if ($offset === 0) {
+ $limit_sql = str_ireplace("SELECT", "SELECT FIRST $count", $sql);
+ } else {
+ $limit_sql = str_ireplace("SELECT", "SELECT SKIP $offset LIMIT $count", $sql);
+ }
+ }
+
+ return $limit_sql;
+ }
+
+ /**
+ * IDS-specific last sequence id
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $sql = 'SELECT '.$this->_adapter->quoteIdentifier($sequenceName).'.CURRVAL FROM '
+ .'systables WHERE tabid = 1';
+
+ return $this->_adapter->fetchOne($sql);
+ }
+
+ /**
+ * IDS-specific sequence id value
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $sql = 'SELECT '.$this->_adapter->quoteIdentifier($sequenceName).'.NEXTVAL FROM '
+ .'systables WHERE tabid = 1';
+
+ return $this->_adapter->fetchOne($sql);
+ }
+}
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Mssql.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Mssql.php
new file mode 100644
index 0000000000..d266066f7a
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Mssql.php
@@ -0,0 +1,438 @@
+ Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'INT' => Zend_Db::INT_TYPE,
+ 'SMALLINT' => Zend_Db::INT_TYPE,
+ 'TINYINT' => Zend_Db::INT_TYPE,
+ 'BIGINT' => Zend_Db::BIGINT_TYPE,
+ 'DECIMAL' => Zend_Db::FLOAT_TYPE,
+ 'FLOAT' => Zend_Db::FLOAT_TYPE,
+ 'MONEY' => Zend_Db::FLOAT_TYPE,
+ 'NUMERIC' => Zend_Db::FLOAT_TYPE,
+ 'REAL' => Zend_Db::FLOAT_TYPE,
+ 'SMALLMONEY' => Zend_Db::FLOAT_TYPE
+ ];
+
+ /**
+ * Creates a PDO DSN for the adapter from $this->_config settings.
+ *
+ * @return string
+ */
+ protected function _dsn()
+ {
+ // baseline of DSN parts
+ $dsn = $this->_config;
+
+ // don't pass the username and password in the DSN
+ unset($dsn['username']);
+ unset($dsn['password']);
+ unset($dsn['options']);
+ unset($dsn['persistent']);
+ unset($dsn['driver_options']);
+
+ if (isset($dsn['port'])) {
+ $seperator = ':';
+ if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+ $seperator = ',';
+ }
+ $dsn['host'] .= $seperator . $dsn['port'];
+ unset($dsn['port']);
+ }
+
+ // this driver supports multiple DSN prefixes
+ // @see http://www.php.net/manual/en/ref.pdo-dblib.connection.php
+ if (isset($dsn['pdoType'])) {
+ switch (strtolower($dsn['pdoType'])) {
+ case 'freetds':
+ case 'sybase':
+ $this->_pdoType = 'sybase';
+ break;
+ case 'mssql':
+ $this->_pdoType = 'mssql';
+ break;
+ case 'dblib':
+ default:
+ $this->_pdoType = 'dblib';
+ break;
+ }
+ unset($dsn['pdoType']);
+ }
+
+ // use all remaining parts in the DSN
+ foreach ($dsn as $key => $val) {
+ $dsn[$key] = "$key=$val";
+ }
+
+ $dsn = $this->_pdoType . ':' . implode(';', $dsn);
+ return $dsn;
+ }
+
+ /**
+ * @return void
+ */
+ protected function _connect()
+ {
+ if ($this->_connection) {
+ return;
+ }
+ parent::_connect();
+ $this->_connection->exec('SET QUOTED_IDENTIFIER ON');
+ }
+
+ /**
+ * Begin a transaction.
+ *
+ * It is necessary to override the abstract PDO transaction functions here, as
+ * the PDO driver for MSSQL does not support transactions.
+ */
+ protected function _beginTransaction()
+ {
+ $this->_connect();
+ $this->_connection->exec('BEGIN TRANSACTION');
+ return true;
+ }
+
+ /**
+ * Commit a transaction.
+ *
+ * It is necessary to override the abstract PDO transaction functions here, as
+ * the PDO driver for MSSQL does not support transactions.
+ */
+ protected function _commit()
+ {
+ $this->_connect();
+ $this->_connection->exec('COMMIT TRANSACTION');
+ return true;
+ }
+
+ /**
+ * Roll-back a transaction.
+ *
+ * It is necessary to override the abstract PDO transaction functions here, as
+ * the PDO driver for MSSQL does not support transactions.
+ */
+ protected function _rollBack() {
+ $this->_connect();
+ $this->_connection->exec('ROLLBACK TRANSACTION');
+ return true;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $sql = "SELECT name FROM sysobjects WHERE type = 'U' ORDER BY name";
+ return $this->fetchCol($sql);
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * PRIMARY_AUTO => integer; position of auto-generated column in primary key
+ *
+ * @todo Discover column primary key position.
+ * @todo Discover integer unsigned property.
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ if ($schemaName != null) {
+ if (strpos($schemaName, '.') !== false) {
+ $result = explode('.', $schemaName);
+ $schemaName = $result[1];
+ }
+ }
+ /**
+ * Discover metadata information about this table.
+ */
+ $sql = "exec sp_columns @table_name = " . $this->quoteIdentifier($tableName, true);
+ if ($schemaName != null) {
+ $sql .= ", @table_owner = " . $this->quoteIdentifier($schemaName, true);
+ }
+
+ $stmt = $this->query($sql);
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ $table_name = 2;
+ $column_name = 3;
+ $type_name = 5;
+ $precision = 6;
+ $length = 7;
+ $scale = 8;
+ $nullable = 10;
+ $column_def = 12;
+ $column_position = 16;
+
+ /**
+ * Discover primary key column(s) for this table.
+ */
+ $sql = "exec sp_pkeys @table_name = " . $this->quoteIdentifier($tableName, true);
+ if ($schemaName != null) {
+ $sql .= ", @table_owner = " . $this->quoteIdentifier($schemaName, true);
+ }
+
+ $stmt = $this->query($sql);
+ $primaryKeysResult = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+ $primaryKeyColumn = [];
+ $pkey_column_name = 3;
+ $pkey_key_seq = 4;
+ foreach ($primaryKeysResult as $pkeysRow) {
+ $primaryKeyColumn[$pkeysRow[$pkey_column_name]] = $pkeysRow[$pkey_key_seq];
+ }
+
+ $desc = [];
+ $p = 1;
+ foreach ($result as $key => $row) {
+ $identity = false;
+ $words = explode(' ', $row[$type_name], 2);
+ if (isset($words[0])) {
+ $type = $words[0];
+ if (isset($words[1])) {
+ $identity = (bool) preg_match('/identity/', $words[1]);
+ }
+ }
+
+ $isPrimary = array_key_exists($row[$column_name], $primaryKeyColumn);
+ if ($isPrimary) {
+ $primaryPosition = $primaryKeyColumn[$row[$column_name]];
+ } else {
+ $primaryPosition = null;
+ }
+
+ $desc[$this->foldCase($row[$column_name])] = [
+ 'SCHEMA_NAME' => null, // @todo
+ 'TABLE_NAME' => $this->foldCase($row[$table_name]),
+ 'COLUMN_NAME' => $this->foldCase($row[$column_name]),
+ 'COLUMN_POSITION' => (int) $row[$column_position],
+ 'DATA_TYPE' => $type,
+ 'DEFAULT' => $row[$column_def],
+ 'NULLABLE' => (bool) $row[$nullable],
+ 'LENGTH' => $row[$length],
+ 'SCALE' => $row[$scale],
+ 'PRECISION' => $row[$precision],
+ 'UNSIGNED' => null, // @todo
+ 'PRIMARY' => $isPrimary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ ];
+ }
+ return $desc;
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @link http://lists.bestpractical.com/pipermail/rt-devel/2005-June/007339.html
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @throws Zend_Db_Adapter_Exception
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = (int)$count;
+ if ($count <= 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = (int)$offset;
+ if ($offset < 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ $sql = preg_replace(
+ '/^SELECT\s+(DISTINCT\s)?/i',
+ 'SELECT $1TOP ' . ($count+$offset) . ' ',
+ $sql
+ );
+
+ if ($offset > 0) {
+ $orderby = stristr($sql, 'ORDER BY');
+
+ if ($orderby !== false) {
+ $orderParts = explode(',', substr($orderby, 8));
+ $pregReplaceCount = null;
+ $orderbyInverseParts = [];
+ foreach ($orderParts as $orderPart) {
+ $orderPart = rtrim($orderPart);
+ $inv = preg_replace('/\s+desc$/i', ' ASC', $orderPart, 1, $pregReplaceCount);
+ if ($pregReplaceCount) {
+ $orderbyInverseParts[] = $inv;
+ continue;
+ }
+ $inv = preg_replace('/\s+asc$/i', ' DESC', $orderPart, 1, $pregReplaceCount);
+ if ($pregReplaceCount) {
+ $orderbyInverseParts[] = $inv;
+ continue;
+ } else {
+ $orderbyInverseParts[] = $orderPart . ' DESC';
+ }
+ }
+
+ $orderbyInverse = 'ORDER BY ' . implode(', ', $orderbyInverseParts);
+ }
+
+
+
+
+ $sql = 'SELECT * FROM (SELECT TOP ' . $count . ' * FROM (' . $sql . ') AS inner_tbl';
+ if ($orderby !== false) {
+ $sql .= ' ' . $orderbyInverse . ' ';
+ }
+ $sql .= ') AS outer_tbl';
+ if ($orderby !== false) {
+ $sql .= ' ' . $orderby;
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * Microsoft SQL Server does not support sequences, so the arguments to
+ * this method are ignored.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return int
+ * @throws Zend_Db_Adapter_Exception
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ $sql = 'SELECT SCOPE_IDENTITY()';
+ return (int)$this->fetchOne($sql);
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ * Pdo_Mssql doesn't support getAttribute(PDO::ATTR_SERVER_VERSION)
+ * @return string
+ */
+ public function getServerVersion()
+ {
+ try {
+ $stmt = $this->query("SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR)");
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+ if (count($result)) {
+ return $result[0][0];
+ }
+ return null;
+ } catch (PDOException $e) {
+ return null;
+ }
+ }
+
+ /**
+ * Quote a raw string.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (!is_int($value) && !is_float($value)) {
+ // Fix for null-byte injection
+ $value = addcslashes($value, "\000\032");
+ }
+ return parent::_quote($value);
+ }
+}
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Mysql.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Mysql.php
new file mode 100644
index 0000000000..f1c5d62d6e
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Mysql.php
@@ -0,0 +1,274 @@
+ Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'INT' => Zend_Db::INT_TYPE,
+ 'INTEGER' => Zend_Db::INT_TYPE,
+ 'MEDIUMINT' => Zend_Db::INT_TYPE,
+ 'SMALLINT' => Zend_Db::INT_TYPE,
+ 'TINYINT' => Zend_Db::INT_TYPE,
+ 'BIGINT' => Zend_Db::BIGINT_TYPE,
+ 'SERIAL' => Zend_Db::BIGINT_TYPE,
+ 'DEC' => Zend_Db::FLOAT_TYPE,
+ 'DECIMAL' => Zend_Db::FLOAT_TYPE,
+ 'DOUBLE' => Zend_Db::FLOAT_TYPE,
+ 'DOUBLE PRECISION' => Zend_Db::FLOAT_TYPE,
+ 'FIXED' => Zend_Db::FLOAT_TYPE,
+ 'FLOAT' => Zend_Db::FLOAT_TYPE
+ ];
+
+ /**
+ * Override _dsn() and ensure that charset is incorporated in mysql
+ * @see Zend_Db_Adapter_Pdo_Abstract::_dsn()
+ *
+ * @return string
+ */
+ protected function _dsn()
+ {
+ $dsn = parent::_dsn();
+ if (isset($this->_config['charset'])) {
+ $dsn .= ';charset=' . $this->_config['charset'];
+ }
+ return $dsn;
+ }
+
+ /**
+ * Creates a PDO object and connects to the database.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Exception
+ */
+ protected function _connect()
+ {
+ if ($this->_connection) {
+ return;
+ }
+
+ if (!empty($this->_config['charset'])
+ && version_compare(PHP_VERSION, '5.3.6', '<')
+ ) {
+ $initCommand = "SET NAMES '" . $this->_config['charset'] . "'";
+ $this->_config['driver_options'][1002] = $initCommand; // 1002 = PDO::MYSQL_ATTR_INIT_COMMAND
+ }
+
+ parent::_connect();
+ }
+
+ /**
+ * @return string
+ */
+ public function getQuoteIdentifierSymbol()
+ {
+ return "`";
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ return $this->fetchCol('SHOW TABLES');
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ // @todo use INFORMATION_SCHEMA someday when MySQL's
+ // implementation has reasonably good performance and
+ // the version with this improvement is in wide use.
+
+ if ($schemaName) {
+ $sql = 'DESCRIBE ' . $this->quoteIdentifier("$schemaName.$tableName", true);
+ } else {
+ $sql = 'DESCRIBE ' . $this->quoteIdentifier($tableName, true);
+ }
+ $stmt = $this->query($sql);
+
+ // Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ $field = 0;
+ $type = 1;
+ $null = 2;
+ $key = 3;
+ $default = 4;
+ $extra = 5;
+
+ $desc = [];
+ $i = 1;
+ $p = 1;
+ foreach ($result as $row) {
+ list($length, $scale, $precision, $unsigned, $primary, $primaryPosition, $identity)
+ = [null, null, null, null, false, null, false];
+ if (preg_match('/unsigned/', $row[$type])) {
+ $unsigned = true;
+ }
+ if (preg_match('/^((?:var)?char)\((\d+)\)/', $row[$type], $matches)) {
+ $row[$type] = $matches[1];
+ $length = $matches[2];
+ } else if (preg_match('/^decimal\((\d+),(\d+)\)/', $row[$type], $matches)) {
+ $row[$type] = 'decimal';
+ $precision = $matches[1];
+ $scale = $matches[2];
+ } else if (preg_match('/^float\((\d+),(\d+)\)/', $row[$type], $matches)) {
+ $row[$type] = 'float';
+ $precision = $matches[1];
+ $scale = $matches[2];
+ } else if (preg_match('/^((?:big|medium|small|tiny)?int)\((\d+)\)/', $row[$type], $matches)) {
+ $row[$type] = $matches[1];
+ // The optional argument of a MySQL int type is not precision
+ // or length; it is only a hint for display width.
+ }
+ if (strtoupper($row[$key]) == 'PRI') {
+ $primary = true;
+ $primaryPosition = $p;
+ if ($row[$extra] == 'auto_increment') {
+ $identity = true;
+ } else {
+ $identity = false;
+ }
+ ++$p;
+ }
+ $desc[$this->foldCase($row[$field])] = [
+ 'SCHEMA_NAME' => null, // @todo
+ 'TABLE_NAME' => $this->foldCase($tableName),
+ 'COLUMN_NAME' => $this->foldCase($row[$field]),
+ 'COLUMN_POSITION' => $i,
+ 'DATA_TYPE' => $row[$type],
+ 'DEFAULT' => $row[$default],
+ 'NULLABLE' => (bool) ($row[$null] == 'YES'),
+ 'LENGTH' => $length,
+ 'SCALE' => $scale,
+ 'PRECISION' => $precision,
+ 'UNSIGNED' => $unsigned,
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ ];
+ ++$i;
+ }
+ return $desc;
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @throws Zend_Db_Adapter_Exception
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = (int)$count;
+ if ($count <= 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = (int)$offset;
+ if ($offset < 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ $sql .= " LIMIT $count";
+ if ($offset > 0) {
+ $sql .= " OFFSET $offset";
+ }
+
+ return $sql;
+ }
+
+}
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Oci.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Oci.php
new file mode 100644
index 0000000000..0ca6e01905
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Oci.php
@@ -0,0 +1,376 @@
+ Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'BINARY_DOUBLE' => Zend_Db::FLOAT_TYPE,
+ 'BINARY_FLOAT' => Zend_Db::FLOAT_TYPE,
+ 'NUMBER' => Zend_Db::FLOAT_TYPE
+ ];
+
+ /**
+ * Creates a PDO DSN for the adapter from $this->_config settings.
+ *
+ * @return string
+ */
+ protected function _dsn()
+ {
+ // baseline of DSN parts
+ $dsn = $this->_config;
+
+ if (isset($dsn['host'])) {
+ $tns = 'dbname=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)' .
+ '(HOST=' . $dsn['host'] . ')';
+
+ if (isset($dsn['port'])) {
+ $tns .= '(PORT=' . $dsn['port'] . ')';
+ } else {
+ $tns .= '(PORT=1521)';
+ }
+
+ $tns .= '))(CONNECT_DATA=(SID=' . $dsn['dbname'] . ')))';
+ } else {
+ $tns = 'dbname=' . $dsn['dbname'];
+ }
+
+ if (isset($dsn['charset'])) {
+ $tns .= ';charset=' . $dsn['charset'];
+ }
+
+ return $this->_pdoType . ':' . $tns;
+ }
+
+ /**
+ * Quote a raw string.
+ * Most PDO drivers have an implementation for the quote() method,
+ * but the Oracle OCI driver must use the same implementation as the
+ * Zend_Db_Adapter_Abstract class.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (is_int($value) || is_float($value)) {
+ return $value;
+ }
+ $value = str_replace("'", "''", $value);
+ return "'" . addcslashes($value, "\000\n\r\\\032") . "'";
+ }
+
+ /**
+ * Quote a table identifier and alias.
+ *
+ * @param string|array|Zend_Db_Expr $ident The identifier or expression.
+ * @param string $alias An alias for the table.
+ * @return string The quoted identifier and alias.
+ */
+ public function quoteTableAs($ident, $alias = null, $auto = false)
+ {
+ // Oracle doesn't allow the 'AS' keyword between the table identifier/expression and alias.
+ return $this->_quoteIdentifierAs($ident, $alias, $auto, ' ');
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ return $this->fetchCol('SELECT table_name FROM all_tables');
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @todo Discover integer unsigned property.
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ $version = $this->getServerVersion();
+ if (($version === null) || version_compare($version, '9.0.0', '>=')) {
+ $sql = "SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE,
+ TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH,
+ TC.DATA_SCALE, TC.DATA_PRECISION, C.CONSTRAINT_TYPE, CC.POSITION
+ FROM ALL_TAB_COLUMNS TC
+ LEFT JOIN (ALL_CONS_COLUMNS CC JOIN ALL_CONSTRAINTS C
+ ON (CC.CONSTRAINT_NAME = C.CONSTRAINT_NAME AND CC.TABLE_NAME = C.TABLE_NAME AND CC.OWNER = C.OWNER AND C.CONSTRAINT_TYPE = 'P'))
+ ON TC.TABLE_NAME = CC.TABLE_NAME AND TC.COLUMN_NAME = CC.COLUMN_NAME
+ WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME)";
+ $bind[':TBNAME'] = $tableName;
+ if ($schemaName) {
+ $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)';
+ $bind[':SCNAME'] = $schemaName;
+ }
+ $sql .= ' ORDER BY TC.COLUMN_ID';
+ } else {
+ $subSql="SELECT AC.OWNER, AC.TABLE_NAME, ACC.COLUMN_NAME, AC.CONSTRAINT_TYPE, ACC.POSITION
+ from ALL_CONSTRAINTS AC, ALL_CONS_COLUMNS ACC
+ WHERE ACC.CONSTRAINT_NAME = AC.CONSTRAINT_NAME
+ AND ACC.TABLE_NAME = AC.TABLE_NAME
+ AND ACC.OWNER = AC.OWNER
+ AND AC.CONSTRAINT_TYPE = 'P'
+ AND UPPER(AC.TABLE_NAME) = UPPER(:TBNAME)";
+ $bind[':TBNAME'] = $tableName;
+ if ($schemaName) {
+ $subSql .= ' AND UPPER(ACC.OWNER) = UPPER(:SCNAME)';
+ $bind[':SCNAME'] = $schemaName;
+ }
+ $sql="SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE,
+ TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH,
+ TC.DATA_SCALE, TC.DATA_PRECISION, CC.CONSTRAINT_TYPE, CC.POSITION
+ FROM ALL_TAB_COLUMNS TC, ($subSql) CC
+ WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME)
+ AND TC.OWNER = CC.OWNER(+) AND TC.TABLE_NAME = CC.TABLE_NAME(+) AND TC.COLUMN_NAME = CC.COLUMN_NAME(+)";
+ if ($schemaName) {
+ $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)';
+ }
+ $sql .= ' ORDER BY TC.COLUMN_ID';
+ }
+
+ $stmt = $this->query($sql, $bind);
+
+ /**
+ * Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
+ */
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ $table_name = 0;
+ $owner = 1;
+ $column_name = 2;
+ $data_type = 3;
+ $data_default = 4;
+ $nullable = 5;
+ $column_id = 6;
+ $data_length = 7;
+ $data_scale = 8;
+ $data_precision = 9;
+ $constraint_type = 10;
+ $position = 11;
+
+ $desc = [];
+ foreach ($result as $key => $row) {
+ [$primary, $primaryPosition, $identity] = [false, null, false];
+ if ($row[$constraint_type] == 'P') {
+ $primary = true;
+ $primaryPosition = $row[$position];
+ /**
+ * Oracle does not support auto-increment keys.
+ */
+ $identity = false;
+ }
+ $desc[$this->foldCase($row[$column_name])] = [
+ 'SCHEMA_NAME' => $this->foldCase($row[$owner]),
+ 'TABLE_NAME' => $this->foldCase($row[$table_name]),
+ 'COLUMN_NAME' => $this->foldCase($row[$column_name]),
+ 'COLUMN_POSITION' => $row[$column_id],
+ 'DATA_TYPE' => $row[$data_type],
+ 'DEFAULT' => $row[$data_default],
+ 'NULLABLE' => (bool) ($row[$nullable] == 'Y'),
+ 'LENGTH' => $row[$data_length],
+ 'SCALE' => $row[$data_scale],
+ 'PRECISION' => $row[$data_precision],
+ 'UNSIGNED' => null, // @todo
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ ];
+ }
+ return $desc;
+ }
+
+ /**
+ * Return the most recent value from the specified sequence in the database.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $this->_connect();
+
+ return $this->fetchOne('SELECT '.$this->quoteIdentifier($sequenceName, true).'.CURRVAL FROM dual');
+ }
+
+ /**
+ * Generate a new value from the specified sequence in the database, and return it.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $this->_connect();
+
+ return $this->fetchOne('SELECT '.$this->quoteIdentifier($sequenceName, true).'.NEXTVAL FROM dual');
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * Oracle does not support IDENTITY columns, so if the sequence is not
+ * specified, this method returns null.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string|null
+ * @throws Zend_Db_Adapter_Oracle_Exception
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ if ($tableName !== null) {
+ $sequenceName = $tableName;
+ if ($primaryKey) {
+ $sequenceName .= $this->foldCase("_$primaryKey");
+ }
+ $sequenceName .= $this->foldCase('_seq');
+ return $this->lastSequenceId($sequenceName);
+ }
+ // No support for IDENTITY columns; return null
+ return null;
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset
+ * @throws Zend_Db_Adapter_Exception
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = (int)$count;
+ if ($count <= 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = (int)$offset;
+ if ($offset < 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ /**
+ * Oracle does not implement the LIMIT clause as some RDBMS do.
+ * We have to simulate it with subqueries and ROWNUM.
+ * Unfortunately because we use the column wildcard "*",
+ * this puts an extra column into the query result set.
+ */
+ return "SELECT z2.*
+ FROM (
+ SELECT z1.*, ROWNUM AS \"zend_db_rownum\"
+ FROM (
+ " . $sql . "
+ ) z1
+ ) z2
+ WHERE z2.\"zend_db_rownum\" BETWEEN " . ($offset+1) . " AND " . ($offset+$count);
+ }
+
+}
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Pgsql.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Pgsql.php
new file mode 100644
index 0000000000..c3012bad1c
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Pgsql.php
@@ -0,0 +1,338 @@
+ Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'INTEGER' => Zend_Db::INT_TYPE,
+ 'SERIAL' => Zend_Db::INT_TYPE,
+ 'SMALLINT' => Zend_Db::INT_TYPE,
+ 'BIGINT' => Zend_Db::BIGINT_TYPE,
+ 'BIGSERIAL' => Zend_Db::BIGINT_TYPE,
+ 'DECIMAL' => Zend_Db::FLOAT_TYPE,
+ 'DOUBLE PRECISION' => Zend_Db::FLOAT_TYPE,
+ 'NUMERIC' => Zend_Db::FLOAT_TYPE,
+ 'REAL' => Zend_Db::FLOAT_TYPE
+ ];
+
+ /**
+ * Creates a PDO object and connects to the database.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Exception
+ */
+ protected function _connect()
+ {
+ if ($this->_connection) {
+ return;
+ }
+
+ parent::_connect();
+
+ if (!empty($this->_config['charset'])) {
+ $sql = "SET NAMES '" . $this->_config['charset'] . "'";
+ $this->_connection->exec($sql);
+ }
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ // @todo use a better query with joins instead of subqueries
+ $sql = "SELECT c.relname AS table_name "
+ . "FROM pg_class c, pg_user u "
+ . "WHERE c.relowner = u.usesysid AND c.relkind = 'r' "
+ . "AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) "
+ . "AND c.relname !~ '^(pg_|sql_)' "
+ . "UNION "
+ . "SELECT c.relname AS table_name "
+ . "FROM pg_class c "
+ . "WHERE c.relkind = 'r' "
+ . "AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) "
+ . "AND NOT EXISTS (SELECT 1 FROM pg_user WHERE usesysid = c.relowner) "
+ . "AND c.relname !~ '^pg_'";
+
+ return $this->fetchCol($sql);
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @todo Discover integer unsigned property.
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ $sql = "SELECT
+ a.attnum,
+ n.nspname,
+ c.relname,
+ a.attname AS colname,
+ t.typname AS type,
+ a.atttypmod,
+ FORMAT_TYPE(a.atttypid, a.atttypmod) AS complete_type,
+ pg_get_expr(d.adbin, d.adrelid) AS default_value,
+ a.attnotnull AS notnull,
+ a.attlen AS length,
+ co.contype,
+ ARRAY_TO_STRING(co.conkey, ',') AS conkey
+ FROM pg_attribute AS a
+ JOIN pg_class AS c ON a.attrelid = c.oid
+ JOIN pg_namespace AS n ON c.relnamespace = n.oid
+ JOIN pg_type AS t ON a.atttypid = t.oid
+ LEFT OUTER JOIN pg_constraint AS co ON (co.conrelid = c.oid
+ AND a.attnum = ANY(co.conkey) AND co.contype = 'p')
+ LEFT OUTER JOIN pg_attrdef AS d ON d.adrelid = c.oid AND d.adnum = a.attnum
+ WHERE a.attnum > 0 AND c.relname = ".$this->quote($tableName);
+ if ($schemaName) {
+ $sql .= " AND n.nspname = ".$this->quote($schemaName);
+ }
+ $sql .= ' ORDER BY a.attnum';
+
+ $stmt = $this->query($sql);
+
+ // Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ $attnum = 0;
+ $nspname = 1;
+ $relname = 2;
+ $colname = 3;
+ $type = 4;
+ $atttypemod = 5;
+ $complete_type = 6;
+ $default_value = 7;
+ $notnull = 8;
+ $length = 9;
+ $contype = 10;
+ $conkey = 11;
+
+ $desc = [];
+ foreach ($result as $key => $row) {
+ $defaultValue = $row[$default_value];
+ if ($row[$type] == 'varchar' || $row[$type] == 'bpchar' ) {
+ if (preg_match('/character(?: varying)?(?:\((\d+)\))?/', $row[$complete_type] ?? "", $matches)) {
+ if (isset($matches[1])) {
+ $row[$length] = $matches[1];
+ } else {
+ $row[$length] = null; // unlimited
+ }
+ }
+ if (preg_match("/^'(.*?)'::(?:character varying|bpchar)$/", $defaultValue ?? "", $matches)) {
+ $defaultValue = $matches[1];
+ }
+ }
+ list($primary, $primaryPosition, $identity) = [false, null, false];
+ if ($row[$contype] == 'p') {
+ $primary = true;
+ $primaryPosition = array_search($row[$attnum], explode(',', $row[$conkey])) + 1;
+ $identity = (bool) (preg_match('/^nextval/', $row[$default_value] ?? ""));
+ }
+ $desc[$this->foldCase($row[$colname])] = [
+ 'SCHEMA_NAME' => $this->foldCase($row[$nspname]),
+ 'TABLE_NAME' => $this->foldCase($row[$relname]),
+ 'COLUMN_NAME' => $this->foldCase($row[$colname]),
+ 'COLUMN_POSITION' => $row[$attnum],
+ 'DATA_TYPE' => $row[$type],
+ 'DEFAULT' => $defaultValue,
+ 'NULLABLE' => (bool) ($row[$notnull] != 't'),
+ 'LENGTH' => $row[$length],
+ 'SCALE' => null, // @todo
+ 'PRECISION' => null, // @todo
+ 'UNSIGNED' => null, // @todo
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ ];
+ }
+ return $desc;
+ }
+
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = (int)$count;
+ if ($count <= 0) {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = (int)$offset;
+ if ($offset < 0) {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ $sql .= " LIMIT $count";
+ if ($offset > 0) {
+ $sql .= " OFFSET $offset";
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Return the most recent value from the specified sequence in the database.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $sequenceName = str_replace($this->getQuoteIdentifierSymbol(), '', (string) $sequenceName);
+
+ return $this->fetchOne(
+ "SELECT CURRVAL("
+ . $this->quote($this->quoteIdentifier($sequenceName, true))
+ . ")"
+ );
+ }
+
+ /**
+ * Generate a new value from the specified sequence in the database, and return it.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $sequenceName = str_replace($this->getQuoteIdentifierSymbol(), '', (string) $sequenceName);
+
+ return $this->fetchOne("SELECT NEXTVAL("
+ . $this->quote($this->quoteIdentifier($sequenceName, true))
+ . ")");
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ if ($tableName !== null) {
+ $sequenceName = $tableName;
+ if ($primaryKey) {
+ $sequenceName .= "_$primaryKey";
+ }
+ $sequenceName .= '_seq';
+ return $this->lastSequenceId($sequenceName);
+ }
+ return $this->_connection->lastInsertId($tableName);
+ }
+
+}
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Sqlite.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Sqlite.php
new file mode 100644
index 0000000000..5b71b99eed
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Sqlite.php
@@ -0,0 +1,311 @@
+ Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'INTEGER' => Zend_Db::BIGINT_TYPE,
+ 'REAL' => Zend_Db::FLOAT_TYPE
+ ];
+
+ /**
+ * Constructor.
+ *
+ * $config is an array of key/value pairs containing configuration
+ * options. Note that the SQLite options are different than most of
+ * the other PDO adapters in that no username or password are needed.
+ * Also, an extra config key "sqlite2" specifies compatibility mode.
+ *
+ * dbname => (string) The name of the database to user (required,
+ * use :memory: for memory-based database)
+ *
+ * sqlite2 => (boolean) PDO_SQLITE defaults to SQLite 3. For compatibility
+ * with an older SQLite 2 database, set this to TRUE.
+ *
+ * @param array $config An array of configuration keys.
+ */
+ public function __construct(array $config = [])
+ {
+ if (isset($config['sqlite2']) && $config['sqlite2']) {
+ $this->_pdoType = 'sqlite2';
+ }
+
+ // SQLite uses no username/password. Stub to satisfy parent::_connect()
+ $this->_config['username'] = null;
+ $this->_config['password'] = null;
+
+ return parent::__construct($config);
+ }
+
+ /**
+ * Check for config options that are mandatory.
+ * Throw exceptions if any are missing.
+ *
+ * @param array $config
+ * @throws Zend_Db_Adapter_Exception
+ */
+ protected function _checkRequiredOptions(array $config)
+ {
+ // we need at least a dbname
+ if (! array_key_exists('dbname', $config)) {
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("Configuration array must have a key for 'dbname' that names the database instance");
+ }
+ }
+
+ /**
+ * DSN builder
+ */
+ protected function _dsn()
+ {
+ return $this->_pdoType .':'. $this->_config['dbname'];
+ }
+
+ /**
+ * Special configuration for SQLite behavior: make sure that result sets
+ * contain keys like 'column' instead of 'table.column'.
+ *
+ * @throws Zend_Db_Adapter_Exception
+ */
+ protected function _connect()
+ {
+ /**
+ * if we already have a PDO object, no need to re-connect.
+ */
+ if ($this->_connection) {
+ return;
+ }
+
+ parent::_connect();
+
+ $retval = $this->_connection->exec('PRAGMA full_column_names=0');
+ if ($retval === false) {
+ $error = $this->_connection->errorInfo();
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception($error[2]);
+ }
+
+ $retval = $this->_connection->exec('PRAGMA short_column_names=1');
+ if ($retval === false) {
+ $error = $this->_connection->errorInfo();
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception($error[2]);
+ }
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $sql = "SELECT name FROM sqlite_master WHERE type='table' "
+ . "UNION ALL SELECT name FROM sqlite_temp_master "
+ . "WHERE type='table' ORDER BY name";
+
+ return $this->fetchCol($sql);
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ $sql = 'PRAGMA ';
+
+ if ($schemaName) {
+ $sql .= $this->quoteIdentifier($schemaName) . '.';
+ }
+
+ $sql .= 'table_info('.$this->quoteIdentifier($tableName).')';
+
+ $stmt = $this->query($sql);
+
+ /**
+ * Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
+ */
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ $cid = 0;
+ $name = 1;
+ $type = 2;
+ $notnull = 3;
+ $dflt_value = 4;
+ $pk = 5;
+
+ $desc = [];
+
+ $p = 1;
+ foreach ($result as $key => $row) {
+ list($length, $scale, $precision, $primary, $primaryPosition, $identity) =
+ [null, null, null, false, null, false];
+ if (preg_match('/^((?:var)?char)\((\d+)\)/i', $row[$type], $matches)) {
+ $row[$type] = $matches[1];
+ $length = $matches[2];
+ } else if (preg_match('/^decimal\((\d+),(\d+)\)/i', $row[$type], $matches)) {
+ $row[$type] = 'DECIMAL';
+ $precision = $matches[1];
+ $scale = $matches[2];
+ }
+ if ((bool) $row[$pk]) {
+ $primary = true;
+ $primaryPosition = $p;
+ /**
+ * SQLite INTEGER primary key is always auto-increment.
+ */
+ $identity = (bool) ($row[$type] == 'INTEGER');
+ ++$p;
+ }
+ $desc[$this->foldCase($row[$name])] = [
+ 'SCHEMA_NAME' => $this->foldCase($schemaName),
+ 'TABLE_NAME' => $this->foldCase($tableName),
+ 'COLUMN_NAME' => $this->foldCase($row[$name]),
+ 'COLUMN_POSITION' => $row[$cid]+1,
+ 'DATA_TYPE' => $row[$type],
+ 'DEFAULT' => $row[$dflt_value],
+ 'NULLABLE' => ! (bool) $row[$notnull],
+ 'LENGTH' => $length,
+ 'SCALE' => $scale,
+ 'PRECISION' => $precision,
+ 'UNSIGNED' => null, // Sqlite3 does not support unsigned data
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ ];
+ }
+ return $desc;
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = (int)$count;
+ if ($count <= 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = (int)$offset;
+ if ($offset < 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ $sql .= " LIMIT $count";
+ if ($offset > 0) {
+ $sql .= " OFFSET $offset";
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Quote a raw string.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (!is_int($value) && !is_float($value)) {
+ // Fix for null-byte injection
+ $value = addcslashes($value, "\000\032");
+ }
+ return parent::_quote($value);
+ }
+}
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Sqlsrv.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Sqlsrv.php
new file mode 100644
index 0000000000..6ba54e72ff
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Pdo/Sqlsrv.php
@@ -0,0 +1,379 @@
+ Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'INT' => Zend_Db::INT_TYPE,
+ 'SMALLINT' => Zend_Db::INT_TYPE,
+ 'TINYINT' => Zend_Db::INT_TYPE,
+ 'BIGINT' => Zend_Db::BIGINT_TYPE,
+ 'DECIMAL' => Zend_Db::FLOAT_TYPE,
+ 'FLOAT' => Zend_Db::FLOAT_TYPE,
+ 'MONEY' => Zend_Db::FLOAT_TYPE,
+ 'NUMERIC' => Zend_Db::FLOAT_TYPE,
+ 'REAL' => Zend_Db::FLOAT_TYPE,
+ 'SMALLMONEY' => Zend_Db::FLOAT_TYPE
+ ];
+
+ /**
+ * Creates a PDO DSN for the adapter from $this->_config settings.
+ *
+ * @return string
+ */
+ protected function _dsn()
+ {
+ // baseline of DSN parts
+ $dsn = $this->_config;
+
+ if (isset($dsn['name'])) {
+ $dsn = $this->_pdoType . ':' . $dsn['name'];
+ } else {
+
+ if(isset($dsn['dbname'])) {
+ $dsn['Database'] = $dsn['dbname'];
+ unset($dsn['dbname']);
+ }
+
+ if(isset($dsn['host'])) {
+ $dsn['Server'] = $dsn['host'];
+ unset($dsn['host']);
+ }
+
+ unset($dsn['username']);
+ unset($dsn['password']);
+ unset($dsn['options']);
+ unset($dsn['charset']);
+ unset($dsn['persistent']);
+ unset($dsn['driver_options']);
+ if (isset($dsn['ReturnDatesAsStrings'])) {
+ // common sqlsrv setting but not supported by pdo_sqlsrv
+ unset($dsn['ReturnDatesAsStrings']);
+ }
+
+
+ foreach ($dsn as $key => $val) {
+ $dsn[$key] = "$key=$val";
+ }
+
+ $dsn = $this->_pdoType . ':' . implode(';', $dsn);
+ }
+
+ return $dsn;
+ }
+
+ /**
+ * @return void
+ */
+ protected function _connect()
+ {
+ if ($this->_connection) {
+ return;
+ }
+ parent::_connect();
+ $this->_connection->exec('SET QUOTED_IDENTIFIER ON');
+ }
+
+ /**
+ * Set the transaction isoltion level.
+ *
+ * @param integer|null $level A fetch mode from SQLSRV_TXN_*.
+ * @return true
+ * @throws Zend_Db_Adapter_Sqlsrv_Exception
+ */
+ public function setTransactionIsolationLevel($level = null)
+ {
+ $this->_connect();
+ $sql = null;
+
+ // Default transaction level in sql server
+ if ($level === null)
+ {
+ $level = SQLSRV_TXN_READ_COMMITTED;
+ }
+
+ switch ($level) {
+ case SQLSRV_TXN_READ_UNCOMMITTED:
+ $sql = "READ UNCOMMITTED";
+ break;
+ case SQLSRV_TXN_READ_COMMITTED:
+ $sql = "READ COMMITTED";
+ break;
+ case SQLSRV_TXN_REPEATABLE_READ:
+ $sql = "REPEATABLE READ";
+ break;
+ case SQLSRV_TXN_SNAPSHOT:
+ $sql = "SNAPSHOT";
+ break;
+ case SQLSRV_TXN_SERIALIZABLE:
+ $sql = "SERIALIZABLE";
+ break;
+ default:
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("Invalid transaction isolation level mode '$level' specified");
+ }
+
+ if (!$this->_connection->exec("SET TRANSACTION ISOLATION LEVEL $sql;")) {
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("Transaction cannot be changed to '$level'");
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $sql = "SELECT name FROM sysobjects WHERE type = 'U' ORDER BY name";
+ return $this->fetchCol($sql);
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * PRIMARY_AUTO => integer; position of auto-generated column in primary key
+ *
+ * @todo Discover column primary key position.
+ * @todo Discover integer unsigned property.
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ if ($schemaName != null) {
+ if (strpos($schemaName, '.') !== false) {
+ $result = explode('.', $schemaName);
+ $schemaName = $result[1];
+ }
+ }
+ /**
+ * Discover metadata information about this table.
+ */
+ $sql = "exec sp_columns @table_name = " . $this->quoteIdentifier($tableName, true);
+ if ($schemaName != null) {
+ $sql .= ", @table_owner = " . $this->quoteIdentifier($schemaName, true);
+ }
+
+ $stmt = $this->query($sql);
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ $table_name = 2;
+ $column_name = 3;
+ $type_name = 5;
+ $precision = 6;
+ $length = 7;
+ $scale = 8;
+ $nullable = 10;
+ $column_def = 12;
+ $column_position = 16;
+
+ /**
+ * Discover primary key column(s) for this table.
+ */
+ $sql = "exec sp_pkeys @table_name = " . $this->quoteIdentifier($tableName, true);
+ if ($schemaName != null) {
+ $sql .= ", @table_owner = " . $this->quoteIdentifier($schemaName, true);
+ }
+
+ $stmt = $this->query($sql);
+ $primaryKeysResult = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+ $primaryKeyColumn = [];
+ $pkey_column_name = 3;
+ $pkey_key_seq = 4;
+ foreach ($primaryKeysResult as $pkeysRow) {
+ $primaryKeyColumn[$pkeysRow[$pkey_column_name]] = $pkeysRow[$pkey_key_seq];
+ }
+
+ $desc = [];
+ $p = 1;
+ foreach ($result as $key => $row) {
+ $identity = false;
+ $words = explode(' ', $row[$type_name], 2);
+ if (isset($words[0])) {
+ $type = $words[0];
+ if (isset($words[1])) {
+ $identity = (bool) preg_match('/identity/', $words[1]);
+ }
+ }
+
+ $isPrimary = array_key_exists($row[$column_name], $primaryKeyColumn);
+ if ($isPrimary) {
+ $primaryPosition = $primaryKeyColumn[$row[$column_name]];
+ } else {
+ $primaryPosition = null;
+ }
+
+ $desc[$this->foldCase($row[$column_name])] = [
+ 'SCHEMA_NAME' => null, // @todo
+ 'TABLE_NAME' => $this->foldCase($row[$table_name]),
+ 'COLUMN_NAME' => $this->foldCase($row[$column_name]),
+ 'COLUMN_POSITION' => (int) $row[$column_position],
+ 'DATA_TYPE' => $type,
+ 'DEFAULT' => $row[$column_def],
+ 'NULLABLE' => (bool) $row[$nullable],
+ 'LENGTH' => $row[$length],
+ 'SCALE' => $row[$scale],
+ 'PRECISION' => $row[$precision],
+ 'UNSIGNED' => null, // @todo
+ 'PRIMARY' => $isPrimary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ ];
+ }
+ return $desc;
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @return string
+ * @throws Zend_Db_Adapter_Exceptions
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = (int)$count;
+ if ($count <= 0) {
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = (int)$offset;
+ if ($offset < 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ if ($offset === 0) {
+ $sql = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . $count . ' ', $sql);
+ } else {
+ $orderby = stristr($sql, 'ORDER BY');
+ if ($orderby !== false) {
+ $sort = (stripos($orderby, ' desc') !== false) ? 'desc' : 'asc';
+ $order = str_ireplace('ORDER BY', '', $orderby);
+ $order = trim(preg_replace('/\bASC\b|\bDESC\b/i', '', $order));
+ }
+
+ $sql = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . ($count+$offset) . ' ', $sql);
+
+ $sql = 'SELECT * FROM (SELECT TOP ' . $count . ' * FROM (' . $sql . ') AS inner_tbl';
+ if ($orderby !== false) {
+ $innerOrder = preg_replace('/\".*\".\"(.*)\"/i', '"inner_tbl"."$1"', $order);
+ $sql .= ' ORDER BY ' . $innerOrder . ' ';
+ $sql .= (stripos($sort, 'asc') !== false) ? 'DESC' : 'ASC';
+ }
+ $sql .= ') AS outer_tbl';
+ if ($orderby !== false) {
+ $outerOrder = preg_replace('/\".*\".\"(.*)\"/i', '"outer_tbl"."$1"', $order);
+ $sql .= ' ORDER BY ' . $outerOrder . ' ' . $sort;
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ * Pdo_Mssql doesn't support getAttribute(PDO::ATTR_SERVER_VERSION)
+ * @return string
+ */
+ public function getServerVersion()
+ {
+ try {
+ $stmt = $this->query("SELECT SERVERPROPERTY('productversion')");
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+ if (count($result)) {
+ return $result[0][0];
+ }
+ return null;
+ } catch (PDOException $e) {
+ return null;
+ }
+ }
+}
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Sqlsrv.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Sqlsrv.php
new file mode 100644
index 0000000000..bbcb49389b
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Sqlsrv.php
@@ -0,0 +1,680 @@
+ (string) Connect to the database as this username.
+ * password => (string) Password associated with the username.
+ * dbname => The name of the local SQL Server instance
+ *
+ * @var array
+ */
+ protected $_config = [
+ 'dbname' => null,
+ 'username' => null,
+ 'password' => null,
+ ];
+
+ /**
+ * Last insert id from INSERT query
+ *
+ * @var int
+ */
+ protected $_lastInsertId;
+
+ /**
+ * Query used to fetch last insert id
+ *
+ * @var string
+ */
+ protected $_lastInsertSQL = 'SELECT SCOPE_IDENTITY() as Current_Identity';
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = [
+ Zend_Db::INT_TYPE => Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'INT' => Zend_Db::INT_TYPE,
+ 'SMALLINT' => Zend_Db::INT_TYPE,
+ 'TINYINT' => Zend_Db::INT_TYPE,
+ 'BIGINT' => Zend_Db::BIGINT_TYPE,
+ 'DECIMAL' => Zend_Db::FLOAT_TYPE,
+ 'FLOAT' => Zend_Db::FLOAT_TYPE,
+ 'MONEY' => Zend_Db::FLOAT_TYPE,
+ 'NUMERIC' => Zend_Db::FLOAT_TYPE,
+ 'REAL' => Zend_Db::FLOAT_TYPE,
+ 'SMALLMONEY' => Zend_Db::FLOAT_TYPE,
+ ];
+
+ /**
+ * Default class name for a DB statement.
+ *
+ * @var string
+ */
+ protected $_defaultStmtClass = 'Zend_Db_Statement_Sqlsrv';
+
+ /**
+ * Creates a connection resource.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Sqlsrv_Exception
+ */
+ protected function _connect()
+ {
+ if (is_resource($this->_connection)) {
+ // connection already exists
+ return;
+ }
+
+ if (!extension_loaded('sqlsrv')) {
+ /**
+ * @see Zend_Db_Adapter_Sqlsrv_Exception
+ */
+ require_once 'Zend/Db/Adapter/Sqlsrv/Exception.php';
+ throw new Zend_Db_Adapter_Sqlsrv_Exception('The Sqlsrv extension is required for this adapter but the extension is not loaded');
+ }
+
+ $serverName = $this->_config['host'];
+ if (isset($this->_config['port'])) {
+ $port = (integer) $this->_config['port'];
+ $serverName .= ', ' . $port;
+ }
+
+ $connectionInfo = [
+ 'Database' => $this->_config['dbname'],
+ ];
+
+ if (isset($this->_config['username']) && isset($this->_config['password']))
+ {
+ $connectionInfo += [
+ 'UID' => $this->_config['username'],
+ 'PWD' => $this->_config['password'],
+ ];
+ }
+ // else - windows authentication
+
+ if (!empty($this->_config['driver_options'])) {
+ foreach ($this->_config['driver_options'] as $option => $value) {
+ // A value may be a constant.
+ if (is_string($value)) {
+ $constantName = strtoupper($value);
+ if (defined($constantName)) {
+ $connectionInfo[$option] = constant($constantName);
+ } else {
+ $connectionInfo[$option] = $value;
+ }
+ }
+ }
+ }
+
+ $this->_connection = sqlsrv_connect($serverName, $connectionInfo);
+
+ if (!$this->_connection) {
+ /**
+ * @see Zend_Db_Adapter_Sqlsrv_Exception
+ */
+ require_once 'Zend/Db/Adapter/Sqlsrv/Exception.php';
+ throw new Zend_Db_Adapter_Sqlsrv_Exception(sqlsrv_errors());
+ }
+ }
+
+ /**
+ * Check for config options that are mandatory.
+ * Throw exceptions if any are missing.
+ *
+ * @param array $config
+ * @throws Zend_Db_Adapter_Exception
+ */
+ protected function _checkRequiredOptions(array $config)
+ {
+ // we need at least a dbname
+ if (! array_key_exists('dbname', $config)) {
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("Configuration array must have a key for 'dbname' that names the database instance");
+ }
+
+ if (! array_key_exists('password', $config) && array_key_exists('username', $config)) {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("Configuration array must have a key for 'password' for login credentials.
+ If Windows Authentication is desired, both keys 'username' and 'password' should be ommited from config.");
+ }
+
+ if (array_key_exists('password', $config) && !array_key_exists('username', $config)) {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("Configuration array must have a key for 'username' for login credentials.
+ If Windows Authentication is desired, both keys 'username' and 'password' should be ommited from config.");
+ }
+ }
+
+ /**
+ * Set the transaction isoltion level.
+ *
+ * @param integer|null $level A fetch mode from SQLSRV_TXN_*.
+ * @return true
+ * @throws Zend_Db_Adapter_Sqlsrv_Exception
+ */
+ public function setTransactionIsolationLevel($level = null)
+ {
+ $this->_connect();
+ $sql = null;
+
+ // Default transaction level in sql server
+ if ($level === null)
+ {
+ $level = SQLSRV_TXN_READ_COMMITTED;
+ }
+
+ switch ($level) {
+ case SQLSRV_TXN_READ_UNCOMMITTED:
+ $sql = "READ UNCOMMITTED";
+ break;
+ case SQLSRV_TXN_READ_COMMITTED:
+ $sql = "READ COMMITTED";
+ break;
+ case SQLSRV_TXN_REPEATABLE_READ:
+ $sql = "REPEATABLE READ";
+ break;
+ case SQLSRV_TXN_SNAPSHOT:
+ $sql = "SNAPSHOT";
+ break;
+ case SQLSRV_TXN_SERIALIZABLE:
+ $sql = "SERIALIZABLE";
+ break;
+ default:
+ require_once 'Zend/Db/Adapter/Sqlsrv/Exception.php';
+ throw new Zend_Db_Adapter_Sqlsrv_Exception("Invalid transaction isolation level mode '$level' specified");
+ }
+
+ if (!sqlsrv_query($this->_connection, "SET TRANSACTION ISOLATION LEVEL $sql;")) {
+ require_once 'Zend/Db/Adapter/Sqlsrv/Exception.php';
+ throw new Zend_Db_Adapter_Sqlsrv_Exception("Transaction cannot be changed to '$level'");
+ }
+
+ return true;
+ }
+
+ /**
+ * Test if a connection is active
+ *
+ * @return boolean
+ */
+ public function isConnected()
+ {
+ return (is_resource($this->_connection)
+ && (get_resource_type($this->_connection) == 'SQL Server Connection')
+ );
+ }
+
+ /**
+ * Force the connection to close.
+ *
+ * @return void
+ */
+ public function closeConnection()
+ {
+ if ($this->isConnected()) {
+ sqlsrv_close($this->_connection);
+ }
+ $this->_connection = null;
+ }
+
+ /**
+ * Returns an SQL statement for preparation.
+ *
+ * @param string $sql The SQL statement with placeholders.
+ * @return Zend_Db_Statement_Sqlsrv
+ */
+ public function prepare($sql)
+ {
+ $this->_connect();
+ $stmtClass = $this->_defaultStmtClass;
+
+ if (!class_exists($stmtClass)) {
+ /**
+ * @see Zend_Loader
+ */
+ require_once 'Zend/Loader.php';
+ Zend_Loader::loadClass($stmtClass);
+ }
+
+ $stmt = new $stmtClass($this, $sql);
+ $stmt->setFetchMode($this->_fetchMode);
+ return $stmt;
+ }
+
+ /**
+ * Quote a raw string.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (is_int($value)) {
+ return $value;
+ } elseif (is_float($value)) {
+ return sprintf('%F', $value);
+ }
+
+ $value = addcslashes($value, "\000\032");
+ return "'" . str_replace("'", "''", $value) . "'";
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ if ($tableName) {
+ $tableName = $this->quote($tableName);
+ $sql = 'SELECT IDENT_CURRENT (' . $tableName . ') as Current_Identity';
+ return (string) $this->fetchOne($sql);
+ }
+
+ if ($this->_lastInsertId > 0) {
+ return (string) $this->_lastInsertId;
+ }
+
+ $sql = $this->_lastInsertSQL;
+ return (string) $this->fetchOne($sql);
+ }
+
+ /**
+ * Inserts a table row with specified data.
+ *
+ * @param mixed $table The table to insert data into.
+ * @param array $bind Column-value pairs.
+ * @return int The number of affected rows.
+ */
+ public function insert($table, array $bind)
+ {
+ // extract and quote col names from the array keys
+ $cols = [];
+ $vals = [];
+ foreach ($bind as $col => $val) {
+ $cols[] = $this->quoteIdentifier($col, true);
+ if ($val instanceof Zend_Db_Expr) {
+ $vals[] = $val->__toString();
+ unset($bind[$col]);
+ } else {
+ $vals[] = '?';
+ }
+ }
+
+ // build the statement
+ $sql = "INSERT INTO "
+ . $this->quoteIdentifier($table, true)
+ . ' (' . implode(', ', $cols) . ') '
+ . 'VALUES (' . implode(', ', $vals) . ')'
+ . ' ' . $this->_lastInsertSQL;
+
+ // execute the statement and return the number of affected rows
+ $stmt = $this->query($sql, array_values($bind));
+ $result = $stmt->rowCount();
+
+ $stmt->nextRowset();
+
+ $this->_lastInsertId = $stmt->fetchColumn();
+
+ return $result;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $this->_connect();
+ $sql = "SELECT name FROM sysobjects WHERE type = 'U' ORDER BY name";
+ return $this->fetchCol($sql);
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @todo Discover integer unsigned property.
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ /**
+ * Discover metadata information about this table.
+ */
+ $sql = "exec sp_columns @table_name = " . $this->quoteIdentifier($tableName, true);
+ $stmt = $this->query($sql);
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ // ZF-7698
+ $stmt->closeCursor();
+
+ if (count($result) === 0) {
+ return [];
+ }
+
+ $owner = 1;
+ $table_name = 2;
+ $column_name = 3;
+ $type_name = 5;
+ $precision = 6;
+ $length = 7;
+ $scale = 8;
+ $nullable = 10;
+ $column_def = 12;
+ $column_position = 16;
+
+ /**
+ * Discover primary key column(s) for this table.
+ */
+ $tableOwner = $result[0][$owner];
+ $sql = "exec sp_pkeys @table_owner = " . $tableOwner
+ . ", @table_name = " . $this->quoteIdentifier($tableName, true);
+ $stmt = $this->query($sql);
+
+ $primaryKeysResult = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+ $primaryKeyColumn = [];
+
+ // Per http://msdn.microsoft.com/en-us/library/ms189813.aspx,
+ // results from sp_keys stored procedure are:
+ // 0=TABLE_QUALIFIER 1=TABLE_OWNER 2=TABLE_NAME 3=COLUMN_NAME 4=KEY_SEQ 5=PK_NAME
+
+ $pkey_column_name = 3;
+ $pkey_key_seq = 4;
+ foreach ($primaryKeysResult as $pkeysRow) {
+ $primaryKeyColumn[$pkeysRow[$pkey_column_name]] = $pkeysRow[$pkey_key_seq];
+ }
+
+ $desc = [];
+ $p = 1;
+ foreach ($result as $key => $row) {
+ $identity = false;
+ $words = explode(' ', $row[$type_name], 2);
+ if (isset($words[0])) {
+ $type = $words[0];
+ if (isset($words[1])) {
+ $identity = (bool) preg_match('/identity/', $words[1]);
+ }
+ }
+
+ $isPrimary = array_key_exists($row[$column_name], $primaryKeyColumn);
+ if ($isPrimary) {
+ $primaryPosition = $primaryKeyColumn[$row[$column_name]];
+ } else {
+ $primaryPosition = null;
+ }
+
+ $desc[$this->foldCase($row[$column_name])] = [
+ 'SCHEMA_NAME' => null, // @todo
+ 'TABLE_NAME' => $this->foldCase($row[$table_name]),
+ 'COLUMN_NAME' => $this->foldCase($row[$column_name]),
+ 'COLUMN_POSITION' => (int) $row[$column_position],
+ 'DATA_TYPE' => $type,
+ 'DEFAULT' => $row[$column_def],
+ 'NULLABLE' => (bool) $row[$nullable],
+ 'LENGTH' => $row[$length],
+ 'SCALE' => $row[$scale],
+ 'PRECISION' => $row[$precision],
+ 'UNSIGNED' => null, // @todo
+ 'PRIMARY' => $isPrimary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity,
+ ];
+ }
+
+ return $desc;
+ }
+
+ /**
+ * Leave autocommit mode and begin a transaction.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Sqlsrv_Exception
+ */
+ protected function _beginTransaction()
+ {
+ if (!sqlsrv_begin_transaction($this->_connection)) {
+ require_once 'Zend/Db/Adapter/Sqlsrv/Exception.php';
+ throw new Zend_Db_Adapter_Sqlsrv_Exception(sqlsrv_errors());
+ }
+ }
+
+ /**
+ * Commit a transaction and return to autocommit mode.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Sqlsrv_Exception
+ */
+ protected function _commit()
+ {
+ if (!sqlsrv_commit($this->_connection)) {
+ require_once 'Zend/Db/Adapter/Sqlsrv/Exception.php';
+ throw new Zend_Db_Adapter_Sqlsrv_Exception(sqlsrv_errors());
+ }
+ }
+
+ /**
+ * Roll back a transaction and return to autocommit mode.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Sqlsrv_Exception
+ */
+ protected function _rollBack()
+ {
+ if (!sqlsrv_rollback($this->_connection)) {
+ require_once 'Zend/Db/Adapter/Sqlsrv/Exception.php';
+ throw new Zend_Db_Adapter_Sqlsrv_Exception(sqlsrv_errors());
+ }
+ }
+
+ /**
+ * Set the fetch mode.
+ *
+ * @todo Support FETCH_CLASS and FETCH_INTO.
+ *
+ * @param integer $mode A fetch mode.
+ * @return void
+ * @throws Zend_Db_Adapter_Sqlsrv_Exception
+ */
+ public function setFetchMode($mode)
+ {
+ switch ($mode) {
+ case Zend_Db::FETCH_NUM: // seq array
+ case Zend_Db::FETCH_ASSOC: // assoc array
+ case Zend_Db::FETCH_BOTH: // seq+assoc array
+ case Zend_Db::FETCH_OBJ: // object
+ $this->_fetchMode = $mode;
+ break;
+ case Zend_Db::FETCH_BOUND: // bound to PHP variable
+ require_once 'Zend/Db/Adapter/Sqlsrv/Exception.php';
+ throw new Zend_Db_Adapter_Sqlsrv_Exception('FETCH_BOUND is not supported yet');
+ break;
+ default:
+ require_once 'Zend/Db/Adapter/Sqlsrv/Exception.php';
+ throw new Zend_Db_Adapter_Sqlsrv_Exception("Invalid fetch mode '$mode' specified");
+ break;
+ }
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @return string
+ * @throws Zend_Db_Adapter_Sqlsrv_Exception
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = (int)$count;
+ if ($count <= 0) {
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = (int)$offset;
+
+ if ($offset < 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ require_once 'Zend/Db/Adapter/Exception.php';
+ throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ if ($offset === 0) {
+ $sql = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . $count . ' ', $sql);
+ } else {
+ $orderby = stristr($sql, 'ORDER BY');
+
+ if (!$orderby) {
+ $over = 'ORDER BY (SELECT 0)';
+ } else {
+ $over = preg_replace('/\"[^,]*\".\"([^,]*)\"/i', '"inner_tbl"."$1"', $orderby);
+ }
+
+ // Remove ORDER BY clause from $sql
+ $sql = preg_replace('/\s+ORDER BY(.*)/', '', $sql);
+
+ // Add ORDER BY clause as an argument for ROW_NUMBER()
+ $sql = "SELECT ROW_NUMBER() OVER ($over) AS \"ZEND_DB_ROWNUM\", * FROM ($sql) AS inner_tbl";
+
+ $start = $offset + 1;
+
+ if ($count == PHP_INT_MAX) {
+ $sql = "WITH outer_tbl AS ($sql) SELECT * FROM outer_tbl WHERE \"ZEND_DB_ROWNUM\" >= $start";
+ }
+ else {
+ $end = $offset + $count;
+ $sql = "WITH outer_tbl AS ($sql) SELECT * FROM outer_tbl WHERE \"ZEND_DB_ROWNUM\" BETWEEN $start AND $end";
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Check if the adapter supports real SQL parameters.
+ *
+ * @param string $type 'positional' or 'named'
+ * @return bool
+ */
+ public function supportsParameters($type)
+ {
+ if ($type == 'positional') {
+ return true;
+ }
+
+ // if its 'named' or anything else
+ return false;
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ *
+ * @return string
+ */
+ public function getServerVersion()
+ {
+ $this->_connect();
+ $serverInfo = sqlsrv_server_info($this->_connection);
+
+ if ($serverInfo !== false) {
+ return $serverInfo['SQLServerVersion'];
+ }
+
+ return null;
+ }
+}
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Sqlsrv/Exception.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Sqlsrv/Exception.php
new file mode 100644
index 0000000000..16bc5ee917
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Adapter/Sqlsrv/Exception.php
@@ -0,0 +1,63 @@
+_expression = (string) $expression;
+ }
+
+ /**
+ * @return string The string of the SQL expression stored in this object.
+ */
+ public function __toString()
+ {
+ return $this->_expression;
+ }
+
+}
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Profiler.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Profiler.php
new file mode 100644
index 0000000000..e7d024c3aa
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Profiler.php
@@ -0,0 +1,473 @@
+setEnabled($enabled);
+ }
+
+ /**
+ * Enable or disable the profiler. If $enable is false, the profiler
+ * is disabled and will not log any queries sent to it.
+ *
+ * @param boolean $enable
+ * @return Zend_Db_Profiler Provides a fluent interface
+ */
+ public function setEnabled($enable)
+ {
+ $this->_enabled = (boolean) $enable;
+
+ return $this;
+ }
+
+ /**
+ * Get the current state of enable. If True is returned,
+ * the profiler is enabled.
+ *
+ * @return boolean
+ */
+ public function getEnabled()
+ {
+ return $this->_enabled;
+ }
+
+ /**
+ * Sets a minimum number of seconds for saving query profiles. If this
+ * is set, only those queries whose elapsed time is equal or greater than
+ * $minimumSeconds will be saved. To save all queries regardless of
+ * elapsed time, set $minimumSeconds to null.
+ *
+ * @param integer $minimumSeconds OPTIONAL
+ * @return Zend_Db_Profiler Provides a fluent interface
+ */
+ public function setFilterElapsedSecs($minimumSeconds = null)
+ {
+ if (null === $minimumSeconds) {
+ $this->_filterElapsedSecs = null;
+ } else {
+ $this->_filterElapsedSecs = (integer) $minimumSeconds;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the minimum number of seconds for saving query profiles, or null if
+ * query profiles are saved regardless of elapsed time.
+ *
+ * @return integer|null
+ */
+ public function getFilterElapsedSecs()
+ {
+ return $this->_filterElapsedSecs;
+ }
+
+ /**
+ * Sets the types of query profiles to save. Set $queryType to one of
+ * the Zend_Db_Profiler::* constants to only save profiles for that type of
+ * query. To save more than one type, logical OR them together. To
+ * save all queries regardless of type, set $queryType to null.
+ *
+ * @param integer $queryTypes OPTIONAL
+ * @return Zend_Db_Profiler Provides a fluent interface
+ */
+ public function setFilterQueryType($queryTypes = null)
+ {
+ $this->_filterTypes = $queryTypes;
+
+ return $this;
+ }
+
+ /**
+ * Returns the types of query profiles saved, or null if queries are saved regardless
+ * of their types.
+ *
+ * @return integer|null
+ * @see Zend_Db_Profiler::setFilterQueryType()
+ */
+ public function getFilterQueryType()
+ {
+ return $this->_filterTypes;
+ }
+
+ /**
+ * Clears the history of any past query profiles. This is relentless
+ * and will even clear queries that were started and may not have
+ * been marked as ended.
+ *
+ * @return Zend_Db_Profiler Provides a fluent interface
+ */
+ public function clear()
+ {
+ $this->_queryProfiles = [];
+
+ return $this;
+ }
+
+ /**
+ * Clone a profiler query
+ *
+ * @param Zend_Db_Profiler_Query $query
+ * @return integer or null
+ */
+ public function queryClone(Zend_Db_Profiler_Query $query)
+ {
+ $this->_queryProfiles[] = clone $query;
+
+ end($this->_queryProfiles);
+
+ return key($this->_queryProfiles);
+ }
+
+ /**
+ * Starts a query. Creates a new query profile object (Zend_Db_Profiler_Query)
+ * and returns the "query profiler handle". Run the query, then call
+ * queryEnd() and pass it this handle to make the query as ended and
+ * record the time. If the profiler is not enabled, this takes no
+ * action and immediately returns null.
+ *
+ * @param string $queryText SQL statement
+ * @param integer $queryType OPTIONAL Type of query, one of the Zend_Db_Profiler::* constants
+ * @return integer|null
+ */
+ public function queryStart($queryText, $queryType = null)
+ {
+ if (!$this->_enabled) {
+ return null;
+ }
+
+ // make sure we have a query type
+ if (null === $queryType) {
+ switch (strtolower(substr(ltrim($queryText), 0, 6))) {
+ case 'insert':
+ $queryType = self::INSERT;
+ break;
+ case 'update':
+ $queryType = self::UPDATE;
+ break;
+ case 'delete':
+ $queryType = self::DELETE;
+ break;
+ case 'select':
+ $queryType = self::SELECT;
+ break;
+ default:
+ $queryType = self::QUERY;
+ break;
+ }
+ }
+
+ /**
+ * @see Zend_Db_Profiler_Query
+ */
+ require_once 'Zend/Db/Profiler/Query.php';
+ $this->_queryProfiles[] = new Zend_Db_Profiler_Query($queryText, $queryType);
+
+ end($this->_queryProfiles);
+
+ return key($this->_queryProfiles);
+ }
+
+ /**
+ * Ends a query. Pass it the handle that was returned by queryStart().
+ * This will mark the query as ended and save the time.
+ *
+ * @param integer $queryId
+ * @throws Zend_Db_Profiler_Exception
+ * @return string Inform that a query is stored or ignored.
+ */
+ public function queryEnd($queryId)
+ {
+ // Don't do anything if the Zend_Db_Profiler is not enabled.
+ if (!$this->_enabled) {
+ return self::IGNORED;
+ }
+
+ // Check for a valid query handle.
+ if (!isset($this->_queryProfiles[$queryId])) {
+ /**
+ * @see Zend_Db_Profiler_Exception
+ */
+ require_once 'Zend/Db/Profiler/Exception.php';
+ throw new Zend_Db_Profiler_Exception("Profiler has no query with handle '$queryId'.");
+ }
+
+ $qp = $this->_queryProfiles[$queryId];
+
+ // Ensure that the query profile has not already ended
+ if ($qp->hasEnded()) {
+ /**
+ * @see Zend_Db_Profiler_Exception
+ */
+ require_once 'Zend/Db/Profiler/Exception.php';
+ throw new Zend_Db_Profiler_Exception("Query with profiler handle '$queryId' has already ended.");
+ }
+
+ // End the query profile so that the elapsed time can be calculated.
+ $qp->end();
+
+ /**
+ * If filtering by elapsed time is enabled, only keep the profile if
+ * it ran for the minimum time.
+ */
+ if (null !== $this->_filterElapsedSecs && $qp->getElapsedSecs() < $this->_filterElapsedSecs) {
+ unset($this->_queryProfiles[$queryId]);
+ return self::IGNORED;
+ }
+
+ /**
+ * If filtering by query type is enabled, only keep the query if
+ * it was one of the allowed types.
+ */
+ if (null !== $this->_filterTypes && !($qp->getQueryType() & $this->_filterTypes)) {
+ unset($this->_queryProfiles[$queryId]);
+ return self::IGNORED;
+ }
+
+ return self::STORED;
+ }
+
+ /**
+ * Get a profile for a query. Pass it the same handle that was returned
+ * by queryStart() and it will return a Zend_Db_Profiler_Query object.
+ *
+ * @param integer $queryId
+ * @throws Zend_Db_Profiler_Exception
+ * @return Zend_Db_Profiler_Query
+ */
+ public function getQueryProfile($queryId)
+ {
+ if (!array_key_exists($queryId, $this->_queryProfiles)) {
+ /**
+ * @see Zend_Db_Profiler_Exception
+ */
+ require_once 'Zend/Db/Profiler/Exception.php';
+ throw new Zend_Db_Profiler_Exception("Query handle '$queryId' not found in profiler log.");
+ }
+
+ return $this->_queryProfiles[$queryId];
+ }
+
+ /**
+ * Get an array of query profiles (Zend_Db_Profiler_Query objects). If $queryType
+ * is set to one of the Zend_Db_Profiler::* constants then only queries of that
+ * type will be returned. Normally, queries that have not yet ended will
+ * not be returned unless $showUnfinished is set to True. If no
+ * queries were found, False is returned. The returned array is indexed by the query
+ * profile handles.
+ *
+ * @param integer $queryType
+ * @param boolean $showUnfinished
+ * @return array|false
+ */
+ public function getQueryProfiles($queryType = null, $showUnfinished = false)
+ {
+ $queryProfiles = [];
+ foreach ($this->_queryProfiles as $key => $qp) {
+ if ($queryType === null) {
+ $condition = true;
+ } else {
+ $condition = ($qp->getQueryType() & $queryType);
+ }
+
+ if (($qp->hasEnded() || $showUnfinished) && $condition) {
+ $queryProfiles[$key] = $qp;
+ }
+ }
+
+ if (empty($queryProfiles)) {
+ $queryProfiles = false;
+ }
+
+ return $queryProfiles;
+ }
+
+ /**
+ * Get the total elapsed time (in seconds) of all of the profiled queries.
+ * Only queries that have ended will be counted. If $queryType is set to
+ * one or more of the Zend_Db_Profiler::* constants, the elapsed time will be calculated
+ * only for queries of the given type(s).
+ *
+ * @param integer $queryType OPTIONAL
+ * @return float
+ */
+ public function getTotalElapsedSecs($queryType = null)
+ {
+ $elapsedSecs = 0;
+ foreach ($this->_queryProfiles as $key => $qp) {
+ if (null === $queryType) {
+ $condition = true;
+ } else {
+ $condition = ($qp->getQueryType() & $queryType);
+ }
+ if (($qp->hasEnded()) && $condition) {
+ $elapsedSecs += $qp->getElapsedSecs();
+ }
+ }
+ return $elapsedSecs;
+ }
+
+ /**
+ * Get the total number of queries that have been profiled. Only queries that have ended will
+ * be counted. If $queryType is set to one of the Zend_Db_Profiler::* constants, only queries of
+ * that type will be counted.
+ *
+ * @param integer $queryType OPTIONAL
+ * @return integer
+ */
+ public function getTotalNumQueries($queryType = null)
+ {
+ if (null === $queryType) {
+ return count($this->_queryProfiles);
+ }
+
+ $numQueries = 0;
+ foreach ($this->_queryProfiles as $qp) {
+ if ($qp->hasEnded() && ($qp->getQueryType() & $queryType)) {
+ $numQueries++;
+ }
+ }
+
+ return $numQueries;
+ }
+
+ /**
+ * Get the Zend_Db_Profiler_Query object for the last query that was run, regardless if it has
+ * ended or not. If the query has not ended, its end time will be null. If no queries have
+ * been profiled, false is returned.
+ *
+ * @return Zend_Db_Profiler_Query|false
+ */
+ public function getLastQueryProfile()
+ {
+ if (empty($this->_queryProfiles)) {
+ return false;
+ }
+
+ end($this->_queryProfiles);
+
+ return current($this->_queryProfiles);
+ }
+
+}
+
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Profiler/Exception.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Profiler/Exception.php
new file mode 100644
index 0000000000..9f715f3fea
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Profiler/Exception.php
@@ -0,0 +1,40 @@
+_label = $label;
+ if(!$this->_label) {
+ $this->_label = 'Zend_Db_Profiler_Firebug';
+ }
+ }
+
+ /**
+ * Enable or disable the profiler. If $enable is false, the profiler
+ * is disabled and will not log any queries sent to it.
+ *
+ * @param boolean $enable
+ * @return Zend_Db_Profiler Provides a fluent interface
+ */
+ public function setEnabled($enable)
+ {
+ parent::setEnabled($enable);
+
+ if ($this->getEnabled()) {
+
+ if (!$this->_message) {
+ $this->_message = new Zend_Wildfire_Plugin_FirePhp_TableMessage($this->_label);
+ $this->_message->setBuffered(true);
+ $this->_message->setHeader(['Time','Event','Parameters']);
+ $this->_message->setDestroy(true);
+ $this->_message->setOption('includeLineNumbers', false);
+ Zend_Wildfire_Plugin_FirePhp::getInstance()->send($this->_message);
+ }
+
+ } else {
+
+ if ($this->_message) {
+ $this->_message->setDestroy(true);
+ $this->_message = null;
+ }
+
+ }
+
+ return $this;
+ }
+
+ /**
+ * Intercept the query end and log the profiling data.
+ *
+ * @param integer $queryId
+ * @throws Zend_Db_Profiler_Exception
+ * @return void
+ */
+ public function queryEnd($queryId)
+ {
+ $state = parent::queryEnd($queryId);
+
+ if (!$this->getEnabled() || $state == self::IGNORED) {
+ return;
+ }
+
+ $this->_message->setDestroy(false);
+
+ $profile = $this->getQueryProfile($queryId);
+
+ $this->_totalElapsedTime += $profile->getElapsedSecs();
+
+ $this->_message->addRow([(string)round($profile->getElapsedSecs(),5),
+ $profile->getQuery(),
+ ($params=$profile->getQueryParams())?$params:null]);
+
+ $this->updateMessageLabel();
+ }
+
+ /**
+ * Update the label of the message holding the profile info.
+ *
+ * @return void
+ */
+ protected function updateMessageLabel()
+ {
+ if (!$this->_message) {
+ return;
+ }
+ $this->_message->setLabel(str_replace(['%label%',
+ '%totalCount%',
+ '%totalDuration%'],
+ [$this->_label,
+ $this->getTotalNumQueries(),
+ (string)round($this->_totalElapsedTime,5)],
+ $this->_label_template));
+ }
+}
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Profiler/Query.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Profiler/Query.php
new file mode 100644
index 0000000000..7d6a65199c
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Profiler/Query.php
@@ -0,0 +1,213 @@
+_query = $query;
+ $this->_queryType = $queryType;
+ // by default, and for backward-compatibility, start the click ticking
+ $this->start();
+ }
+
+ /**
+ * Clone handler for the query object.
+ * @return void
+ */
+ public function __clone()
+ {
+ $this->_boundParams = [];
+ $this->_endedMicrotime = null;
+ $this->start();
+ }
+
+ /**
+ * Starts the elapsed time click ticking.
+ * This can be called subsequent to object creation,
+ * to restart the clock. For instance, this is useful
+ * right before executing a prepared query.
+ *
+ * @return void
+ */
+ public function start()
+ {
+ $this->_startedMicrotime = microtime(true);
+ }
+
+ /**
+ * Ends the query and records the time so that the elapsed time can be determined later.
+ *
+ * @return void
+ */
+ public function end()
+ {
+ $this->_endedMicrotime = microtime(true);
+ }
+
+ /**
+ * Returns true if and only if the query has ended.
+ *
+ * @return boolean
+ */
+ public function hasEnded()
+ {
+ return $this->_endedMicrotime !== null;
+ }
+
+ /**
+ * Get the original SQL text of the query.
+ *
+ * @return string
+ */
+ public function getQuery()
+ {
+ return $this->_query;
+ }
+
+ /**
+ * Get the type of this query (one of the Zend_Db_Profiler::* constants)
+ *
+ * @return integer
+ */
+ public function getQueryType()
+ {
+ return $this->_queryType;
+ }
+
+ /**
+ * @param string $param
+ * @param mixed $variable
+ * @return void
+ */
+ public function bindParam($param, $variable)
+ {
+ $this->_boundParams[$param] = $variable;
+ }
+
+ /**
+ * @param array $param
+ * @return void
+ */
+ public function bindParams(array $params)
+ {
+ if (array_key_exists(0, $params)) {
+ array_unshift($params, null);
+ unset($params[0]);
+ }
+ foreach ($params as $param => $value) {
+ $this->bindParam($param, $value);
+ }
+ }
+
+ /**
+ * @return array
+ */
+ public function getQueryParams()
+ {
+ return $this->_boundParams;
+ }
+
+ /**
+ * Get the elapsed time (in seconds) that the query ran.
+ * If the query has not yet ended, false is returned.
+ *
+ * @return float|false
+ */
+ public function getElapsedSecs()
+ {
+ if (null === $this->_endedMicrotime) {
+ return false;
+ }
+
+ return $this->_endedMicrotime - $this->_startedMicrotime;
+ }
+
+ /**
+ * Get the time (in seconds) when the profiler started running.
+ *
+ * @return bool|float
+ */
+ public function getStartedMicrotime()
+ {
+ if(null === $this->_startedMicrotime) {
+ return false;
+ }
+
+ return $this->_startedMicrotime;
+ }
+}
+
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Schema/AbstractChange.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Schema/AbstractChange.php
new file mode 100644
index 0000000000..16590ea095
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Schema/AbstractChange.php
@@ -0,0 +1,41 @@
+_db = $db;
+ $this->_tablePrefix = $tablePrefix;
+ }
+
+ /**
+ * Changes to be applied in this change
+ *
+ * @return null
+ */
+ abstract function up();
+
+ /**
+ * Rollback changes made in up()
+ *
+ * @return null
+ */
+ abstract function down();
+
+}
\ No newline at end of file
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Schema/Exception.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Schema/Exception.php
new file mode 100644
index 0000000000..38e996cc90
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Schema/Exception.php
@@ -0,0 +1,3 @@
+ prefix string to place before table names
+ * 'schema_version_table_name' => name of table to use for holding the schema version number
+ *
+ *
+ * @param string $dir Directory where migrations files are stored
+ * @param Zend_Db_Adapter_Abstract $db Database adapter
+ * @param string $tablePrefix Table prefix to be used by change files
+ */
+ public function __construct($dir, Zend_Db_Adapter_Abstract $db, $tablePrefix='')
+ {
+ $this->_dir = realpath($dir);
+ $this->_db = $db;
+ $this->_tablePrefix = $tablePrefix;
+ }
+
+ /**
+ * Retrieves the current database schema version from the database
+ *
+ * If the table does not exist, it will be created and the version will
+ * be set to 0.
+ *
+ * @return string
+ */
+ public function getCurrentSchemaVersion()
+ {
+ // Ensure we have valid connection to the database
+ if (!$this->_db->isConnected()) {
+ $this->_db->getServerVersion();
+ }
+ $schemaVersionTableName = $this->getPrefixedSchemaVersionTableName();
+
+ $sql = "SELECT version FROM " . $schemaVersionTableName;
+ try {
+ $version = $this->_db->fetchOne($sql);
+ } catch (Zend_Db_Exception $e) {
+ // exception means that the schema version table doesn't exist, so create it
+ $createSql = "CREATE TABLE $schemaVersionTableName (
+ version bigint NOT NULL,
+ PRIMARY KEY (version)
+ )";
+ $this->_db->query($createSql);
+ $insertSql = "INSERT INTO $schemaVersionTableName (version) VALUES (0)";
+ $this->_db->query($insertSql);
+ $version = $this->_db->fetchOne($sql);
+ }
+
+ return $version;
+ }
+
+ /**
+ * Updates the database schema to a specified version. If upgrading (increasing
+ * version number) the schema version will be the largest available version
+ * that is less than or equal to the specified version. ie, if the highest version
+ * is 050 and 7000 is specified for $version, the resulting version will be
+ * 050. If downgrading (decreasing version number) the ending version will be
+ * the highest version that is less than or equal to the specified version
+ * number. i.e, if versions 10, 15 and 20 exist and the version is updated
+ * to 19, the resulting version will be 15 since version 20 will be downgraded.
+ *
+ * The method automatcally determines the direction of the migration by comparing
+ * the current version (from the database) and the desired version. If they
+ * are the same, no migration will be performed and the version will remain
+ * the same.
+ *
+ * @param string $version
+ *
+ * @return string
+ */
+ public function updateTo($version = null)
+ {
+ if (is_null($version)) {
+ $version = PHP_INT_MAX;
+ }
+ $version = (int)$version;
+ $currentVersion = $this->getCurrentSchemaVersion();
+ if($currentVersion == $version) {
+ return self::RESULT_AT_CURRENT_VERSION;
+ }
+
+ $migrations = $this->_getMigrationFiles($currentVersion, $version);
+ if(empty($migrations)) {
+ if ($version == PHP_INT_MAX) {
+ return self::RESULT_AT_CURRENT_VERSION;
+ }
+ return self::RESULT_NO_MIGRATIONS_FOUND;
+ }
+
+ $direction = 'up';
+ if ($currentVersion > $version) {
+ $direction = 'down';
+ }
+ foreach ($migrations as $migration) {
+ $this->_processFile($migration, $direction);
+ }
+
+ // figure out what the real version we're going to is if going down
+ // TODO: make this more efficient by caching file information instead
+ // of fetching it again.
+ if ($direction == 'down') {
+ $files = $this->_getMigrationFiles($version, 0);
+ if (empty($files)) {
+ $realVersion = 0;
+ } else {
+ $versionFile = array_shift($files);
+ $realVersion = $versionFile['version'];
+ }
+ // update the database to the version we're actually at
+ $this->_updateSchemaVersion($realVersion);
+ }
+
+ return self::RESULT_OK;
+ }
+
+ /**
+ * Increments the database version a specified number of upgrades. For instance,
+ * if $versions is 1, it will update to the next highest version of the database.
+ *
+ * If $versions is provided and less than 1, it will assume 1 and update
+ * a single version. If a number higher than the available upgradable versions
+ * is specified, it will update to the highest version number.
+ *
+ * If the database is already at the highest version number available, it will
+ * not do anything and indicate it is at the maximum version number via
+ * the return value.
+ *
+ * @param int $versions Number of versions to increment. Must be 1 or greater
+ *
+ * @return string
+ */
+ public function incrementVersion($versions)
+ {
+ $versions = (int)$versions;
+ if ($versions < 1) {
+ $versions = 1;
+ }
+ $currentVersion = $this->getCurrentSchemaVersion();
+
+ $files = $this->_getMigrationFiles($currentVersion, PHP_INT_MAX);
+ if (empty($files)) {
+ return self::RESULT_AT_MAXIMUM_VERSION;
+ }
+
+ $files = array_slice($files, 0, $versions);
+
+ $nextFile = array_pop($files);
+ $nextVersion = $nextFile['version'];
+
+ return $this->updateTo($nextVersion);
+ }
+
+ /**
+ * Decrements the version of the database by the specified number of versions.
+ *
+ * If the database is already at the lowest version number, it will indicate
+ * this through the return value.
+ *
+ * @param int $versions Number of versions to decrement.
+ *
+ * @return string
+ */
+ public function decrementVersion($versions)
+ {
+ $versions = (int)$versions;
+ if ($versions < 1) {
+ $versions = 1;
+ }
+ $currentVersion = $this->getCurrentSchemaVersion();
+
+ $files = $this->_getMigrationFiles($currentVersion, 0);
+ if (empty($files)) {
+ return self::RESULT_AT_MINIMUM_VERSION;
+ }
+
+ $files = array_slice($files, 0, $versions+1);
+ $nextFile = array_pop($files);
+ $nextVersion = $nextFile['version'];
+
+ return $this->updateTo($nextVersion);
+ }
+
+ /**
+ * Retrieves the migration files that are needed to take the database from
+ * its a specified version (current version) to the desired version. It
+ * will also determine the direction of the migration and sort the files
+ * accordingly.
+ *
+ * @param string $currentVersion Version to migrate database from
+ * @param string $stopVersion Version to migrate database to
+ * @param string $dir Directory containing migration files
+ *
+ * @throws Zend_Db_Schema_Exception
+ *
+ * @return array of file name, version and class name
+ */
+ protected function _getMigrationFiles($currentVersion, $stopVersion, $dir = null)
+ {
+ if ($dir === null) {
+ $dir = $this->_dir;
+ }
+
+ $direction = 'up';
+ $from = $currentVersion;
+ $to = $stopVersion;
+ if($stopVersion < $currentVersion) {
+ $direction = 'down';
+ $from = $stopVersion;
+ $to = $currentVersion;
+ }
+
+ $files = [];
+ if (!is_dir($dir) || !is_readable($dir)) {
+ return $files;
+ }
+
+ $d = dir($dir);
+ $seen = [];
+ while (false !== ($entry = $d->read())) {
+ if (preg_match('/^([0-9]+)\-(.*)\.php/i', $entry, $matches) ) {
+ $versionNumber = (int)$matches[1];
+ if (isset($seen[$versionNumber])) {
+ throw new Zend_Db_Schema_Exception("version $versionNumber is used for multiple migrations.");
+ }
+ $seen[$versionNumber] = true;
+ $className = $matches[2];
+ if ($versionNumber > $from && $versionNumber <= $to) {
+ $path = $this->_relativePath($this->_dir, $dir);
+ $files["v{$matches[1]}"] = [
+ 'path'=>$path,
+ 'filename'=>$entry,
+ 'version'=>$versionNumber,
+ 'classname'=>$className];
+ }
+ } elseif ($entry != '.' && $entry != '..') {
+ $subdir = $dir . '/' . $entry;
+ if (is_dir($subdir) && is_readable($subdir)) {
+ $files = array_merge(
+ $files,
+ $this->_getMigrationFiles(
+ $currentVersion, $stopVersion, $subdir
+ )
+ );
+ }
+ }
+ }
+ $d->close();
+
+ if($direction == 'up') {
+ ksort($files);
+ } else {
+ krsort($files);
+ }
+
+ return $files;
+ }
+
+ /**
+ * Runs a migration file according to the information provided. The
+ * migration parameter is an array or object allowing ArrayAccess with the
+ * following fields:
+ *
+ * version - The version of the migration this file represents
+ * filename - The name of the file containing the code to upgrade or downgrade the database
+ * classname - The name of the class contained in the file
+ *
+ * The direction parameter should be one of either "up" or "down" and indicates
+ * which of the migration class methods should be executed. The up method is
+ * assumed to move the database schema to the next version while down is
+ * assumed to undo whatever up did.
+ *
+ * @param array|ArrayAccess $migration Information about the migration file
+ * @param string $direction "up" or "down"
+ *
+ * @throws Zend_Db_Schema_Exception
+ *
+ * @return null
+ *
+ * @todo I think there may be a problem with different migration files using
+ * the same class name. -- Confirmed. If you migrate single versions at a time,
+ * i.e. using increment or decrement then you will have no problems. If you
+ * try to migrate through files where there are more than one file with a
+ * particular class name, it will fail because it tries to redeclare a class
+ * that already exists.
+ */
+ protected function _processFile($migration, $direction)
+ {
+ $path = $migration['path'];
+ $version = $migration['version'];
+ $filename = $migration['filename'];
+ $classname = $migration['classname'];
+ require_once($this->_dir.'/'.$path.'/'.$filename);
+ if (!class_exists($classname, false)) {
+ throw new Zend_Db_Schema_Exception("Could not find class '$classname' in file '$filename'");
+ }
+ $class = new $classname($this->_db, $this->_tablePrefix);
+ $class->$direction();
+
+ if($direction == 'down') {
+ // current version is actually one lower than this version now
+ $version--;
+ }
+ $this->_updateSchemaVersion($version);
+ }
+
+ /**
+ * Updates the schema version in the database.
+ *
+ * @param int $version Version to update into database
+ *
+ * @return null
+ */
+ protected function _updateSchemaVersion($version)
+ {
+ $schemaVersionTableName = $this->getPrefixedSchemaVersionTableName();
+ $sql = "UPDATE $schemaVersionTableName SET version = " . (int)$version;
+ $this->_db->query($sql);
+ }
+
+ /**
+ * Retrieves the prefixed version of the schema version table.
+ *
+ * @return string
+ */
+ public function getPrefixedSchemaVersionTableName()
+ {
+ return $this->_tablePrefix . $this->_schemaVersionTableName;
+ }
+
+ /**
+ * Returns a relative path from one directory to another
+ *
+ * @param string $from Directory to start from
+ * @param string $to Directory to end at
+ * @param string $ps Path seperator
+ *
+ * @return string
+ */
+ protected function _relativePath($from, $to, $ps = DIRECTORY_SEPARATOR)
+ {
+ $arFrom = explode($ps, rtrim($from, $ps));
+ $arTo = explode($ps, rtrim($to, $ps));
+ while (count($arFrom) && count($arTo) && ($arFrom[0] == $arTo[0])) {
+ array_shift($arFrom);
+ array_shift($arTo);
+ }
+ return str_pad("", count($arFrom) * 3, '..'.$ps).implode($ps, $arTo);
+ }
+}
+
diff --git a/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Select.php b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Select.php
new file mode 100644
index 0000000000..69a9e6e4d8
--- /dev/null
+++ b/tests/Frameworks/ZendFramework/Version_1_21/library/Zend/Db/Select.php
@@ -0,0 +1,1401 @@
+ false,
+ self::COLUMNS => [],
+ self::UNION => [],
+ self::FROM => [],
+ self::WHERE => [],
+ self::GROUP => [],
+ self::HAVING => [],
+ self::ORDER => [],
+ self::LIMIT_COUNT => null,
+ self::LIMIT_OFFSET => null,
+ self::FOR_UPDATE => false,
+ self::FOR_UPDATE_MODE => null,
+ ];
+
+ /**
+ * Specify legal join types.
+ *
+ * @var array
+ */
+ protected static $_joinTypes = [
+ self::INNER_JOIN,
+ self::LEFT_JOIN,
+ self::RIGHT_JOIN,
+ self::FULL_JOIN,
+ self::CROSS_JOIN,
+ self::NATURAL_JOIN,
+ ];
+
+ /**
+ * Specify legal union types.
+ *
+ * @var array
+ */
+ protected static $_unionTypes = [
+ self::SQL_UNION,
+ self::SQL_UNION_ALL
+ ];
+
+ /**
+ * The component parts of a SELECT statement.
+ * Initialized to the $_partsInit array in the constructor.
+ *
+ * @var array
+ */
+ protected $_parts = [];
+
+ /**
+ * Tracks which columns are being select from each table and join.
+ *
+ * @var array
+ */
+ protected $_tableCols = [];
+
+ /**
+ * Class constructor
+ *
+ * @param Zend_Db_Adapter_Abstract $adapter
+ */
+ public function __construct(Zend_Db_Adapter_Abstract $adapter)
+ {
+ $this->_adapter = $adapter;
+ $this->_parts = self::$_partsInit;
+ }
+
+ /**
+ * Get bind variables
+ *
+ * @return array
+ */
+ public function getBind()
+ {
+ return $this->_bind;
+ }
+
+ /**
+ * Set bind variables
+ *
+ * @param mixed $bind
+ * @return $this
+ */
+ public function bind($bind)
+ {
+ $this->_bind = $bind;
+
+ return $this;
+ }
+
+ /**
+ * Makes the query SELECT DISTINCT.
+ *
+ * @param bool $flag Whether or not the SELECT is DISTINCT (default true).
+ * @return $this This Zend_Db_Select object.
+ */
+ public function distinct($flag = true)
+ {
+ $this->_parts[self::DISTINCT] = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Adds a FROM table and optional columns to the query.
+ *
+ * The first parameter $name can be a simple string, in which case the
+ * correlation name is generated automatically. If you want to specify
+ * the correlation name, the first parameter must be an associative
+ * array in which the key is the correlation name, and the value is
+ * the physical table name. For example, array('alias' => 'table').
+ * The correlation name is prepended to all columns fetched for this
+ * table.
+ *
+ * The second parameter can be a single string or Zend_Db_Expr object,
+ * or else an array of strings or Zend_Db_Expr objects.
+ *
+ * The first parameter can be null or an empty string, in which case
+ * no correlation name is generated or prepended to the columns named
+ * in the second parameter.
+ *
+ * @param array|string|Zend_Db_Expr $name The table name or an associative array
+ * relating correlation name to table name.
+ * @param array|string|Zend_Db_Expr $cols The columns to select from this table.
+ * @param string $schema The schema name to specify, if any.
+ * @return $this This Zend_Db_Select object.
+ */
+ public function from($name, $cols = '*', $schema = null)
+ {
+ return $this->_join(self::FROM, $name, null, $cols, $schema);
+ }
+
+ /**
+ * Specifies the columns used in the FROM clause.
+ *
+ * The parameter can be a single string or Zend_Db_Expr object,
+ * or else an array of strings or Zend_Db_Expr objects.
+ *
+ * @param array|string|Zend_Db_Expr $cols The columns to select from this table.
+ * @param string $correlationName Correlation name of target table. OPTIONAL
+ * @return $this This Zend_Db_Select object.
+ */
+ public function columns($cols = '*', $correlationName = null)
+ {
+ if ($correlationName === null && count($this->_parts[self::FROM])) {
+ $correlationNameKeys = array_keys($this->_parts[self::FROM]);
+ $correlationName = current($correlationNameKeys);
+ }
+
+ if (!array_key_exists($correlationName, $this->_parts[self::FROM])) {
+ /**
+ * @see Zend_Db_Select_Exception
+ */
+ require_once 'Zend/Db/Select/Exception.php';
+ throw new Zend_Db_Select_Exception("No table has been specified for the FROM clause");
+ }
+
+ $this->_tableCols($correlationName, $cols);
+
+ return $this;
+ }
+
+ /**
+ * Adds a UNION clause to the query.
+ *
+ * The first parameter has to be an array of Zend_Db_Select or
+ * sql query strings.
+ *
+ *
+ * $sql1 = $db->select();
+ * $sql2 = "SELECT ...";
+ * $select = $db->select()
+ * ->union(array($sql1, $sql2))
+ * ->order("id");
+ *
+ *
+ * @param array $select Array of select clauses for the union.
+ * @return $this This Zend_Db_Select object.
+ */
+ public function union($select = [], $type = self::SQL_UNION)
+ {
+ if (!is_array($select)) {
+ require_once 'Zend/Db/Select/Exception.php';
+ throw new Zend_Db_Select_Exception(
+ "union() only accepts an array of Zend_Db_Select instances of sql query strings."
+ );
+ }
+
+ if (!in_array($type, self::$_unionTypes)) {
+ require_once 'Zend/Db/Select/Exception.php';
+ throw new Zend_Db_Select_Exception("Invalid union type '{$type}'");
+ }
+
+ foreach ($select as $target) {
+ $this->_parts[self::UNION][] = [$target, $type];
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds a JOIN table and columns to the query.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Zend_Db_Expr $name The table name.
+ * @param string $cond Join on this condition.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return $this This Zend_Db_Select object.
+ */
+ public function join($name, $cond, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->joinInner($name, $cond, $cols, $schema);
+ }
+
+ /**
+ * Add an INNER JOIN table and colums to the query
+ * Rows in both tables are matched according to the expression
+ * in the $cond argument. The result set is comprised
+ * of all cases where rows from the left table match
+ * rows from the right table.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Zend_Db_Expr $name The table name.
+ * @param string $cond Join on this condition.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return $this This Zend_Db_Select object.
+ */
+ public function joinInner($name, $cond, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->_join(self::INNER_JOIN, $name, $cond, $cols, $schema);
+ }
+
+ /**
+ * Add a LEFT OUTER JOIN table and colums to the query
+ * All rows from the left operand table are included,
+ * matching rows from the right operand table included,
+ * and the columns from the right operand table are filled
+ * with NULLs if no row exists matching the left table.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Zend_Db_Expr $name The table name.
+ * @param string $cond Join on this condition.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return $this This Zend_Db_Select object.
+ */
+ public function joinLeft($name, $cond, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->_join(self::LEFT_JOIN, $name, $cond, $cols, $schema);
+ }
+
+ /**
+ * Add a RIGHT OUTER JOIN table and colums to the query.
+ * Right outer join is the complement of left outer join.
+ * All rows from the right operand table are included,
+ * matching rows from the left operand table included,
+ * and the columns from the left operand table are filled
+ * with NULLs if no row exists matching the right table.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Zend_Db_Expr $name The table name.
+ * @param string $cond Join on this condition.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return $this This Zend_Db_Select object.
+ */
+ public function joinRight($name, $cond, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->_join(self::RIGHT_JOIN, $name, $cond, $cols, $schema);
+ }
+
+ /**
+ * Add a FULL OUTER JOIN table and colums to the query.
+ * A full outer join is like combining a left outer join
+ * and a right outer join. All rows from both tables are
+ * included, paired with each other on the same row of the
+ * result set if they satisfy the join condition, and otherwise
+ * paired with NULLs in place of columns from the other table.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Zend_Db_Expr $name The table name.
+ * @param string $cond Join on this condition.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return $this This Zend_Db_Select object.
+ */
+ public function joinFull($name, $cond, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->_join(self::FULL_JOIN, $name, $cond, $cols, $schema);
+ }
+
+ /**
+ * Add a CROSS JOIN table and colums to the query.
+ * A cross join is a cartesian product; there is no join condition.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Zend_Db_Expr $name The table name.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return $this This Zend_Db_Select object.
+ */
+ public function joinCross($name, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->_join(self::CROSS_JOIN, $name, null, $cols, $schema);
+ }
+
+ /**
+ * Add a NATURAL JOIN table and colums to the query.
+ * A natural join assumes an equi-join across any column(s)
+ * that appear with the same name in both tables.
+ * Only natural inner joins are supported by this API,
+ * even though SQL permits natural outer joins as well.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Zend_Db_Expr $name The table name.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return $this This Zend_Db_Select object.
+ */
+ public function joinNatural($name, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->_join(self::NATURAL_JOIN, $name, null, $cols, $schema);
+ }
+
+ /**
+ * Adds a WHERE condition to the query by AND.
+ *
+ * If a value is passed as the second param, it will be quoted
+ * and replaced into the condition wherever a question-mark
+ * appears. Array values are quoted and comma-separated.
+ *
+ *
+ * // simplest but non-secure
+ * $select->where("id = $id");
+ *
+ * // secure (ID is quoted but matched anyway)
+ * $select->where('id = ?', $id);
+ *
+ * // alternatively, with named binding
+ * $select->where('id = :id');
+ *
+ *
+ * Note that it is more correct to use named bindings in your
+ * queries for values other than strings. When you use named
+ * bindings, don't forget to pass the values when actually
+ * making a query:
+ *
+ *
+ * $db->fetchAll($select, array('id' => 5));
+ *
+ *
+ * @param string $cond The WHERE condition.
+ * @param mixed $value OPTIONAL The value to quote into the condition.
+ * @param int $type OPTIONAL The type of the given value
+ * @return $this This Zend_Db_Select object.
+ */
+ public function where($cond, $value = null, $type = null)
+ {
+ $this->_parts[self::WHERE][] = $this->_where($cond, $value, $type, true);
+
+ return $this;
+ }
+
+ /**
+ * Adds a WHERE condition to the query by OR.
+ *
+ * Otherwise identical to where().
+ *
+ * @param string $cond The WHERE condition.
+ * @param mixed $value OPTIONAL The value to quote into the condition.
+ * @param int $type OPTIONAL The type of the given value
+ * @return $this This Zend_Db_Select object.
+ *
+ * @see where()
+ */
+ public function orWhere($cond, $value = null, $type = null)
+ {
+ $this->_parts[self::WHERE][] = $this->_where($cond, $value, $type, false);
+
+ return $this;
+ }
+
+ /**
+ * Adds grouping to the query.
+ *
+ * @param array|string $spec The column(s) to group by.
+ * @return $this This Zend_Db_Select object.
+ */
+ public function group($spec)
+ {
+ if (!is_array($spec)) {
+ $spec = [$spec];
+ }
+
+ foreach ($spec as $val) {
+ // Remove comments from SQL statement
+ $noComments = preg_replace(self::REGEX_SQL_COMMENTS, '$1', (string) $val);
+ if (preg_match(self::REGEX_COLUMN_EXPR_GROUP, $noComments)) {
+ $val = new Zend_Db_Expr($val);
+ }
+ $this->_parts[self::GROUP][] = $val;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds a HAVING condition to the query by AND.
+ *
+ * If a value is passed as the second param, it will be quoted
+ * and replaced into the condition wherever a question-mark
+ * appears. See {@link where()} for an example
+ *
+ * @param string $cond The HAVING condition.
+ * @param mixed $value OPTIONAL The value to quote into the condition.
+ * @param int $type OPTIONAL The type of the given value
+ * @return $this This Zend_Db_Select object.
+ */
+ public function having($cond, $value = null, $type = null)
+ {
+ if ($value !== null) {
+ $cond = $this->_adapter->quoteInto($cond, $value, $type);
+ }
+
+ if ($this->_parts[self::HAVING]) {
+ $this->_parts[self::HAVING][] = self::SQL_AND . " ($cond)";
+ } else {
+ $this->_parts[self::HAVING][] = "($cond)";
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds a HAVING condition to the query by OR.
+ *
+ * Otherwise identical to orHaving().
+ *
+ * @param string $cond The HAVING condition.
+ * @param mixed $value OPTIONAL The value to quote into the condition.
+ * @param int $type OPTIONAL The type of the given value
+ * @return $this This Zend_Db_Select object.
+ *
+ * @see having()
+ */
+ public function orHaving($cond, $value = null, $type = null)
+ {
+ if ($value !== null) {
+ $cond = $this->_adapter->quoteInto($cond, $value, $type);
+ }
+
+ if ($this->_parts[self::HAVING]) {
+ $this->_parts[self::HAVING][] = self::SQL_OR . " ($cond)";
+ } else {
+ $this->_parts[self::HAVING][] = "($cond)";
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds a row order to the query.
+ *
+ * @param mixed $spec The column(s) and direction to order by.
+ * @return $this This Zend_Db_Select object.
+ */
+ public function order($spec)
+ {
+ if (!is_array($spec)) {
+ $spec = [$spec];
+ }
+
+ // force 'ASC' or 'DESC' on each order spec, default is ASC.
+ foreach ($spec as $val) {
+ if ($val instanceof Zend_Db_Expr) {
+ $expr = $val->__toString();
+ if (empty($expr)) {
+ continue;
+ }
+ $this->_parts[self::ORDER][] = $val;
+ } else {
+ if (empty($val)) {
+ continue;
+ }
+ $direction = self::SQL_ASC;
+ if (preg_match('/(.*\W)(' . self::SQL_ASC . '|' . self::SQL_DESC . ')\b/si', $val, $matches)) {
+ $val = trim($matches[1]);
+ $direction = $matches[2];
+ }
+ // Remove comments from SQL statement
+ $noComments = preg_replace(self::REGEX_SQL_COMMENTS, '$1', (string) $val);
+ if (preg_match(self::REGEX_COLUMN_EXPR_ORDER, $noComments)) {
+ $val = new Zend_Db_Expr($val);
+ }
+ $this->_parts[self::ORDER][] = [$val, $direction];
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets a limit count and offset to the query.
+ *
+ * @param int $count OPTIONAL The number of rows to return.
+ * @param int $offset OPTIONAL Start returning after this many rows.
+ * @return $this This Zend_Db_Select object.
+ */
+ public function limit($count = null, $offset = null)
+ {
+ $this->_parts[self::LIMIT_COUNT] = (int) $count;
+ $this->_parts[self::LIMIT_OFFSET] = (int) $offset;
+ return $this;
+ }
+
+ /**
+ * Sets the limit and count by page number.
+ *
+ * @param int $page Limit results to this page number.
+ * @param int $rowCount Use this many rows per page.
+ * @return $this This Zend_Db_Select object.
+ */
+ public function limitPage($page, $rowCount)
+ {
+ $page = ($page > 0) ? $page : 1;
+ $rowCount = ($rowCount > 0) ? $rowCount : 1;
+ $this->_parts[self::LIMIT_COUNT] = (int) $rowCount;
+ $this->_parts[self::LIMIT_OFFSET] = (int) $rowCount * ($page - 1);
+ return $this;
+ }
+
+ /**
+ * Makes the query SELECT FOR UPDATE, optionally with NOWAIT or SKIP LOCKED options
+ *
+ * @param mixed $flag Whether or not the SELECT is FOR UPDATE (default true),
+ pass the flag FU_MODE_NOWAIT or FU_MODE_SKIP to make
+ the FOR UPDATE either NOWAIT or SKIP LOCKED
+ * @return $this This Zend_Db_Select object.
+ */
+ public function forUpdate($flag = true)
+ {
+ $this->_parts[self::FOR_UPDATE] = (bool) $flag; //still use bool here for backwards compatibility
+ if ($flag === self::FU_MODE_NOWAIT || $flag === self::FU_MODE_SKIP) {
+ $this->_parts[self::FOR_UPDATE_MODE] = $flag;
+ }
+ return $this;
+ }
+
+ /**
+ * Get part of the structured information for the current query.
+ *
+ * @param string $part
+ * @return mixed
+ * @throws Zend_Db_Select_Exception
+ */
+ public function getPart($part)
+ {
+ $part = strtolower($part);
+ if (!array_key_exists($part, $this->_parts)) {
+ require_once 'Zend/Db/Select/Exception.php';
+ throw new Zend_Db_Select_Exception("Invalid Select part '$part'");
+ }
+ return $this->_parts[$part];
+ }
+
+ /**
+ * Executes the current select object and returns the result
+ *
+ * @param integer $fetchMode OPTIONAL
+ * @param mixed $bind An array of data to bind to the placeholders.
+ * @return Zend_Db_Statement
+ */
+ public function query($fetchMode = null, $bind = [])
+ {
+ if (!empty($bind)) {
+ $this->bind($bind);
+ }
+
+ $stmt = $this->_adapter->query($this);
+ if ($fetchMode == null) {
+ $fetchMode = $this->_adapter->getFetchMode();
+ }
+ $stmt->setFetchMode($fetchMode);
+ return $stmt;
+ }
+
+ /**
+ * Converts this object to an SQL SELECT string.
+ *
+ * @return string|null This object as a SELECT string. (or null if a string cannot be produced.)
+ */
+ public function assemble()
+ {
+ $sql = self::SQL_SELECT;
+ foreach (array_keys(self::$_partsInit) as $part) {
+ $method = '_render' . ucfirst($part);
+ if (method_exists($this, $method)) {
+ $sql = $this->$method($sql);
+ }
+ }
+ return $sql;
+ }
+
+ /**
+ * Clear parts of the Select object, or an individual part.
+ *
+ * @param string $part OPTIONAL
+ * @return $this
+ */
+ public function reset($part = null)
+ {
+ if ($part == null) {
+ $this->_parts = self::$_partsInit;
+ } elseif (array_key_exists($part, self::$_partsInit)) {
+ $this->_parts[$part] = self::$_partsInit[$part];
+ }
+ return $this;
+ }
+
+ /**
+ * Gets the Zend_Db_Adapter_Abstract for this
+ * particular Zend_Db_Select object.
+ *
+ * @return Zend_Db_Adapter_Abstract
+ */
+ public function getAdapter()
+ {
+ return $this->_adapter;
+ }
+
+ /**
+ * Populate the {@link $_parts} 'join' key
+ *
+ * Does the dirty work of populating the join key.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param null|string $type Type of join; inner, left, and null are currently supported
+ * @param array|string|Zend_Db_Expr $name Table name
+ * @param string $cond Join on this condition
+ * @param array|string $cols The columns to select from the joined table
+ * @param string $schema The database name to specify, if any.
+ * @return $this This Zend_Db_Select object
+ * @throws Zend_Db_Select_Exception
+ */
+ protected function _join($type, $name, $cond, $cols, $schema = null)
+ {
+ if (!in_array($type, self::$_joinTypes) && $type != self::FROM) {
+ /**
+ * @see Zend_Db_Select_Exception
+ */
+ require_once 'Zend/Db/Select/Exception.php';
+ throw new Zend_Db_Select_Exception("Invalid join type '$type'");
+ }
+
+ if (count($this->_parts[self::UNION])) {
+ require_once 'Zend/Db/Select/Exception.php';
+ throw new Zend_Db_Select_Exception("Invalid use of table with " . self::SQL_UNION);
+ }
+
+ if (empty($name)) {
+ $correlationName = $tableName = '';
+ } elseif (is_array($name)) {
+ // Must be array($correlationName => $tableName) or array($ident, ...)
+ foreach ($name as $_correlationName => $_tableName) {
+ if (is_string($_correlationName)) {
+ // We assume the key is the correlation name and value is the table name
+ $tableName = $_tableName;
+ $correlationName = $_correlationName;
+ } else {
+ // We assume just an array of identifiers, with no correlation name
+ $tableName = $_tableName;
+ $correlationName = $this->_uniqueCorrelation($tableName);
+ }
+ break;
+ }
+ } elseif ($name instanceof Zend_Db_Expr|| $name instanceof Zend_Db_Select) {
+ $tableName = $name;
+ $correlationName = $this->_uniqueCorrelation('t');
+ } elseif (preg_match('/^(.+)\s+AS\s+(.+)$/i', $name, $m)) {
+ $tableName = $m[1];
+ $correlationName = $m[2];
+ } else {
+ $tableName = $name;
+ $correlationName = $this->_uniqueCorrelation($tableName);
+ }
+
+ // Schema from table name overrides schema argument
+ if (!is_object($tableName) && false !== strpos($tableName, '.')) {
+ list($schema, $tableName) = explode('.', $tableName);
+ }
+
+ $lastFromCorrelationName = null;
+ if (!empty($correlationName)) {
+ if (array_key_exists($correlationName, $this->_parts[self::FROM])) {
+ /**
+ * @see Zend_Db_Select_Exception
+ */
+ require_once 'Zend/Db/Select/Exception.php';
+ throw new Zend_Db_Select_Exception("You cannot define a correlation name '$correlationName' more than once");
+ }
+
+ if ($type == self::FROM) {
+ // append this from after the last from joinType
+ $tmpFromParts = $this->_parts[self::FROM];
+ $this->_parts[self::FROM] = [];
+ // move all the froms onto the stack
+ while ($tmpFromParts) {
+ $currentCorrelationName = key($tmpFromParts);
+ if ($tmpFromParts[$currentCorrelationName]['joinType'] != self::FROM) {
+ break;
+ }
+ $lastFromCorrelationName = $currentCorrelationName;
+ $this->_parts[self::FROM][$currentCorrelationName] = array_shift($tmpFromParts);
+ }
+ } else {
+ $tmpFromParts = [];
+ }
+ $this->_parts[self::FROM][$correlationName] = [
+ 'joinType' => $type,
+ 'schema' => $schema,
+ 'tableName' => $tableName,
+ 'joinCondition' => $cond
+ ];
+ while ($tmpFromParts) {
+ $currentCorrelationName = key($tmpFromParts);
+ $this->_parts[self::FROM][$currentCorrelationName] = array_shift($tmpFromParts);
+ }
+ }
+
+ // add to the columns from this joined table
+ if ($type == self::FROM && $lastFromCorrelationName == null) {
+ $lastFromCorrelationName = true;
+ }
+ $this->_tableCols($correlationName, $cols, $lastFromCorrelationName);
+
+ return $this;
+ }
+
+ /**
+ * Handle JOIN... USING... syntax
+ *
+ * This is functionality identical to the existing JOIN methods, however
+ * the join condition can be passed as a single column name. This method
+ * then completes the ON condition by using the same field for the FROM
+ * table and the JOIN table.
+ *
+ *
+ * $select = $db->select()->from('table1')
+ * ->joinUsing('table2', 'column1');
+ *
+ * // SELECT * FROM table1 JOIN table2 ON table1.column1 = table2.column2
+ *
+ *
+ * These joins are called by the developer simply by adding 'Using' to the
+ * method name. E.g.
+ * * joinUsing
+ * * joinInnerUsing
+ * * joinFullUsing
+ * * joinRightUsing
+ * * joinLeftUsing
+ *
+ * @return $this This Zend_Db_Select object.
+ */
+ public function _joinUsing($type, $name, $cond, $cols = '*', $schema = null)
+ {
+ if (empty($this->_parts[self::FROM])) {
+ require_once 'Zend/Db/Select/Exception.php';
+ throw new Zend_Db_Select_Exception("You can only perform a joinUsing after specifying a FROM table");
+ }
+
+ $join = $this->_adapter->quoteIdentifier(key($this->_parts[self::FROM]), true);
+ $from = $this->_adapter->quoteIdentifier($this->_uniqueCorrelation($name), true);
+
+ $joinCond = [];
+ foreach ((array)$cond as $fieldName) {
+ $cond1 = $from . '.' . $fieldName;
+ $cond2 = $join . '.' . $fieldName;
+ $joinCond[] = $cond1 . ' = ' . $cond2;
+ }
+ $cond = implode(' '.self::SQL_AND.' ', $joinCond);
+
+ return $this->_join($type, $name, $cond, $cols, $schema);
+ }
+
+ /**
+ * Generate a unique correlation name
+ *
+ * @param string|array $name A qualified identifier.
+ * @return string A unique correlation name.
+ */
+ private function _uniqueCorrelation($name)
+ {
+ if (is_array($name)) {
+ $k = key($name);
+ $c = is_string($k) ? $k : end($name);
+ } else {
+ // Extract just the last name of a qualified table name
+ $dot = strrpos($name,'.');
+ $c = ($dot === false) ? $name : substr($name, $dot+1);
+ }
+ for ($i = 2; array_key_exists($c, $this->_parts[self::FROM]); ++$i) {
+ $c = $name . '_' . (string) $i;
+ }
+ return $c;
+ }
+
+ /**
+ * Adds to the internal table-to-column mapping array.
+ *
+ * @param string $tbl The table/join the columns come from.
+ * @param array|string $cols The list of columns; preferably as
+ * an array, but possibly as a string containing one column.
+ * @param bool|string True if it should be prepended, a correlation name if it should be inserted
+ * @return void
+ */
+ protected function _tableCols($correlationName, $cols, $afterCorrelationName = null)
+ {
+ if (!is_array($cols)) {
+ $cols = [$cols];
+ }
+
+ if ($correlationName == null) {
+ $correlationName = '';
+ }
+
+ $columnValues = [];
+
+ foreach (array_filter($cols) as $alias => $col) {
+ $currentCorrelationName = $correlationName;
+ if (is_string($col)) {
+ // Check for a column matching "