diff --git a/application/Bootstrap.php b/application/Bootstrap.php new file mode 100644 index 0000000..49fffab --- /dev/null +++ b/application/Bootstrap.php @@ -0,0 +1,102 @@ +getOptions()); + Zend_Registry::set('config', $config); + + //Load old config + require_once 'Scores/Configure.php'; + $oldconfig = new Configure(); + + return $config; + } + + //Initialisation global des paramètres de vue + protected function _initViewSettings() + { + $this->bootstrap('view'); + + $view = $this->getResource('view'); + $view->setEncoding('UTF-8'); + $view->doctype('HTML5'); + + $view->headMeta() + ->appendHttpEquiv('viewport', 'width=device-width, initial-scale=1.0') + ->appendHttpEquiv('Content-Type', 'text/html; charset=UTF-8') + ->appendHttpEquiv('Content-Language', 'fr-FR'); + + $view->headLink() + ->appendStylesheet('/libs/bootstrap-v3.0.0/css/bootstrap.min.css', 'all') + ->appendStylesheet('/themes/default/css/main.css', 'all'); + + $view->headScript() + ->appendFile('/libs/html5shiv.js', 'text/javascript', array('conditional' => 'lt IE 9')) + ->appendFile('/libs/respond.min.js', 'text/javascript', array('conditional' => 'lt IE 9')); + + $view->inlineScript() + ->appendFile('/libs/jquery-2.0.3.min.js', 'text/javascript') + ->appendFile('/libs/bootstrap-v3.0.0/js/bootstrap.min.js', 'text/javascript'); + + $view->headTitle()->setSeparator(' - '); + $view->headTitle('Backoffice'); + } + + protected function _initLogging() + { + //Firebug + $writer = new Zend_Log_Writer_Firebug(); + if(APPLICATION_ENV=='production') { + $writer->setEnabled(false); + } + $logger = new Zend_Log($writer); + Zend_Registry::set('firebug', $logger); + } + + protected function _initDb() + { + $c = new Zend_Config($this->getOptions()); + try { + $db = Zend_Db::factory($c->resources->db); + $db->getConnection(); + } catch ( Exception $e ) { + if (APPLICATION_ENV == 'development') { + echo '
'; print_r($e); echo ''; + } else { + echo "Le service rencontre actuellement un problème technique."; + } + exit; + } + + /** + * Set the default adapter to use with all model + */ + Zend_Db_Table::setDefaultAdapter($db); + + /** + * Set Firebug Database profiler + */ + if (APPLICATION_ENV == 'development') { + $profiler = new Zend_Db_Profiler_Firebug('All DB Queries'); + $profiler->setEnabled(true); + $db->setProfiler($profiler); + } + + return $db; + } + + protected function _initCache() + { + if ( APPLICATION_ENV!='development' ) { + //MetadataCache pour la base de données + $frontendOptions = array( + 'lifetime' => 3600, + 'automatic_serialization' => true + ); + $backendOptions = array(); + $cache = Zend_Cache::factory('Core','Apc', $frontendOptions, $backendOptions); + Zend_Db_Table_Abstract::setDefaultMetadataCache($cache); + } + } +} \ No newline at end of file diff --git a/library/Zend/Acl.php b/library/Zend/Acl.php new file mode 100644 index 0000000..cc6de6b --- /dev/null +++ b/library/Zend/Acl.php @@ -0,0 +1,1242 @@ + array( + 'allRoles' => array( + 'allPrivileges' => array( + 'type' => self::TYPE_DENY, + 'assert' => null + ), + 'byPrivilegeId' => array() + ), + 'byRoleId' => array() + ), + 'byResourceId' => array() + ); + + /** + * Adds a Role having an identifier unique to the registry + * + * The $parents parameter may be a reference to, or the string identifier for, + * a Role existing in the registry, or $parents may be passed as an array of + * these - mixing string identifiers and objects is ok - to indicate the Roles + * from which the newly added Role will directly inherit. + * + * In order to resolve potential ambiguities with conflicting rules inherited + * from different parents, the most recently added parent takes precedence over + * parents that were previously added. In other words, the first parent added + * will have the least priority, and the last parent added will have the + * highest priority. + * + * @param Zend_Acl_Role_Interface|string $role + * @param Zend_Acl_Role_Interface|string|array $parents + * @uses Zend_Acl_Role_Registry::add() + * @return Zend_Acl Provides a fluent interface + */ + public function addRole($role, $parents = null) + { + if (is_string($role)) { + $role = new Zend_Acl_Role($role); + } + + if (!$role instanceof Zend_Acl_Role_Interface) { + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception('addRole() expects $role to be of type Zend_Acl_Role_Interface'); + } + + + $this->_getRoleRegistry()->add($role, $parents); + + return $this; + } + + /** + * Returns the identified Role + * + * The $role parameter can either be a Role or Role identifier. + * + * @param Zend_Acl_Role_Interface|string $role + * @uses Zend_Acl_Role_Registry::get() + * @return Zend_Acl_Role_Interface + */ + public function getRole($role) + { + return $this->_getRoleRegistry()->get($role); + } + + /** + * Returns true if and only if the Role exists in the registry + * + * The $role parameter can either be a Role or a Role identifier. + * + * @param Zend_Acl_Role_Interface|string $role + * @uses Zend_Acl_Role_Registry::has() + * @return boolean + */ + public function hasRole($role) + { + return $this->_getRoleRegistry()->has($role); + } + + /** + * Returns true if and only if $role inherits from $inherit + * + * Both parameters may be either a Role or a Role identifier. If + * $onlyParents is true, then $role must inherit directly from + * $inherit in order to return true. By default, this method looks + * through the entire inheritance DAG to determine whether $role + * inherits from $inherit through its ancestor Roles. + * + * @param Zend_Acl_Role_Interface|string $role + * @param Zend_Acl_Role_Interface|string $inherit + * @param boolean $onlyParents + * @uses Zend_Acl_Role_Registry::inherits() + * @return boolean + */ + public function inheritsRole($role, $inherit, $onlyParents = false) + { + return $this->_getRoleRegistry()->inherits($role, $inherit, $onlyParents); + } + + /** + * Removes the Role from the registry + * + * The $role parameter can either be a Role or a Role identifier. + * + * @param Zend_Acl_Role_Interface|string $role + * @uses Zend_Acl_Role_Registry::remove() + * @return Zend_Acl Provides a fluent interface + */ + public function removeRole($role) + { + $this->_getRoleRegistry()->remove($role); + + if ($role instanceof Zend_Acl_Role_Interface) { + $roleId = $role->getRoleId(); + } else { + $roleId = $role; + } + + foreach ($this->_rules['allResources']['byRoleId'] as $roleIdCurrent => $rules) { + if ($roleId === $roleIdCurrent) { + unset($this->_rules['allResources']['byRoleId'][$roleIdCurrent]); + } + } + foreach ($this->_rules['byResourceId'] as $resourceIdCurrent => $visitor) { + if (array_key_exists('byRoleId', $visitor)) { + foreach ($visitor['byRoleId'] as $roleIdCurrent => $rules) { + if ($roleId === $roleIdCurrent) { + unset($this->_rules['byResourceId'][$resourceIdCurrent]['byRoleId'][$roleIdCurrent]); + } + } + } + } + + return $this; + } + + /** + * Removes all Roles from the registry + * + * @uses Zend_Acl_Role_Registry::removeAll() + * @return Zend_Acl Provides a fluent interface + */ + public function removeRoleAll() + { + $this->_getRoleRegistry()->removeAll(); + + foreach ($this->_rules['allResources']['byRoleId'] as $roleIdCurrent => $rules) { + unset($this->_rules['allResources']['byRoleId'][$roleIdCurrent]); + } + foreach ($this->_rules['byResourceId'] as $resourceIdCurrent => $visitor) { + foreach ($visitor['byRoleId'] as $roleIdCurrent => $rules) { + unset($this->_rules['byResourceId'][$resourceIdCurrent]['byRoleId'][$roleIdCurrent]); + } + } + + return $this; + } + + /** + * Adds a Resource having an identifier unique to the ACL + * + * The $parent parameter may be a reference to, or the string identifier for, + * the existing Resource from which the newly added Resource will inherit. + * + * @param Zend_Acl_Resource_Interface|string $resource + * @param Zend_Acl_Resource_Interface|string $parent + * @throws Zend_Acl_Exception + * @return Zend_Acl Provides a fluent interface + */ + public function addResource($resource, $parent = null) + { + if (is_string($resource)) { + $resource = new Zend_Acl_Resource($resource); + } + + if (!$resource instanceof Zend_Acl_Resource_Interface) { + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception('addResource() expects $resource to be of type Zend_Acl_Resource_Interface'); + } + + $resourceId = $resource->getResourceId(); + + if ($this->has($resourceId)) { + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception("Resource id '$resourceId' already exists in the ACL"); + } + + $resourceParent = null; + + if (null !== $parent) { + try { + if ($parent instanceof Zend_Acl_Resource_Interface) { + $resourceParentId = $parent->getResourceId(); + } else { + $resourceParentId = $parent; + } + $resourceParent = $this->get($resourceParentId); + } catch (Zend_Acl_Exception $e) { + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception("Parent Resource id '$resourceParentId' does not exist", 0, $e); + } + $this->_resources[$resourceParentId]['children'][$resourceId] = $resource; + } + + $this->_resources[$resourceId] = array( + 'instance' => $resource, + 'parent' => $resourceParent, + 'children' => array() + ); + + return $this; + } + + /** + * Adds a Resource having an identifier unique to the ACL + * + * The $parent parameter may be a reference to, or the string identifier for, + * the existing Resource from which the newly added Resource will inherit. + * + * @deprecated in version 1.9.1 and will be available till 2.0. New code + * should use addResource() instead. + * + * @param Zend_Acl_Resource_Interface $resource + * @param Zend_Acl_Resource_Interface|string $parent + * @throws Zend_Acl_Exception + * @return Zend_Acl Provides a fluent interface + */ + public function add(Zend_Acl_Resource_Interface $resource, $parent = null) + { + return $this->addResource($resource, $parent); + } + + /** + * Returns the identified Resource + * + * The $resource parameter can either be a Resource or a Resource identifier. + * + * @param Zend_Acl_Resource_Interface|string $resource + * @throws Zend_Acl_Exception + * @return Zend_Acl_Resource_Interface + */ + public function get($resource) + { + if ($resource instanceof Zend_Acl_Resource_Interface) { + $resourceId = $resource->getResourceId(); + } else { + $resourceId = (string) $resource; + } + + if (!$this->has($resource)) { + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception("Resource '$resourceId' not found"); + } + + return $this->_resources[$resourceId]['instance']; + } + + /** + * Returns true if and only if the Resource exists in the ACL + * + * The $resource parameter can either be a Resource or a Resource identifier. + * + * @param Zend_Acl_Resource_Interface|string $resource + * @return boolean + */ + public function has($resource) + { + if ($resource instanceof Zend_Acl_Resource_Interface) { + $resourceId = $resource->getResourceId(); + } else { + $resourceId = (string) $resource; + } + + return isset($this->_resources[$resourceId]); + } + + /** + * Returns true if and only if $resource inherits from $inherit + * + * Both parameters may be either a Resource or a Resource identifier. If + * $onlyParent is true, then $resource must inherit directly from + * $inherit in order to return true. By default, this method looks + * through the entire inheritance tree to determine whether $resource + * inherits from $inherit through its ancestor Resources. + * + * @param Zend_Acl_Resource_Interface|string $resource + * @param Zend_Acl_Resource_Interface|string $inherit + * @param boolean $onlyParent + * @throws Zend_Acl_Resource_Registry_Exception + * @return boolean + */ + public function inherits($resource, $inherit, $onlyParent = false) + { + try { + $resourceId = $this->get($resource)->getResourceId(); + $inheritId = $this->get($inherit)->getResourceId(); + } catch (Zend_Acl_Exception $e) { + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception($e->getMessage(), $e->getCode(), $e); + } + + if (null !== $this->_resources[$resourceId]['parent']) { + $parentId = $this->_resources[$resourceId]['parent']->getResourceId(); + if ($inheritId === $parentId) { + return true; + } else if ($onlyParent) { + return false; + } + } else { + return false; + } + + while (null !== $this->_resources[$parentId]['parent']) { + $parentId = $this->_resources[$parentId]['parent']->getResourceId(); + if ($inheritId === $parentId) { + return true; + } + } + + return false; + } + + /** + * Removes a Resource and all of its children + * + * The $resource parameter can either be a Resource or a Resource identifier. + * + * @param Zend_Acl_Resource_Interface|string $resource + * @throws Zend_Acl_Exception + * @return Zend_Acl Provides a fluent interface + */ + public function remove($resource) + { + try { + $resourceId = $this->get($resource)->getResourceId(); + } catch (Zend_Acl_Exception $e) { + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception($e->getMessage(), $e->getCode(), $e); + } + + $resourcesRemoved = array($resourceId); + if (null !== ($resourceParent = $this->_resources[$resourceId]['parent'])) { + unset($this->_resources[$resourceParent->getResourceId()]['children'][$resourceId]); + } + foreach ($this->_resources[$resourceId]['children'] as $childId => $child) { + $this->remove($childId); + $resourcesRemoved[] = $childId; + } + + foreach ($resourcesRemoved as $resourceIdRemoved) { + foreach ($this->_rules['byResourceId'] as $resourceIdCurrent => $rules) { + if ($resourceIdRemoved === $resourceIdCurrent) { + unset($this->_rules['byResourceId'][$resourceIdCurrent]); + } + } + } + + unset($this->_resources[$resourceId]); + + return $this; + } + + /** + * Removes all Resources + * + * @return Zend_Acl Provides a fluent interface + */ + public function removeAll() + { + foreach ($this->_resources as $resourceId => $resource) { + foreach ($this->_rules['byResourceId'] as $resourceIdCurrent => $rules) { + if ($resourceId === $resourceIdCurrent) { + unset($this->_rules['byResourceId'][$resourceIdCurrent]); + } + } + } + + $this->_resources = array(); + + return $this; + } + + /** + * Adds an "allow" rule to the ACL + * + * @param Zend_Acl_Role_Interface|string|array $roles + * @param Zend_Acl_Resource_Interface|string|array $resources + * @param string|array $privileges + * @param Zend_Acl_Assert_Interface $assert + * @uses Zend_Acl::setRule() + * @return Zend_Acl Provides a fluent interface + */ + public function allow($roles = null, $resources = null, $privileges = null, Zend_Acl_Assert_Interface $assert = null) + { + return $this->setRule(self::OP_ADD, self::TYPE_ALLOW, $roles, $resources, $privileges, $assert); + } + + /** + * Adds a "deny" rule to the ACL + * + * @param Zend_Acl_Role_Interface|string|array $roles + * @param Zend_Acl_Resource_Interface|string|array $resources + * @param string|array $privileges + * @param Zend_Acl_Assert_Interface $assert + * @uses Zend_Acl::setRule() + * @return Zend_Acl Provides a fluent interface + */ + public function deny($roles = null, $resources = null, $privileges = null, Zend_Acl_Assert_Interface $assert = null) + { + return $this->setRule(self::OP_ADD, self::TYPE_DENY, $roles, $resources, $privileges, $assert); + } + + /** + * Removes "allow" permissions from the ACL + * + * @param Zend_Acl_Role_Interface|string|array $roles + * @param Zend_Acl_Resource_Interface|string|array $resources + * @param string|array $privileges + * @uses Zend_Acl::setRule() + * @return Zend_Acl Provides a fluent interface + */ + public function removeAllow($roles = null, $resources = null, $privileges = null) + { + return $this->setRule(self::OP_REMOVE, self::TYPE_ALLOW, $roles, $resources, $privileges); + } + + /** + * Removes "deny" restrictions from the ACL + * + * @param Zend_Acl_Role_Interface|string|array $roles + * @param Zend_Acl_Resource_Interface|string|array $resources + * @param string|array $privileges + * @uses Zend_Acl::setRule() + * @return Zend_Acl Provides a fluent interface + */ + public function removeDeny($roles = null, $resources = null, $privileges = null) + { + return $this->setRule(self::OP_REMOVE, self::TYPE_DENY, $roles, $resources, $privileges); + } + + /** + * Performs operations on ACL rules + * + * The $operation parameter may be either OP_ADD or OP_REMOVE, depending on whether the + * user wants to add or remove a rule, respectively: + * + * OP_ADD specifics: + * + * A rule is added that would allow one or more Roles access to [certain $privileges + * upon] the specified Resource(s). + * + * OP_REMOVE specifics: + * + * The rule is removed only in the context of the given Roles, Resources, and privileges. + * Existing rules to which the remove operation does not apply would remain in the + * ACL. + * + * The $type parameter may be either TYPE_ALLOW or TYPE_DENY, depending on whether the + * rule is intended to allow or deny permission, respectively. + * + * The $roles and $resources parameters may be references to, or the string identifiers for, + * existing Resources/Roles, or they may be passed as arrays of these - mixing string identifiers + * and objects is ok - to indicate the Resources and Roles to which the rule applies. If either + * $roles or $resources is null, then the rule applies to all Roles or all Resources, respectively. + * Both may be null in order to work with the default rule of the ACL. + * + * The $privileges parameter may be used to further specify that the rule applies only + * to certain privileges upon the Resource(s) in question. This may be specified to be a single + * privilege with a string, and multiple privileges may be specified as an array of strings. + * + * If $assert is provided, then its assert() method must return true in order for + * the rule to apply. If $assert is provided with $roles, $resources, and $privileges all + * equal to null, then a rule having a type of: + * + * TYPE_ALLOW will imply a type of TYPE_DENY, and + * + * TYPE_DENY will imply a type of TYPE_ALLOW + * + * when the rule's assertion fails. This is because the ACL needs to provide expected + * behavior when an assertion upon the default ACL rule fails. + * + * @param string $operation + * @param string $type + * @param Zend_Acl_Role_Interface|string|array $roles + * @param Zend_Acl_Resource_Interface|string|array $resources + * @param string|array $privileges + * @param Zend_Acl_Assert_Interface $assert + * @throws Zend_Acl_Exception + * @uses Zend_Acl_Role_Registry::get() + * @uses Zend_Acl::get() + * @return Zend_Acl Provides a fluent interface + */ + public function setRule($operation, $type, $roles = null, $resources = null, $privileges = null, + Zend_Acl_Assert_Interface $assert = null) + { + // ensure that the rule type is valid; normalize input to uppercase + $type = strtoupper($type); + if (self::TYPE_ALLOW !== $type && self::TYPE_DENY !== $type) { + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception("Unsupported rule type; must be either '" . self::TYPE_ALLOW . "' or '" + . self::TYPE_DENY . "'"); + } + + // ensure that all specified Roles exist; normalize input to array of Role objects or null + if (!is_array($roles)) { + $roles = array($roles); + } else if (0 === count($roles)) { + $roles = array(null); + } + $rolesTemp = $roles; + $roles = array(); + foreach ($rolesTemp as $role) { + if (null !== $role) { + $roles[] = $this->_getRoleRegistry()->get($role); + } else { + $roles[] = null; + } + } + unset($rolesTemp); + + // ensure that all specified Resources exist; normalize input to array of Resource objects or null + if ($resources !== null) { + if (!is_array($resources)) { + $resources = array($resources); + } else if (0 === count($resources)) { + $resources = array(null); + } + $resourcesTemp = $resources; + $resources = array(); + foreach ($resourcesTemp as $resource) { + if (null !== $resource) { + $resources[] = $this->get($resource); + } else { + $resources[] = null; + } + } + unset($resourcesTemp, $resource); + } else { + $allResources = array(); // this might be used later if resource iteration is required + foreach ($this->_resources as $rTarget) { + $allResources[] = $rTarget['instance']; + } + unset($rTarget); + } + + // normalize privileges to array + if (null === $privileges) { + $privileges = array(); + } else if (!is_array($privileges)) { + $privileges = array($privileges); + } + + switch ($operation) { + + // add to the rules + case self::OP_ADD: + if ($resources !== null) { + // this block will iterate the provided resources + foreach ($resources as $resource) { + foreach ($roles as $role) { + $rules =& $this->_getRules($resource, $role, true); + if (0 === count($privileges)) { + $rules['allPrivileges']['type'] = $type; + $rules['allPrivileges']['assert'] = $assert; + if (!isset($rules['byPrivilegeId'])) { + $rules['byPrivilegeId'] = array(); + } + } else { + foreach ($privileges as $privilege) { + $rules['byPrivilegeId'][$privilege]['type'] = $type; + $rules['byPrivilegeId'][$privilege]['assert'] = $assert; + } + } + } + } + } else { + // this block will apply to all resources in a global rule + foreach ($roles as $role) { + $rules =& $this->_getRules(null, $role, true); + if (0 === count($privileges)) { + $rules['allPrivileges']['type'] = $type; + $rules['allPrivileges']['assert'] = $assert; + } else { + foreach ($privileges as $privilege) { + $rules['byPrivilegeId'][$privilege]['type'] = $type; + $rules['byPrivilegeId'][$privilege]['assert'] = $assert; + } + } + } + } + break; + + // remove from the rules + case self::OP_REMOVE: + if ($resources !== null) { + // this block will iterate the provided resources + foreach ($resources as $resource) { + foreach ($roles as $role) { + $rules =& $this->_getRules($resource, $role); + if (null === $rules) { + continue; + } + if (0 === count($privileges)) { + if (null === $resource && null === $role) { + if ($type === $rules['allPrivileges']['type']) { + $rules = array( + 'allPrivileges' => array( + 'type' => self::TYPE_DENY, + 'assert' => null + ), + 'byPrivilegeId' => array() + ); + } + continue; + } + + if (isset($rules['allPrivileges']['type']) && + $type === $rules['allPrivileges']['type']) + { + unset($rules['allPrivileges']); + } + } else { + foreach ($privileges as $privilege) { + if (isset($rules['byPrivilegeId'][$privilege]) && + $type === $rules['byPrivilegeId'][$privilege]['type']) + { + unset($rules['byPrivilegeId'][$privilege]); + } + } + } + } + } + } else { + // this block will apply to all resources in a global rule + foreach ($roles as $role) { + /** + * since null (all resources) was passed to this setRule() call, we need + * clean up all the rules for the global allResources, as well as the indivually + * set resources (per privilege as well) + */ + foreach (array_merge(array(null), $allResources) as $resource) { + $rules =& $this->_getRules($resource, $role, true); + if (null === $rules) { + continue; + } + if (0 === count($privileges)) { + if (null === $role) { + if ($type === $rules['allPrivileges']['type']) { + $rules = array( + 'allPrivileges' => array( + 'type' => self::TYPE_DENY, + 'assert' => null + ), + 'byPrivilegeId' => array() + ); + } + continue; + } + + if (isset($rules['allPrivileges']['type']) && $type === $rules['allPrivileges']['type']) { + unset($rules['allPrivileges']); + } + } else { + foreach ($privileges as $privilege) { + if (isset($rules['byPrivilegeId'][$privilege]) && + $type === $rules['byPrivilegeId'][$privilege]['type']) + { + unset($rules['byPrivilegeId'][$privilege]); + } + } + } + } + } + } + break; + + default: + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception("Unsupported operation; must be either '" . self::OP_ADD . "' or '" + . self::OP_REMOVE . "'"); + } + + return $this; + } + + /** + * Returns true if and only if the Role has access to the Resource + * + * The $role and $resource parameters may be references to, or the string identifiers for, + * an existing Resource and Role combination. + * + * If either $role or $resource is null, then the query applies to all Roles or all Resources, + * respectively. Both may be null to query whether the ACL has a "blacklist" rule + * (allow everything to all). By default, Zend_Acl creates a "whitelist" rule (deny + * everything to all), and this method would return false unless this default has + * been overridden (i.e., by executing $acl->allow()). + * + * If a $privilege is not provided, then this method returns false if and only if the + * Role is denied access to at least one privilege upon the Resource. In other words, this + * method returns true if and only if the Role is allowed all privileges on the Resource. + * + * This method checks Role inheritance using a depth-first traversal of the Role registry. + * The highest priority parent (i.e., the parent most recently added) is checked first, + * and its respective parents are checked similarly before the lower-priority parents of + * the Role are checked. + * + * @param Zend_Acl_Role_Interface|string $role + * @param Zend_Acl_Resource_Interface|string $resource + * @param string $privilege + * @uses Zend_Acl::get() + * @uses Zend_Acl_Role_Registry::get() + * @return boolean + */ + public function isAllowed($role = null, $resource = null, $privilege = null) + { + // reset role & resource to null + $this->_isAllowedRole = null; + $this->_isAllowedResource = null; + $this->_isAllowedPrivilege = null; + + if (null !== $role) { + // keep track of originally called role + $this->_isAllowedRole = $role; + $role = $this->_getRoleRegistry()->get($role); + if (!$this->_isAllowedRole instanceof Zend_Acl_Role_Interface) { + $this->_isAllowedRole = $role; + } + } + + if (null !== $resource) { + // keep track of originally called resource + $this->_isAllowedResource = $resource; + $resource = $this->get($resource); + if (!$this->_isAllowedResource instanceof Zend_Acl_Resource_Interface) { + $this->_isAllowedResource = $resource; + } + } + + if (null === $privilege) { + // query on all privileges + do { + // depth-first search on $role if it is not 'allRoles' pseudo-parent + if (null !== $role && null !== ($result = $this->_roleDFSAllPrivileges($role, $resource, $privilege))) { + return $result; + } + + // look for rule on 'allRoles' psuedo-parent + if (null !== ($rules = $this->_getRules($resource, null))) { + foreach ($rules['byPrivilegeId'] as $privilege => $rule) { + if (self::TYPE_DENY === ($ruleTypeOnePrivilege = $this->_getRuleType($resource, null, $privilege))) { + return false; + } + } + if (null !== ($ruleTypeAllPrivileges = $this->_getRuleType($resource, null, null))) { + return self::TYPE_ALLOW === $ruleTypeAllPrivileges; + } + } + + // try next Resource + $resource = $this->_resources[$resource->getResourceId()]['parent']; + + } while (true); // loop terminates at 'allResources' pseudo-parent + } else { + $this->_isAllowedPrivilege = $privilege; + // query on one privilege + do { + // depth-first search on $role if it is not 'allRoles' pseudo-parent + if (null !== $role && null !== ($result = $this->_roleDFSOnePrivilege($role, $resource, $privilege))) { + return $result; + } + + // look for rule on 'allRoles' pseudo-parent + if (null !== ($ruleType = $this->_getRuleType($resource, null, $privilege))) { + return self::TYPE_ALLOW === $ruleType; + } else if (null !== ($ruleTypeAllPrivileges = $this->_getRuleType($resource, null, null))) { + return self::TYPE_ALLOW === $ruleTypeAllPrivileges; + } + + // try next Resource + $resource = $this->_resources[$resource->getResourceId()]['parent']; + + } while (true); // loop terminates at 'allResources' pseudo-parent + } + } + + /** + * Returns the Role registry for this ACL + * + * If no Role registry has been created yet, a new default Role registry + * is created and returned. + * + * @return Zend_Acl_Role_Registry + */ + protected function _getRoleRegistry() + { + if (null === $this->_roleRegistry) { + $this->_roleRegistry = new Zend_Acl_Role_Registry(); + } + return $this->_roleRegistry; + } + + /** + * Performs a depth-first search of the Role DAG, starting at $role, in order to find a rule + * allowing/denying $role access to all privileges upon $resource + * + * This method returns true if a rule is found and allows access. If a rule exists and denies access, + * then this method returns false. If no applicable rule is found, then this method returns null. + * + * @param Zend_Acl_Role_Interface $role + * @param Zend_Acl_Resource_Interface $resource + * @return boolean|null + */ + protected function _roleDFSAllPrivileges(Zend_Acl_Role_Interface $role, Zend_Acl_Resource_Interface $resource = null) + { + $dfs = array( + 'visited' => array(), + 'stack' => array() + ); + + if (null !== ($result = $this->_roleDFSVisitAllPrivileges($role, $resource, $dfs))) { + return $result; + } + + while (null !== ($role = array_pop($dfs['stack']))) { + if (!isset($dfs['visited'][$role->getRoleId()])) { + if (null !== ($result = $this->_roleDFSVisitAllPrivileges($role, $resource, $dfs))) { + return $result; + } + } + } + + return null; + } + + /** + * Visits an $role in order to look for a rule allowing/denying $role access to all privileges upon $resource + * + * This method returns true if a rule is found and allows access. If a rule exists and denies access, + * then this method returns false. If no applicable rule is found, then this method returns null. + * + * This method is used by the internal depth-first search algorithm and may modify the DFS data structure. + * + * @param Zend_Acl_Role_Interface $role + * @param Zend_Acl_Resource_Interface $resource + * @param array $dfs + * @return boolean|null + * @throws Zend_Acl_Exception + */ + protected function _roleDFSVisitAllPrivileges(Zend_Acl_Role_Interface $role, Zend_Acl_Resource_Interface $resource = null, + &$dfs = null) + { + if (null === $dfs) { + /** + * @see Zend_Acl_Exception + */ + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception('$dfs parameter may not be null'); + } + + if (null !== ($rules = $this->_getRules($resource, $role))) { + foreach ($rules['byPrivilegeId'] as $privilege => $rule) { + if (self::TYPE_DENY === ($ruleTypeOnePrivilege = $this->_getRuleType($resource, $role, $privilege))) { + return false; + } + } + if (null !== ($ruleTypeAllPrivileges = $this->_getRuleType($resource, $role, null))) { + return self::TYPE_ALLOW === $ruleTypeAllPrivileges; + } + } + + $dfs['visited'][$role->getRoleId()] = true; + foreach ($this->_getRoleRegistry()->getParents($role) as $roleParentId => $roleParent) { + $dfs['stack'][] = $roleParent; + } + + return null; + } + + /** + * Performs a depth-first search of the Role DAG, starting at $role, in order to find a rule + * allowing/denying $role access to a $privilege upon $resource + * + * This method returns true if a rule is found and allows access. If a rule exists and denies access, + * then this method returns false. If no applicable rule is found, then this method returns null. + * + * @param Zend_Acl_Role_Interface $role + * @param Zend_Acl_Resource_Interface $resource + * @param string $privilege + * @return boolean|null + * @throws Zend_Acl_Exception + */ + protected function _roleDFSOnePrivilege(Zend_Acl_Role_Interface $role, Zend_Acl_Resource_Interface $resource = null, + $privilege = null) + { + if (null === $privilege) { + /** + * @see Zend_Acl_Exception + */ + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception('$privilege parameter may not be null'); + } + + $dfs = array( + 'visited' => array(), + 'stack' => array() + ); + + if (null !== ($result = $this->_roleDFSVisitOnePrivilege($role, $resource, $privilege, $dfs))) { + return $result; + } + + while (null !== ($role = array_pop($dfs['stack']))) { + if (!isset($dfs['visited'][$role->getRoleId()])) { + if (null !== ($result = $this->_roleDFSVisitOnePrivilege($role, $resource, $privilege, $dfs))) { + return $result; + } + } + } + + return null; + } + + /** + * Visits an $role in order to look for a rule allowing/denying $role access to a $privilege upon $resource + * + * This method returns true if a rule is found and allows access. If a rule exists and denies access, + * then this method returns false. If no applicable rule is found, then this method returns null. + * + * This method is used by the internal depth-first search algorithm and may modify the DFS data structure. + * + * @param Zend_Acl_Role_Interface $role + * @param Zend_Acl_Resource_Interface $resource + * @param string $privilege + * @param array $dfs + * @return boolean|null + * @throws Zend_Acl_Exception + */ + protected function _roleDFSVisitOnePrivilege(Zend_Acl_Role_Interface $role, Zend_Acl_Resource_Interface $resource = null, + $privilege = null, &$dfs = null) + { + if (null === $privilege) { + /** + * @see Zend_Acl_Exception + */ + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception('$privilege parameter may not be null'); + } + + if (null === $dfs) { + /** + * @see Zend_Acl_Exception + */ + require_once 'Zend/Acl/Exception.php'; + throw new Zend_Acl_Exception('$dfs parameter may not be null'); + } + + if (null !== ($ruleTypeOnePrivilege = $this->_getRuleType($resource, $role, $privilege))) { + return self::TYPE_ALLOW === $ruleTypeOnePrivilege; + } else if (null !== ($ruleTypeAllPrivileges = $this->_getRuleType($resource, $role, null))) { + return self::TYPE_ALLOW === $ruleTypeAllPrivileges; + } + + $dfs['visited'][$role->getRoleId()] = true; + foreach ($this->_getRoleRegistry()->getParents($role) as $roleParentId => $roleParent) { + $dfs['stack'][] = $roleParent; + } + + return null; + } + + /** + * Returns the rule type associated with the specified Resource, Role, and privilege + * combination. + * + * If a rule does not exist or its attached assertion fails, which means that + * the rule is not applicable, then this method returns null. Otherwise, the + * rule type applies and is returned as either TYPE_ALLOW or TYPE_DENY. + * + * If $resource or $role is null, then this means that the rule must apply to + * all Resources or Roles, respectively. + * + * If $privilege is null, then the rule must apply to all privileges. + * + * If all three parameters are null, then the default ACL rule type is returned, + * based on whether its assertion method passes. + * + * @param Zend_Acl_Resource_Interface $resource + * @param Zend_Acl_Role_Interface $role + * @param string $privilege + * @return string|null + */ + protected function _getRuleType(Zend_Acl_Resource_Interface $resource = null, Zend_Acl_Role_Interface $role = null, + $privilege = null) + { + // get the rules for the $resource and $role + if (null === ($rules = $this->_getRules($resource, $role))) { + return null; + } + + // follow $privilege + if (null === $privilege) { + if (isset($rules['allPrivileges'])) { + $rule = $rules['allPrivileges']; + } else { + return null; + } + } else if (!isset($rules['byPrivilegeId'][$privilege])) { + return null; + } else { + $rule = $rules['byPrivilegeId'][$privilege]; + } + + // check assertion first + if ($rule['assert']) { + $assertion = $rule['assert']; + $assertionValue = $assertion->assert( + $this, + ($this->_isAllowedRole instanceof Zend_Acl_Role_Interface) ? $this->_isAllowedRole : $role, + ($this->_isAllowedResource instanceof Zend_Acl_Resource_Interface) ? $this->_isAllowedResource : $resource, + $this->_isAllowedPrivilege + ); + } + + if (null === $rule['assert'] || $assertionValue) { + return $rule['type']; + } else if (null !== $resource || null !== $role || null !== $privilege) { + return null; + } else if (self::TYPE_ALLOW === $rule['type']) { + return self::TYPE_DENY; + } else { + return self::TYPE_ALLOW; + } + } + + /** + * Returns the rules associated with a Resource and a Role, or null if no such rules exist + * + * If either $resource or $role is null, this means that the rules returned are for all Resources or all Roles, + * respectively. Both can be null to return the default rule set for all Resources and all Roles. + * + * If the $create parameter is true, then a rule set is first created and then returned to the caller. + * + * @param Zend_Acl_Resource_Interface $resource + * @param Zend_Acl_Role_Interface $role + * @param boolean $create + * @return array|null + */ + protected function &_getRules(Zend_Acl_Resource_Interface $resource = null, Zend_Acl_Role_Interface $role = null, + $create = false) + { + // create a reference to null + $null = null; + $nullRef =& $null; + + // follow $resource + do { + if (null === $resource) { + $visitor =& $this->_rules['allResources']; + break; + } + $resourceId = $resource->getResourceId(); + if (!isset($this->_rules['byResourceId'][$resourceId])) { + if (!$create) { + return $nullRef; + } + $this->_rules['byResourceId'][$resourceId] = array(); + } + $visitor =& $this->_rules['byResourceId'][$resourceId]; + } while (false); + + + // follow $role + if (null === $role) { + if (!isset($visitor['allRoles'])) { + if (!$create) { + return $nullRef; + } + $visitor['allRoles']['byPrivilegeId'] = array(); + } + return $visitor['allRoles']; + } + $roleId = $role->getRoleId(); + if (!isset($visitor['byRoleId'][$roleId])) { + if (!$create) { + return $nullRef; + } + $visitor['byRoleId'][$roleId]['byPrivilegeId'] = array(); + $visitor['byRoleId'][$roleId]['allPrivileges'] = array('type' => null, 'assert' => null); + } + return $visitor['byRoleId'][$roleId]; + } + + + /** + * @return array of registered roles (Deprecated) + * @deprecated Deprecated since version 1.10 (December 2009) + */ + public function getRegisteredRoles() + { + trigger_error('The method getRegisteredRoles() was deprecated as of ' + . 'version 1.0, and may be removed. You\'re encouraged ' + . 'to use getRoles() instead.'); + + return $this->_getRoleRegistry()->getRoles(); + } + + /** + * Returns an array of registered roles. + * + * Note that this method does not return instances of registered roles, + * but only the role identifiers. + * + * @return array of registered roles + */ + public function getRoles() + { + return array_keys($this->_getRoleRegistry()->getRoles()); + } + + /** + * @return array of registered resources + */ + public function getResources() + { + return array_keys($this->_resources); + } + +} + diff --git a/library/Zend/Acl/Assert/Interface.php b/library/Zend/Acl/Assert/Interface.php new file mode 100644 index 0000000..e61c308 --- /dev/null +++ b/library/Zend/Acl/Assert/Interface.php @@ -0,0 +1,64 @@ +_resourceId = (string) $resourceId; + } + + /** + * Defined by Zend_Acl_Resource_Interface; returns the Resource identifier + * + * @return string + */ + public function getResourceId() + { + return $this->_resourceId; + } + + /** + * Defined by Zend_Acl_Resource_Interface; returns the Resource identifier + * Proxies to getResourceId() + * + * @return string + */ + public function __toString() + { + return $this->getResourceId(); + } +} diff --git a/library/Zend/Acl/Resource/Interface.php b/library/Zend/Acl/Resource/Interface.php new file mode 100644 index 0000000..4ab2284 --- /dev/null +++ b/library/Zend/Acl/Resource/Interface.php @@ -0,0 +1,37 @@ +_roleId = (string) $roleId; + } + + /** + * Defined by Zend_Acl_Role_Interface; returns the Role identifier + * + * @return string + */ + public function getRoleId() + { + return $this->_roleId; + } + + /** + * Defined by Zend_Acl_Role_Interface; returns the Role identifier + * Proxies to getRoleId() + * + * @return string + */ + public function __toString() + { + return $this->getRoleId(); + } +} diff --git a/library/Zend/Acl/Role/Interface.php b/library/Zend/Acl/Role/Interface.php new file mode 100644 index 0000000..ddb7b33 --- /dev/null +++ b/library/Zend/Acl/Role/Interface.php @@ -0,0 +1,37 @@ +getRoleId(); + + if ($this->has($roleId)) { + /** + * @see Zend_Acl_Role_Registry_Exception + */ + require_once 'Zend/Acl/Role/Registry/Exception.php'; + throw new Zend_Acl_Role_Registry_Exception("Role id '$roleId' already exists in the registry"); + } + + $roleParents = array(); + + if (null !== $parents) { + if (!is_array($parents)) { + $parents = array($parents); + } + /** + * @see Zend_Acl_Role_Registry_Exception + */ + require_once 'Zend/Acl/Role/Registry/Exception.php'; + foreach ($parents as $parent) { + try { + if ($parent instanceof Zend_Acl_Role_Interface) { + $roleParentId = $parent->getRoleId(); + } else { + $roleParentId = $parent; + } + $roleParent = $this->get($roleParentId); + } catch (Zend_Acl_Role_Registry_Exception $e) { + throw new Zend_Acl_Role_Registry_Exception("Parent Role id '$roleParentId' does not exist", 0, $e); + } + $roleParents[$roleParentId] = $roleParent; + $this->_roles[$roleParentId]['children'][$roleId] = $role; + } + } + + $this->_roles[$roleId] = array( + 'instance' => $role, + 'parents' => $roleParents, + 'children' => array() + ); + + return $this; + } + + /** + * Returns the identified Role + * + * The $role parameter can either be a Role or a Role identifier. + * + * @param Zend_Acl_Role_Interface|string $role + * @throws Zend_Acl_Role_Registry_Exception + * @return Zend_Acl_Role_Interface + */ + public function get($role) + { + if ($role instanceof Zend_Acl_Role_Interface) { + $roleId = $role->getRoleId(); + } else { + $roleId = (string) $role; + } + + if (!$this->has($role)) { + /** + * @see Zend_Acl_Role_Registry_Exception + */ + require_once 'Zend/Acl/Role/Registry/Exception.php'; + throw new Zend_Acl_Role_Registry_Exception("Role '$roleId' not found"); + } + + return $this->_roles[$roleId]['instance']; + } + + /** + * Returns true if and only if the Role exists in the registry + * + * The $role parameter can either be a Role or a Role identifier. + * + * @param Zend_Acl_Role_Interface|string $role + * @return boolean + */ + public function has($role) + { + if ($role instanceof Zend_Acl_Role_Interface) { + $roleId = $role->getRoleId(); + } else { + $roleId = (string) $role; + } + + return isset($this->_roles[$roleId]); + } + + /** + * Returns an array of an existing Role's parents + * + * The array keys are the identifiers of the parent Roles, and the values are + * the parent Role instances. The parent Roles are ordered in this array by + * ascending priority. The highest priority parent Role, last in the array, + * corresponds with the parent Role most recently added. + * + * If the Role does not have any parents, then an empty array is returned. + * + * @param Zend_Acl_Role_Interface|string $role + * @uses Zend_Acl_Role_Registry::get() + * @return array + */ + public function getParents($role) + { + $roleId = $this->get($role)->getRoleId(); + + return $this->_roles[$roleId]['parents']; + } + + /** + * Returns true if and only if $role inherits from $inherit + * + * Both parameters may be either a Role or a Role identifier. If + * $onlyParents is true, then $role must inherit directly from + * $inherit in order to return true. By default, this method looks + * through the entire inheritance DAG to determine whether $role + * inherits from $inherit through its ancestor Roles. + * + * @param Zend_Acl_Role_Interface|string $role + * @param Zend_Acl_Role_Interface|string $inherit + * @param boolean $onlyParents + * @throws Zend_Acl_Role_Registry_Exception + * @return boolean + */ + public function inherits($role, $inherit, $onlyParents = false) + { + /** + * @see Zend_Acl_Role_Registry_Exception + */ + require_once 'Zend/Acl/Role/Registry/Exception.php'; + try { + $roleId = $this->get($role)->getRoleId(); + $inheritId = $this->get($inherit)->getRoleId(); + } catch (Zend_Acl_Role_Registry_Exception $e) { + throw new Zend_Acl_Role_Registry_Exception($e->getMessage(), $e->getCode(), $e); + } + + $inherits = isset($this->_roles[$roleId]['parents'][$inheritId]); + + if ($inherits || $onlyParents) { + return $inherits; + } + + foreach ($this->_roles[$roleId]['parents'] as $parentId => $parent) { + if ($this->inherits($parentId, $inheritId)) { + return true; + } + } + + return false; + } + + /** + * Removes the Role from the registry + * + * The $role parameter can either be a Role or a Role identifier. + * + * @param Zend_Acl_Role_Interface|string $role + * @throws Zend_Acl_Role_Registry_Exception + * @return Zend_Acl_Role_Registry Provides a fluent interface + */ + public function remove($role) + { + /** + * @see Zend_Acl_Role_Registry_Exception + */ + require_once 'Zend/Acl/Role/Registry/Exception.php'; + try { + $roleId = $this->get($role)->getRoleId(); + } catch (Zend_Acl_Role_Registry_Exception $e) { + throw new Zend_Acl_Role_Registry_Exception($e->getMessage(), $e->getCode(), $e); + } + + foreach ($this->_roles[$roleId]['children'] as $childId => $child) { + unset($this->_roles[$childId]['parents'][$roleId]); + } + foreach ($this->_roles[$roleId]['parents'] as $parentId => $parent) { + unset($this->_roles[$parentId]['children'][$roleId]); + } + + unset($this->_roles[$roleId]); + + return $this; + } + + /** + * Removes all Roles from the registry + * + * @return Zend_Acl_Role_Registry Provides a fluent interface + */ + public function removeAll() + { + $this->_roles = array(); + + return $this; + } + + public function getRoles() + { + return $this->_roles; + } + +} diff --git a/library/Zend/Acl/Role/Registry/Exception.php b/library/Zend/Acl/Role/Registry/Exception.php new file mode 100644 index 0000000..9a518ad --- /dev/null +++ b/library/Zend/Acl/Role/Registry/Exception.php @@ -0,0 +1,36 @@ +_acl = new Zend_Acl(); + $xml = simplexml_load_file($rolefile); +/* +Roles file format: +
Zend Amf Endpoint
' ; + } + } + + /** + * Retrieve raw AMF Request + * + * @return string + */ + public function getRawRequest() + { + return $this->_rawRequest; + } +} diff --git a/library/Zend/Amf/Response.php b/library/Zend/Amf/Response.php new file mode 100644 index 0000000..cac34de --- /dev/null +++ b/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/library/Zend/Amf/Response/Http.php b/library/Zend/Amf/Response/Http.php new file mode 100644 index 0000000..0e52c78 --- /dev/null +++ b/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/library/Zend/Amf/Server.php b/library/Zend/Amf/Server.php new file mode 100644 index 0000000..c2ca709 --- /dev/null +++ b/library/Zend/Amf/Server.php @@ -0,0 +1,1048 @@ + method pairs + * @var array + */ + protected $_table = array(); + + /** + * + * @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(array("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 + */ + 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 unknown_type + */ + 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 = array($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 $param values for the method + * @return mixed $response the result of executing the method + * @throws Zend_Amf_Server_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(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 + * + * @see Zend_Amf_Value_Messaging_CommandMessage + * @param Zend_Amf_Value_Messaging_CommandMessage $message + * @return Zend_Amf_Value_Messaging_AcknowledgeMessage + */ + 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 array ( + '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 + */ + 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. + * + * @todo should implement and SPL observer pattern for custom AMF headers + * @todo DescribeService support + * @param Zend_Amf_Request $request + * @return Zend_Amf_Response + * @throws Zend_Amf_server_Exception|Exception + */ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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_Server_Response + */ + 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 + */ + 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'); + } elseif (!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'); + } + + $argv = null; + if (2 < func_num_args()) { + $argv = 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, $argv, $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 + */ + 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 + */ + 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 + */ + protected function _buildDispatchTable() + { + $table = array(); + 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 = array( + 'null', + 'mixed', + 'void', + 'unknown', + 'bool', + 'boolean', + 'number', + 'int', + 'integer', + 'double', + 'float', + 'string', + 'array', + 'object', + 'stdclass', + ); + $types = array(); + 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/library/Zend/Amf/Server/Exception.php b/library/Zend/Amf/Server/Exception.php new file mode 100644 index 0000000..b961831 --- /dev/null +++ b/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 Zend_Amf_Util_BinaryStream + */ + 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/library/Zend/Amf/Value/ByteArray.php b/library/Zend/Amf/Value/ByteArray.php new file mode 100644 index 0000000..b62c182 --- /dev/null +++ b/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/library/Zend/Amf/Value/MessageBody.php b/library/Zend/Amf/Value/MessageBody.php new file mode 100644 index 0000000..fe58eea --- /dev/null +++ b/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-2012 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/library/Zend/Amf/Value/MessageHeader.php b/library/Zend/Amf/Value/MessageHeader.php new file mode 100644 index 0000000..b106c50 --- /dev/null +++ b/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/library/Zend/Amf/Value/Messaging/AbstractMessage.php b/library/Zend/Amf/Value/Messaging/AbstractMessage.php new file mode 100644 index 0000000..f342b3f --- /dev/null +++ b/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/library/Zend/Amf/Value/Messaging/ArrayCollection.php b/library/Zend/Amf/Value/Messaging/ArrayCollection.php new file mode 100644 index 0000000..bdcfeec --- /dev/null +++ b/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/library/Zend/Amf/Value/Messaging/ErrorMessage.php b/library/Zend/Amf/Value/Messaging/ErrorMessage.php new file mode 100644 index 0000000..40813a0 --- /dev/null +++ b/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/library/Zend/Amf/Value/TraitsInfo.php b/library/Zend/Amf/Value/TraitsInfo.php new file mode 100644 index 0000000..f8863cd --- /dev/null +++ b/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/library/Zend/Application.php b/library/Zend/Application.php new file mode 100644 index 0000000..fa229c9 --- /dev/null +++ b/library/Zend/Application.php @@ -0,0 +1,417 @@ +_environment = (string) $environment; + + require_once 'Zend/Loader/Autoloader.php'; + $this->_autoloader = Zend_Loader_Autoloader::getInstance(); + + 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 = array(); + 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($array2[$key])) { + $array1[$key] = (array_key_exists($key, $array1) && is_array($array1[$key])) + ? $this->mergeOptions($array1[$key], $array2[$key]) + : $array2[$key]; + } 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; + + switch (strtolower($suffix)) { + case 'ini': + $config = new Zend_Config_Ini($file, $environment); + break; + + case 'xml': + $config = new Zend_Config_Xml($file, $environment); + break; + + case 'json': + $config = new Zend_Config_Json($file, $environment); + break; + + case 'yaml': + case 'yml': + $config = new Zend_Config_Yaml($file, $environment); + 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/library/Zend/Application/Bootstrap/Bootstrap.php b/library/Zend/Application/Bootstrap/Bootstrap.php new file mode 100644 index 0000000..d81cc25 --- /dev/null +++ b/library/Zend/Application/Bootstrap/Bootstrap.php @@ -0,0 +1,156 @@ +hasOption('resourceloader')) { + $this->setOptions(array( + '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_Module_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(array( + '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/library/Zend/Application/Bootstrap/BootstrapAbstract.php b/library/Zend/Application/Bootstrap/BootstrapAbstract.php new file mode 100644 index 0000000..7a0f94f --- /dev/null +++ b/library/Zend/Application/Bootstrap/BootstrapAbstract.php @@ -0,0 +1,772 @@ +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($array2[$key])) { + $array1[$key] = (array_key_exists($key, $array1) && is_array($array1[$key])) + ? $this->mergeOptions($array1[$key], $array2[$key]) + : $array2[$key]; + } 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 = array(); + + foreach ($classMethods as $method) { + $methodNames[] = $method->getName(); + } + } else { + $methodNames = get_class_methods($this); + } + + $this->_classResources = array(); + 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 $resourceName + * @return Zend_Application_Resource_Resource + */ + 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 = array( + '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 + */ + 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 + */ + 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 ressource + * 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 ressource 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/library/Zend/Captcha/Image.php b/library/Zend/Captcha/Image.php new file mode 100644 index 0000000..0db2cc8 --- /dev/null +++ b/library/Zend/Captcha/Image.php @@ -0,0 +1,609 @@ +_imgAlt; + } + /** + * @return string + */ + public function getStartImage () + { + return $this->_startImage; + } + /** + * @return int + */ + public function getDotNoiseLevel () + { + return $this->_dotNoiseLevel; + } + /** + * @return int + */ + public function getLineNoiseLevel () + { + return $this->_lineNoiseLevel; + } + /** + * Get captcha expiration + * + * @return int + */ + public function getExpiration() + { + return $this->_expiration; + } + + /** + * Get garbage collection frequency + * + * @return int + */ + public function getGcFreq() + { + return $this->_gcFreq; + } + /** + * Get font to use when generating captcha + * + * @return string + */ + public function getFont() + { + return $this->_font; + } + + /** + * Get font size + * + * @return int + */ + public function getFontSize() + { + return $this->_fsize; + } + + /** + * Get captcha image height + * + * @return int + */ + public function getHeight() + { + return $this->_height; + } + + /** + * Get captcha image directory + * + * @return string + */ + public function getImgDir() + { + return $this->_imgDir; + } + /** + * Get captcha image base URL + * + * @return string + */ + public function getImgUrl() + { + return $this->_imgUrl; + } + /** + * Get captcha image file suffix + * + * @return string + */ + public function getSuffix() + { + return $this->_suffix; + } + /** + * Get captcha image width + * + * @return int + */ + public function getWidth() + { + return $this->_width; + } + /** + * @param string $startImage + */ + public function setStartImage ($startImage) + { + $this->_startImage = $startImage; + return $this; + } + /** + * @param int $dotNoiseLevel + */ + public function setDotNoiseLevel ($dotNoiseLevel) + { + $this->_dotNoiseLevel = $dotNoiseLevel; + return $this; + } + /** + * @param int $lineNoiseLevel + */ + 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; + } + /** + * @param string $imgAlt + */ + 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 + */ + 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; + } else { + $color = (imagecolorat($img, $sx, $sy) >> 16) & 0xFF; + $color_x = (imagecolorat($img, $sx + 1, $sy) >> 16) & 0xFF; + $color_y = (imagecolorat($img, $sx, $sy + 1) >> 16) & 0xFF; + $color_xy = (imagecolorat($img, $sx + 1, $sy + 1) >> 16) & 0xFF; + } + if ($color == 255 && $color_x == 255 && $color_y == 255 && $color_xy == 255) { + // ignore background + continue; + } elseif ($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, $newcolor, $newcolor, $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 ' 'Missing captcha fields', + self::ERR_CAPTCHA => 'Failed to validate captcha', + self::BAD_CAPTCHA => 'Captcha value is wrong: %value%', + ); + + /** + * Retrieve ReCaptcha Private key + * + * @return string + */ + public function getPrivkey() + { + return $this->getService()->getPrivateKey(); + } + + /** + * Retrieve ReCaptcha Public key + * + * @return string + */ + public function getPubkey() + { + return $this->getService()->getPublicKey(); + } + + /** + * Set ReCaptcha Private key + * + * @param string $privkey + * @return Zend_Captcha_ReCaptcha + */ + public function setPrivkey($privkey) + { + $this->getService()->setPrivateKey($privkey); + return $this; + } + + /** + * Set ReCaptcha public key + * + * @param string $pubkey + * @return Zend_Captcha_ReCaptcha + */ + public function setPubkey($pubkey) + { + $this->getService()->setPublicKey($pubkey); + return $this; + } + + /** + * Constructor + * + * @param array|Zend_Config $options + * @return void + */ + public function __construct($options = null) + { + $this->setService(new Zend_Service_ReCaptcha()); + $this->_serviceParams = $this->getService()->getParams(); + $this->_serviceOptions = $this->getService()->getOptions(); + + parent::__construct($options); + + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } + if (!empty($options)) { + $this->setOptions($options); + } + } + + /** + * Set service object + * + * @param Zend_Service_ReCaptcha $service + * @return Zend_Captcha_ReCaptcha + */ + public function setService(Zend_Service_ReCaptcha $service) + { + $this->_service = $service; + return $this; + } + + /** + * Retrieve ReCaptcha service object + * + * @return Zend_Service_ReCaptcha + */ + public function getService() + { + return $this->_service; + } + + /** + * Set option + * + * If option is a service parameter, proxies to the service. The same + * goes for any service options (distinct from service params) + * + * @param string $key + * @param mixed $value + * @return Zend_Captcha_ReCaptcha + */ + public function setOption($key, $value) + { + $service = $this->getService(); + if (isset($this->_serviceParams[$key])) { + $service->setParam($key, $value); + return $this; + } + if (isset($this->_serviceOptions[$key])) { + $service->setOption($key, $value); + return $this; + } + return parent::setOption($key, $value); + } + + /** + * Generate captcha + * + * @see Zend_Form_Captcha_Adapter::generate() + * @return string + */ + public function generate() + { + return ""; + } + + /** + * Validate captcha + * + * @see Zend_Validate_Interface::isValid() + * @param mixed $value + * @return boolean + */ + public function isValid($value, $context = null) + { + if (!is_array($value) && !is_array($context)) { + $this->_error(self::MISSING_VALUE); + return false; + } + + if (!is_array($value) && is_array($context)) { + $value = $context; + } + + if (empty($value[$this->_CHALLENGE]) || empty($value[$this->_RESPONSE])) { + $this->_error(self::MISSING_VALUE); + return false; + } + + $service = $this->getService(); + + $res = $service->verify($value[$this->_CHALLENGE], $value[$this->_RESPONSE]); + + if (!$res) { + $this->_error(self::ERR_CAPTCHA); + return false; + } + + if (!$res->isValid()) { + $this->_error(self::BAD_CAPTCHA, $res->getErrorCode()); + $service->setParam('error', $res->getErrorCode()); + return false; + } + + return true; + } + + /** + * Render captcha + * + * @param Zend_View_Interface $view + * @param mixed $element + * @return string + */ + public function render(Zend_View_Interface $view = null, $element = null) + { + $name = null; + if ($element instanceof Zend_Form_Element) { + $name = $element->getBelongsTo(); + } + return $this->getService()->getHTML($name); + } + + /** + * Get captcha decorator + * + * @return string + */ + public function getDecorator() + { + return "Captcha_ReCaptcha"; + } +} diff --git a/library/Zend/Captcha/Word.php b/library/Zend/Captcha/Word.php new file mode 100644 index 0000000..7bef5ef --- /dev/null +++ b/library/Zend/Captcha/Word.php @@ -0,0 +1,418 @@ + 'Empty captcha value', + self::MISSING_ID => 'Captcha ID field is missing', + self::BAD_CAPTCHA => 'Captcha value is wrong', + ); + + /** + * Length of the word to generate + * + * @var integer + */ + protected $_wordlen = 8; + + /** + * Retrieve session class to utilize + * + * @return string + */ + public function getSessionClass() + { + return $this->_sessionClass; + } + + /** + * Set session class for persistence + * + * @param string $_sessionClass + * @return Zend_Captcha_Word + */ + public function setSessionClass($_sessionClass) + { + $this->_sessionClass = $_sessionClass; + return $this; + } + + /** + * Retrieve word length to use when genrating captcha + * + * @return integer + */ + public function getWordlen() + { + return $this->_wordlen; + } + + /** + * Set word length of captcha + * + * @param integer $wordlen + * @return Zend_Captcha_Word + */ + public function setWordlen($wordlen) + { + $this->_wordlen = $wordlen; + return $this; + } + + /** + * Retrieve captcha ID + * + * @return string + */ + public function getId () + { + if (null === $this->_id) { + $this->_setId($this->_generateRandomId()); + } + return $this->_id; + } + + /** + * Set captcha identifier + * + * @param string $id + * return Zend_Captcha_Word + */ + protected function _setId ($id) + { + $this->_id = $id; + return $this; + } + + /** + * Set timeout for session token + * + * @param int $ttl + * @return Zend_Captcha_Word + */ + public function setTimeout($ttl) + { + $this->_timeout = (int) $ttl; + return $this; + } + + /** + * Get session token timeout + * + * @return int + */ + public function getTimeout() + { + return $this->_timeout; + } + + /** + * Sets if session should be preserved on generate() + * + * @param bool $keepSession Should session be kept on generate()? + * @return Zend_Captcha_Word + */ + public function setKeepSession($keepSession) + { + $this->_keepSession = $keepSession; + return $this; + } + + /** + * Numbers should be included in the pattern? + * + * @return bool + */ + public function getUseNumbers() + { + return $this->_useNumbers; + } + + /** + * Set if numbers should be included in the pattern + * + * @param bool $_useNumbers numbers should be included in the pattern? + * @return Zend_Captcha_Word + */ + public function setUseNumbers($_useNumbers) + { + $this->_useNumbers = $_useNumbers; + return $this; + } + + /** + * Get session object + * + * @return Zend_Session_Namespace + */ + public function getSession() + { + if (!isset($this->_session) || (null === $this->_session)) { + $id = $this->getId(); + if (!class_exists($this->_sessionClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($this->_sessionClass); + } + $this->_session = new $this->_sessionClass('Zend_Form_Captcha_' . $id); + $this->_session->setExpirationHops(1, null, true); + $this->_session->setExpirationSeconds($this->getTimeout()); + } + return $this->_session; + } + + /** + * Set session namespace object + * + * @param Zend_Session_Namespace $session + * @return Zend_Captcha_Word + */ + public function setSession(Zend_Session_Namespace $session) + { + $this->_session = $session; + if($session) { + $this->_keepSession = true; + } + return $this; + } + + /** + * Get captcha word + * + * @return string + */ + public function getWord() + { + if (empty($this->_word)) { + $session = $this->getSession(); + $this->_word = $session->word; + } + return $this->_word; + } + + /** + * Set captcha word + * + * @param string $word + * @return Zend_Captcha_Word + */ + protected function _setWord($word) + { + $session = $this->getSession(); + $session->word = $word; + $this->_word = $word; + return $this; + } + + /** + * Generate new random word + * + * @return string + */ + protected function _generateWord() + { + $word = ''; + $wordLen = $this->getWordLen(); + $vowels = $this->_useNumbers ? self::$VN : self::$V; + $consonants = $this->_useNumbers ? self::$CN : self::$C; + + for ($i=0; $i < $wordLen; $i = $i + 2) { + // generate word with mix of vowels and consonants + $consonant = $consonants[array_rand($consonants)]; + $vowel = $vowels[array_rand($vowels)]; + $word .= $consonant . $vowel; + } + + if (strlen($word) > $wordLen) { + $word = substr($word, 0, $wordLen); + } + + return $word; + } + + /** + * Generate new session ID and new word + * + * @return string session ID + */ + public function generate() + { + if(!$this->_keepSession) { + $this->_session = null; + } + $id = $this->_generateRandomId(); + $this->_setId($id); + $word = $this->_generateWord(); + $this->_setWord($word); + return $id; + } + + protected function _generateRandomId() + { + return md5(mt_rand(0, 1000) . microtime(true)); + } + + /** + * Validate the word + * + * @see Zend_Validate_Interface::isValid() + * @param mixed $value + * @return boolean + */ + public function isValid($value, $context = null) + { + if (!is_array($value) && !is_array($context)) { + $this->_error(self::MISSING_VALUE); + return false; + } + if (!is_array($value) && is_array($context)) { + $value = $context; + } + + $name = $this->getName(); + + if (isset($value[$name])) { + $value = $value[$name]; + } + + if (!isset($value['input'])) { + $this->_error(self::MISSING_VALUE); + return false; + } + $input = strtolower($value['input']); + $this->_setValue($input); + + if (!isset($value['id'])) { + $this->_error(self::MISSING_ID); + return false; + } + + $this->_id = $value['id']; + if ($input !== $this->getWord()) { + $this->_error(self::BAD_CAPTCHA); + return false; + } + + return true; + } + + /** + * Get captcha decorator + * + * @return string + */ + public function getDecorator() + { + return "Captcha_Word"; + } +} diff --git a/library/Zend/Cloud/AbstractFactory.php b/library/Zend/Cloud/AbstractFactory.php new file mode 100644 index 0000000..ad480a0 --- /dev/null +++ b/library/Zend/Cloud/AbstractFactory.php @@ -0,0 +1,67 @@ +toArray(); + } + + if (!isset($options[$adapterOption])) { + return null; + } + + $classname = $options[$adapterOption]; + unset($options[$adapterOption]); + if (!class_exists($classname)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($classname); + } + + return new $classname($options); + } +} diff --git a/library/Zend/Cloud/DocumentService/Adapter.php b/library/Zend/Cloud/DocumentService/Adapter.php new file mode 100644 index 0000000..9479fea --- /dev/null +++ b/library/Zend/Cloud/DocumentService/Adapter.php @@ -0,0 +1,155 @@ +_documentClass = (string) $class; + return $this; + } + + /** + * Get the class for document objects + * + * @return string + */ + public function getDocumentClass() + { + return $this->_documentClass; + } + + /** + * Set the class for document set objects + * + * @param string $class + * @return Zend_Cloud_DocumentService_Adapter_AbstractAdapter + */ + public function setDocumentSetClass($class) + { + $this->_documentSetClass = (string) $class; + return $this; + } + + /** + * Get the class for document set objects + * + * @return string + */ + public function getDocumentSetClass() + { + return $this->_documentSetClass; + } + + /** + * Set the query class for query objects + * + * @param string $class + * @return Zend_Cloud_DocumentService_Adapter_AbstractAdapter + */ + public function setQueryClass($class) + { + $this->_queryClass = (string) $class; + return $this; + } + + /** + * Get the class for query objects + * + * @return string + */ + public function getQueryClass() + { + return $this->_queryClass; + } +} diff --git a/library/Zend/Cloud/DocumentService/Adapter/SimpleDb.php b/library/Zend/Cloud/DocumentService/Adapter/SimpleDb.php new file mode 100644 index 0000000..1e2ffe0 --- /dev/null +++ b/library/Zend/Cloud/DocumentService/Adapter/SimpleDb.php @@ -0,0 +1,468 @@ +toArray(); + } + + if (!is_array($options)) { + throw new Zend_Cloud_DocumentService_Exception('Invalid options provided to constructor'); + } + + $this->_simpleDb = new Zend_Service_Amazon_SimpleDb( + $options[self::AWS_ACCESS_KEY], $options[self::AWS_SECRET_KEY] + ); + + if (isset($options[self::HTTP_ADAPTER])) { + $this->_simpleDb->getHttpClient()->setAdapter($options[self::HTTP_ADAPTER]); + } + + if (isset($options[self::DOCUMENT_CLASS])) { + $this->setDocumentClass($options[self::DOCUMENT_CLASS]); + } + + if (isset($options[self::DOCUMENTSET_CLASS])) { + $this->setDocumentSetClass($options[self::DOCUMENTSET_CLASS]); + } + + if (isset($options[self::QUERY_CLASS])) { + $this->setQueryClass($options[self::QUERY_CLASS]); + } + } + + /** + * Create collection. + * + * @param string $name + * @param array $options + * @return void + */ + public function createCollection($name, $options = null) + { + try { + $this->_simpleDb->createDomain($name); + } catch(Zend_Service_Amazon_Exception $e) { + throw new Zend_Cloud_DocumentService_Exception('Error on domain creation: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Delete collection. + * + * @param string $name + * @param array $options + * @return void + */ + public function deleteCollection($name, $options = null) + { + try { + $this->_simpleDb->deleteDomain($name); + } catch(Zend_Service_Amazon_Exception $e) { + throw new Zend_Cloud_DocumentService_Exception('Error on collection deletion: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * List collections. + * + * @param array $options + * @return array + */ + public function listCollections($options = null) + { + try { + // TODO package this in Pages + $domains = $this->_simpleDb->listDomains()->getData(); + } catch(Zend_Service_Amazon_Exception $e) { + throw new Zend_Cloud_DocumentService_Exception('Error on collection deletion: '.$e->getMessage(), $e->getCode(), $e); + } + + return $domains; + } + + /** + * List documents + * + * Returns a key/value array of document names to document objects. + * + * @param string $collectionName Name of collection for which to list documents + * @param array|null $options + * @return Zend_Cloud_DocumentService_DocumentSet + */ + public function listDocuments($collectionName, array $options = null) + { + $query = $this->select('*')->from($collectionName); + $items = $this->query($collectionName, $query, $options); + return $items; + } + + /** + * Insert document + * + * @param string $collectionName Collection into which to insert document + * @param array|Zend_Cloud_DocumentService_Document $document + * @param array $options + * @return void + */ + public function insertDocument($collectionName, $document, $options = null) + { + if (is_array($document)) { + $document = $this->_getDocumentFromArray($document); + } + + if (!$document instanceof Zend_Cloud_DocumentService_Document) { + throw new Zend_Cloud_DocumentService_Exception('Invalid document supplied'); + } + + try { + $this->_simpleDb->putAttributes( + $collectionName, + $document->getID(), + $this->_makeAttributes($document->getID(), $document->getFields()) + ); + } catch(Zend_Service_Amazon_Exception $e) { + throw new Zend_Cloud_DocumentService_Exception('Error on document insertion: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Replace an existing document with a new version + * + * @param string $collectionName + * @param array|Zend_Cloud_DocumentService_Document $document + * @param array $options + * @return void + */ + public function replaceDocument($collectionName, $document, $options = null) + { + if (is_array($document)) { + $document = $this->_getDocumentFromArray($document); + } + + if (!$document instanceof Zend_Cloud_DocumentService_Document) { + throw new Zend_Cloud_DocumentService_Exception('Invalid document supplied'); + } + + // Delete document first, then insert. PutAttributes always keeps any + // fields not referenced in the payload, but present in the document + $documentId = $document->getId(); + $fields = $document->getFields(); + $docClass = get_class($document); + $this->deleteDocument($collectionName, $document, $options); + + $document = new $docClass($fields, $documentId); + $this->insertDocument($collectionName, $document); + } + + /** + * Update document. The new document replaces the existing document. + * + * Option 'merge' specifies to add all attributes (if true) or + * specific attributes ("attr" => true) instead of replacing them. + * By default, attributes are replaced. + * + * @param string $collectionName + * @param mixed|Zend_Cloud_DocumentService_Document $documentId Document ID, adapter-dependent + * @param array|Zend_Cloud_DocumentService_Document $fieldset Set of fields to update + * @param array $options + * @return boolean + */ + public function updateDocument($collectionName, $documentId, $fieldset = null, $options = null) + { + if (null === $fieldset && $documentId instanceof Zend_Cloud_DocumentService_Document) { + $fieldset = $documentId->getFields(); + if (empty($documentId)) { + $documentId = $documentId->getId(); + } + } elseif ($fieldset instanceof Zend_Cloud_DocumentService_Document) { + if (empty($documentId)) { + $documentId = $fieldset->getId(); + } + $fieldset = $fieldset->getFields(); + } + + $replace = array(); + if (empty($options[self::MERGE_OPTION])) { + // no merge option - we replace all + foreach ($fieldset as $key => $value) { + $replace[$key] = true; + } + } elseif (is_array($options[self::MERGE_OPTION])) { + foreach ($fieldset as $key => $value) { + if (empty($options[self::MERGE_OPTION][$key])) { + // if there's merge key, we add it, otherwise we replace it + $replace[$key] = true; + } + } + } // otherwise $replace is empty - all is merged + + try { + $this->_simpleDb->putAttributes( + $collectionName, + $documentId, + $this->_makeAttributes($documentId, $fieldset), + $replace + ); + } catch(Zend_Service_Amazon_Exception $e) { + throw new Zend_Cloud_DocumentService_Exception('Error on document update: '.$e->getMessage(), $e->getCode(), $e); + } + return true; + } + + /** + * Delete document. + * + * @param string $collectionName Collection from which to delete document + * @param mixed $document Document ID or Document object. + * @param array $options + * @return boolean + */ + public function deleteDocument($collectionName, $document, $options = null) + { + if ($document instanceof Zend_Cloud_DocumentService_Document) { + $document = $document->getId(); + } + try { + $this->_simpleDb->deleteAttributes($collectionName, $document); + } catch(Zend_Service_Amazon_Exception $e) { + throw new Zend_Cloud_DocumentService_Exception('Error on document deletion: '.$e->getMessage(), $e->getCode(), $e); + } + return true; + } + + /** + * Fetch single document by ID + * + * @param string $collectionName Collection name + * @param mixed $documentId Document ID, adapter-dependent + * @param array $options + * @return Zend_Cloud_DocumentService_Document + */ + public function fetchDocument($collectionName, $documentId, $options = null) + { + try { + $attributes = $this->_simpleDb->getAttributes($collectionName, $documentId); + if ($attributes == false || count($attributes) == 0) { + return false; + } + return $this->_resolveAttributes($attributes, true); + } catch(Zend_Service_Amazon_Exception $e) { + throw new Zend_Cloud_DocumentService_Exception('Error on fetching document: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Query for documents stored in the document service. If a string is passed in + * $query, the query string will be passed directly to the service. + * + * @param string $collectionName Collection name + * @param string $query + * @param array $options + * @return array Zend_Cloud_DocumentService_DocumentSet + */ + public function query($collectionName, $query, $options = null) + { + $returnDocs = isset($options[self::RETURN_DOCUMENTS]) + ? (bool) $options[self::RETURN_DOCUMENTS] + : true; + + try { + if ($query instanceof Zend_Cloud_DocumentService_Adapter_SimpleDb_Query) { + $query = $query->assemble($collectionName); + } + $result = $this->_simpleDb->select($query); + } catch(Zend_Service_Amazon_Exception $e) { + throw new Zend_Cloud_DocumentService_Exception('Error on document query: '.$e->getMessage(), $e->getCode(), $e); + } + + return $this->_getDocumentSetFromResultSet($result, $returnDocs); + } + + /** + * Create query statement + * + * @param string $fields + * @return Zend_Cloud_DocumentService_Adapter_SimpleDb_Query + */ + public function select($fields = null) + { + $queryClass = $this->getQueryClass(); + if (!class_exists($queryClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($queryClass); + } + + $query = new $queryClass($this); + $defaultClass = self::DEFAULT_QUERY_CLASS; + if (!$query instanceof $defaultClass) { + throw new Zend_Cloud_DocumentService_Exception('Query class must extend ' . self::DEFAULT_QUERY_CLASS); + } + + $query->select($fields); + return $query; + } + + /** + * Get the concrete service client + * + * @return Zend_Service_Amazon_SimpleDb + */ + public function getClient() + { + return $this->_simpleDb; + } + + /** + * Convert array of key-value pairs to array of Amazon attributes + * + * @param string $name + * @param array $attributes + * @return array + */ + protected function _makeAttributes($name, $attributes) + { + $result = array(); + foreach ($attributes as $key => $attr) { + $result[] = new Zend_Service_Amazon_SimpleDb_Attribute($name, $key, $attr); + } + return $result; + } + + /** + * Convert array of Amazon attributes to array of key-value pairs + * + * @param array $attributes + * @return array + */ + protected function _resolveAttributes($attributes, $returnDocument = false) + { + $result = array(); + foreach ($attributes as $attr) { + $value = $attr->getValues(); + if (count($value) == 0) { + $value = null; + } elseif (count($value) == 1) { + $value = $value[0]; + } + $result[$attr->getName()] = $value; + } + + // Return as document object? + if ($returnDocument) { + $documentClass = $this->getDocumentClass(); + return new $documentClass($result, $attr->getItemName()); + } + + return $result; + } + + /** + * Create suitable document from array of fields + * + * @param array $document + * @return Zend_Cloud_DocumentService_Document + */ + protected function _getDocumentFromArray($document) + { + if (!isset($document[Zend_Cloud_DocumentService_Document::KEY_FIELD])) { + if (isset($document[self::ITEM_NAME])) { + $key = $document[self::ITEM_NAME]; + unset($document[self::ITEM_NAME]); + } else { + throw new Zend_Cloud_DocumentService_Exception('Fields array should contain the key field '.Zend_Cloud_DocumentService_Document::KEY_FIELD); + } + } else { + $key = $document[Zend_Cloud_DocumentService_Document::KEY_FIELD]; + unset($document[Zend_Cloud_DocumentService_Document::KEY_FIELD]); + } + + $documentClass = $this->getDocumentClass(); + return new $documentClass($document, $key); + } + + /** + * Create a DocumentSet from a SimpleDb resultset + * + * @param Zend_Service_Amazon_SimpleDb_Page $resultSet + * @param bool $returnDocs + * @return Zend_Cloud_DocumentService_DocumentSet + */ + protected function _getDocumentSetFromResultSet(Zend_Service_Amazon_SimpleDb_Page $resultSet, $returnDocs = true) + { + $docs = array(); + foreach ($resultSet->getData() as $item) { + $docs[] = $this->_resolveAttributes($item, $returnDocs); + } + + $setClass = $this->getDocumentSetClass(); + return new $setClass($docs); + } +} diff --git a/library/Zend/Cloud/DocumentService/Adapter/SimpleDb/Query.php b/library/Zend/Cloud/DocumentService/Adapter/SimpleDb/Query.php new file mode 100644 index 0000000..89545d8 --- /dev/null +++ b/library/Zend/Cloud/DocumentService/Adapter/SimpleDb/Query.php @@ -0,0 +1,175 @@ +_adapter = $adapter; + if (null !== $collectionName) { + $this->from($collectionName); + } + } + + /** + * Get adapter + * + * @return Zend_Cloud_DocumentService_Adapter_SimpleDb + */ + public function getAdapter() + { + return $this->_adapter; + } + + /** + * Assemble the query into a format the adapter can utilize + * + * @var string $collectionName Name of collection from which to select + * @return string + */ + public function assemble($collectionName = null) + { + $adapter = $this->getAdapter()->getClient(); + $select = null; + $from = null; + $where = null; + $order = null; + $limit = null; + foreach ($this->getClauses() as $clause) { + list($name, $args) = $clause; + + switch ($name) { + case self::QUERY_SELECT: + $select = $args[0]; + break; + case self::QUERY_FROM: + if (null === $from) { + // Only allow setting FROM clause once + $from = $adapter->quoteName($args); + } + break; + case self::QUERY_WHERE: + $statement = $this->_parseWhere($args[0], $args[1]); + if (null === $where) { + $where = $statement; + } else { + $operator = empty($args[2]) ? 'AND' : $args[2]; + $where .= ' ' . $operator . ' ' . $statement; + } + break; + case self::QUERY_WHEREID: + $statement = $this->_parseWhere('ItemName() = ?', array($args)); + if (null === $where) { + $where = $statement; + } else { + $operator = empty($args[2]) ? 'AND' : $args[2]; + $where .= ' ' . $operator . ' ' . $statement; + } + break; + case self::QUERY_ORDER: + $order = $adapter->quoteName($args[0]); + if (isset($args[1])) { + $order .= ' ' . $args[1]; + } + break; + case self::QUERY_LIMIT: + $limit = $args; + break; + default: + // Ignore unknown clauses + break; + } + } + + if (empty($select)) { + $select = "*"; + } + if (empty($from)) { + if (null === $collectionName) { + require_once 'Zend/Cloud/DocumentService/Exception.php'; + throw new Zend_Cloud_DocumentService_Exception("Query requires a FROM clause"); + } + $from = $adapter->quoteName($collectionName); + } + $query = "select $select from $from"; + if (!empty($where)) { + $query .= " where $where"; + } + if (!empty($order)) { + $query .= " order by $order"; + } + if (!empty($limit)) { + $query .= " limit $limit"; + } + return $query; + } + + /** + * Parse a where statement into service-specific language + * + * @todo Ensure this fulfills the entire SimpleDB query specification for WHERE + * @param string $where + * @param array $args + * @return string + */ + protected function _parseWhere($where, $args) + { + if (!is_array($args)) { + $args = (array) $args; + } + $adapter = $this->getAdapter()->getClient(); + $i = 0; + while (false !== ($pos = strpos($where, '?'))) { + $where = substr_replace($where, $adapter->quote($args[$i]), $pos); + ++$i; + } + if (('(' != $where[0]) || (')' != $where[strlen($where) - 1])) { + $where = '(' . $where . ')'; + } + return $where; + } + } diff --git a/library/Zend/Cloud/DocumentService/Adapter/WindowsAzure.php b/library/Zend/Cloud/DocumentService/Adapter/WindowsAzure.php new file mode 100644 index 0000000..e812f89 --- /dev/null +++ b/library/Zend/Cloud/DocumentService/Adapter/WindowsAzure.php @@ -0,0 +1,628 @@ +toArray(); + } + + if (empty($options)) { + $options = array(); + } + + if (!is_array($options)) { + throw new Zend_Cloud_DocumentService_Exception('Invalid options provided'); + } + + if (isset($options[self::DOCUMENT_CLASS])) { + $this->setDocumentClass($options[self::DOCUMENT_CLASS]); + } + + if (isset($options[self::DOCUMENTSET_CLASS])) { + $this->setDocumentSetClass($options[self::DOCUMENTSET_CLASS]); + } + + if (isset($options[self::QUERY_CLASS])) { + $this->setQueryClass($options[self::QUERY_CLASS]); + } + + // Build Zend_Service_WindowsAzure_Storage_Blob instance + if (!isset($options[self::HOST])) { + $host = self::DEFAULT_HOST; + } else { + $host = $options[self::HOST]; + } + + if (! isset($options[self::ACCOUNT_NAME])) { + throw new Zend_Cloud_DocumentService_Exception('No Windows Azure account name provided.'); + } + + if (! isset($options[self::ACCOUNT_KEY])) { + throw new Zend_Cloud_DocumentService_Exception('No Windows Azure account key provided.'); + } + + // TODO: support $usePathStyleUri and $retryPolicy + try { + $this->_storageClient = new Zend_Service_WindowsAzure_Storage_Table( + $host, $options[self::ACCOUNT_NAME], $options[self::ACCOUNT_KEY]); + // Parse other options + if (! empty($options[self::PROXY_HOST])) { + $proxyHost = $options[self::PROXY_HOST]; + $proxyPort = isset($options[self::PROXY_PORT]) ? $options[self::PROXY_PORT] : 8080; + $proxyCredentials = isset($options[self::PROXY_CREDENTIALS]) ? $options[self::PROXY_CREDENTIALS] : ''; + $this->_storageClient->setProxy(true, $proxyHost, $proxyPort, $proxyCredentials); + } + if (isset($options[self::HTTP_ADAPTER])) { + $this->_storageClient->setHttpClientChannel($options[self::HTTP_ADAPTER]); + } + } catch(Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_DocumentService_Exception('Error on document service creation: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Set the default partition key + * + * @param string $key + * @return Zend_Cloud_DocumentService_Adapter_WindowsAzure + */ + public function setDefaultPartitionKey($key) + { + $this->_validateKey($key); + $this->_defaultPartitionKey = $key; + return $this; + } + + /** + * Retrieve default partition key + * + * @return null|string + */ + public function getDefaultPartitionKey() + { + return $this->_defaultPartitionKey; + } + + /** + * Create collection. + * + * @param string $name + * @param array $options + * @return boolean + */ + public function createCollection($name, $options = null) + { + if (!preg_match('/^[A-Za-z][A-Za-z0-9]{2,}$/', $name)) { + throw new Zend_Cloud_DocumentService_Exception('Invalid collection name; Windows Azure collection names must consist of alphanumeric characters only, and be at least 3 characters long'); + } + try { + $this->_storageClient->createTable($name); + } catch(Zend_Service_WindowsAzure_Exception $e) { + if (strpos($e->getMessage(), "table specified already exists") === false) { + throw new Zend_Cloud_DocumentService_Exception('Error on collection creation: '.$e->getMessage(), $e->getCode(), $e); + } + } + return true; + } + + /** + * Delete collection. + * + * @param string $name + * @param array $options + * @return boolean + */ + public function deleteCollection($name, $options = null) + { + try { + $this->_storageClient->deleteTable($name); + } catch(Zend_Service_WindowsAzure_Exception $e) { + if (strpos($e->getMessage(), "does not exist") === false) { + throw new Zend_Cloud_DocumentService_Exception('Error on collection deletion: '.$e->getMessage(), $e->getCode(), $e); + } + } + return true; + } + + /** + * List collections. + * + * @param array $options + * @return array + */ + public function listCollections($options = null) + { + try { + $tables = $this->_storageClient->listTables(); + $restables = array(); + foreach ($tables as $table) { + $restables[] = $table->name; + } + return $restables; + } catch(Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_DocumentService_Exception('Error on collection list: '.$e->getMessage(), $e->getCode(), $e); + } + + return $tables; + } + + /** + * Create suitable document from array of fields + * + * @param array $document + * @param null|string $collectionName Collection to which this document belongs + * @return Zend_Cloud_DocumentService_Document + */ + protected function _getDocumentFromArray($document, $collectionName = null) + { + $key = null; + if (!isset($document[Zend_Cloud_DocumentService_Document::KEY_FIELD])) { + if (isset($document[self::ROW_KEY])) { + $rowKey = $document[self::ROW_KEY]; + unset($document[self::ROW_KEY]); + if (isset($document[self::PARTITION_KEY])) { + $key = array($document[self::PARTITION_KEY], $rowKey); + unset($document[self::PARTITION_KEY]); + } elseif (null !== ($partitionKey = $this->getDefaultPartitionKey())) { + $key = array($partitionKey, $rowKey); + } elseif (null !== $collectionName) { + $key = array($collectionName, $rowKey); + } + } + } else { + $key = $document[Zend_Cloud_DocumentService_Document::KEY_FIELD]; + unset($document[Zend_Cloud_DocumentService_Document::KEY_FIELD]); + } + + $documentClass = $this->getDocumentClass(); + return new $documentClass($document, $key); + } + + /** + * List all documents in a collection + * + * @param string $collectionName + * @param null|array $options + * @return Zend_Cloud_DocumentService_DocumentSet + */ + public function listDocuments($collectionName, array $options = null) + { + $select = $this->select()->from($collectionName); + return $this->query($collectionName, $select); + } + + /** + * Insert document + * + * @param array|Zend_Cloud_DocumentService_Document $document + * @param array $options + * @return boolean + */ + public function insertDocument($collectionName, $document, $options = null) + { + if (is_array($document)) { + $document = $this->_getDocumentFromArray($document, $collectionName); + } + + if (!$document instanceof Zend_Cloud_DocumentService_Document) { + throw new Zend_Cloud_DocumentService_Exception('Invalid document supplied'); + } + + $key = $this->_validateDocumentId($document->getId(), $collectionName); + $document->setId($key); + + $this->_validateCompositeKey($key); + $this->_validateFields($document); + try { + + $entity = new Zend_Service_WindowsAzure_Storage_DynamicTableEntity($key[0], $key[1]); + $entity->setAzureValues($document->getFields(), true); + $this->_storageClient->insertEntity($collectionName, $entity); + } catch(Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_DocumentService_Exception('Error on document insertion: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Replace document. + * + * The new document replaces the existing document. + * + * @param Zend_Cloud_DocumentService_Document $document + * @param array $options + * @return boolean + */ + public function replaceDocument($collectionName, $document, $options = null) + { + if (is_array($document)) { + $document = $this->_getDocumentFromArray($document, $collectionName); + } + + if (!$document instanceof Zend_Cloud_DocumentService_Document) { + throw new Zend_Cloud_DocumentService_Exception('Invalid document supplied'); + } + + $key = $this->_validateDocumentId($document->getId(), $collectionName); + $this->_validateFields($document); + try { + $entity = new Zend_Service_WindowsAzure_Storage_DynamicTableEntity($key[0], $key[1]); + $entity->setAzureValues($document->getFields(), true); + if (isset($options[self::VERIFY_ETAG])) { + $entity->setEtag($options[self::VERIFY_ETAG]); + } + + $this->_storageClient->updateEntity($collectionName, $entity, isset($options[self::VERIFY_ETAG])); + } catch(Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_DocumentService_Exception('Error on document replace: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Update document. + * + * The new document is merged the existing document. + * + * @param string $collectionName + * @param mixed|Zend_Cloud_DocumentService_Document $documentId Document identifier or document contaiing updates + * @param null|array|Zend_Cloud_DocumentService_Document Fields to update (or new fields)) + * @param array $options + * @return boolean + */ + public function updateDocument($collectionName, $documentId, $fieldset = null, $options = null) + { + if (null === $fieldset && $documentId instanceof Zend_Cloud_DocumentService_Document) { + $fieldset = $documentId->getFields(); + $documentId = $documentId->getId(); + } elseif ($fieldset instanceof Zend_Cloud_DocumentService_Document) { + if ($documentId == null) { + $documentId = $fieldset->getId(); + } + $fieldset = $fieldset->getFields(); + } + + $this->_validateCompositeKey($documentId, $collectionName); + $this->_validateFields($fieldset); + try { + $entity = new Zend_Service_WindowsAzure_Storage_DynamicTableEntity($documentId[0], $documentId[1]); + + // Ensure timestamp is set correctly + if (isset($fieldset[self::TIMESTAMP_KEY])) { + $entity->setTimestamp($fieldset[self::TIMESTAMP_KEY]); + unset($fieldset[self::TIMESTAMP_KEY]); + } + + $entity->setAzureValues($fieldset, true); + if (isset($options[self::VERIFY_ETAG])) { + $entity->setEtag($options[self::VERIFY_ETAG]); + } + + $this->_storageClient->mergeEntity($collectionName, $entity, isset($options[self::VERIFY_ETAG])); + } catch(Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_DocumentService_Exception('Error on document update: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Delete document. + * + * @param mixed $document Document ID or Document object. + * @param array $options + * @return void + */ + public function deleteDocument($collectionName, $documentId, $options = null) + { + if ($documentId instanceof Zend_Cloud_DocumentService_Document) { + $documentId = $documentId->getId(); + } + + $documentId = $this->_validateDocumentId($documentId, $collectionName); + + try { + $entity = new Zend_Service_WindowsAzure_Storage_DynamicTableEntity($documentId[0], $documentId[1]); + if (isset($options[self::VERIFY_ETAG])) { + $entity->setEtag($options[self::VERIFY_ETAG]); + } + $this->_storageClient->deleteEntity($collectionName, $entity, isset($options[self::VERIFY_ETAG])); + } catch(Zend_Service_WindowsAzure_Exception $e) { + if (strpos($e->getMessage(), "does not exist") === false) { + throw new Zend_Cloud_DocumentService_Exception('Error on document deletion: '.$e->getMessage(), $e->getCode(), $e); + } + } + } + + /** + * Fetch single document by ID + * + * @param string $collectionName Collection name + * @param mixed $documentId Document ID, adapter-dependent + * @param array $options + * @return Zend_Cloud_DocumentService_Document + */ + public function fetchDocument($collectionName, $documentId, $options = null) + { + $documentId = $this->_validateDocumentId($documentId, $collectionName); + try { + $entity = $this->_storageClient->retrieveEntityById($collectionName, $documentId[0], $documentId[1]); + $documentClass = $this->getDocumentClass(); + return new $documentClass($this->_resolveAttributes($entity), array($entity->getPartitionKey(), $entity->getRowKey())); + } catch (Zend_Service_WindowsAzure_Exception $e) { + if (strpos($e->getMessage(), "does not exist") !== false) { + return false; + } + throw new Zend_Cloud_DocumentService_Exception('Error on document fetch: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Query for documents stored in the document service. If a string is passed in + * $query, the query string will be passed directly to the service. + * + * @param string $collectionName Collection name + * @param string|Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query $query + * @param array $options + * @return array Zend_Cloud_DocumentService_DocumentSet + */ + public function query($collectionName, $query, $options = null) + { + try { + if ($query instanceof Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query) { + $entities = $this->_storageClient->retrieveEntities($query->assemble()); + } else { + $entities = $this->_storageClient->retrieveEntities($collectionName, $query); + } + + $documentClass = $this->getDocumentClass(); + $resultSet = array(); + foreach ($entities as $entity) { + $resultSet[] = new $documentClass( + $this->_resolveAttributes($entity), + array($entity->getPartitionKey(), $entity->getRowKey()) + ); + } + } catch(Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_DocumentService_Exception('Error on document query: '.$e->getMessage(), $e->getCode(), $e); + } + + $setClass = $this->getDocumentSetClass(); + return new $setClass($resultSet); + } + + /** + * Create query statement + * + * @return Zend_Cloud_DocumentService_Query + */ + public function select($fields = null) + { + $queryClass = $this->getQueryClass(); + if (!class_exists($queryClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($queryClass); + } + + $query = new $queryClass(); + $defaultClass = self::DEFAULT_QUERY_CLASS; + if (!$query instanceof $defaultClass) { + throw new Zend_Cloud_DocumentService_Exception('Query class must extend ' . self::DEFAULT_QUERY_CLASS); + } + + $query->select($fields); + return $query; + } + + /** + * Get the concrete service client + * + * @return Zend_Service_WindowsAzure_Storage_Table + */ + public function getClient() + { + return $this->_storageClient; + } + + /** + * Resolve table values to attributes + * + * @param Zend_Service_WindowsAzure_Storage_TableEntity $entity + * @return array + */ + protected function _resolveAttributes(Zend_Service_WindowsAzure_Storage_TableEntity $entity) + { + $result = array(); + foreach ($entity->getAzureValues() as $attr) { + $result[$attr->Name] = $attr->Value; + } + return $result; + } + + + /** + * Validate a partition or row key + * + * @param string $key + * @return void + * @throws Zend_Cloud_DocumentService_Exception + */ + protected function _validateKey($key) + { + if (preg_match('@[/#?' . preg_quote('\\') . ']@', $key)) { + throw new Zend_Cloud_DocumentService_Exception('Invalid partition or row key provided; must not contain /, \\, #, or ? characters'); + } + } + + /** + * Validate a composite key + * + * @param array $key + * @return throws Zend_Cloud_DocumentService_Exception + */ + protected function _validateCompositeKey(array $key) + { + if (2 != count($key)) { + throw new Zend_Cloud_DocumentService_Exception('Invalid document key provided; must contain exactly two elements: a PartitionKey and a RowKey'); + } + foreach ($key as $k) { + $this->_validateKey($k); + } + } + + /** + * Validate a document identifier + * + * If the identifier is an array containing a valid partition and row key, + * returns it. If the identifier is a string: + * - if a default partition key is present, it creates an identifier using + * that and the provided document ID + * - if a collection name is provided, it will use that for the partition key + * - otherwise, it's invalid + * + * @param array|string $documentId + * @param null|string $collectionName + * @return array + * @throws Zend_Cloud_DocumentService_Exception + */ + protected function _validateDocumentId($documentId, $collectionName = false) + { + if (is_array($documentId)) { + $this->_validateCompositeKey($documentId); + return $documentId; + } + if (!is_string($documentId)) { + throw new Zend_Cloud_DocumentService_Exception('Invalid document identifier; must be a string or an array'); + } + + $this->_validateKey($documentId); + + if (null !== ($partitionKey = $this->getDefaultPartitionKey())) { + return array($partitionKey, $documentId); + } + if (null !== $collectionName) { + return array($collectionName, $documentId); + } + throw new Zend_Cloud_DocumentService_Exception('Cannot determine partition name; invalid document identifier'); + } + + /** + * Validate a document's fields for well-formedness + * + * Since Azure uses Atom, and fieldnames are included as part of XML + * element tag names, the field names must be valid XML names. + * + * @param Zend_Cloud_DocumentService_Document|array $document + * @return void + * @throws Zend_Cloud_DocumentService_Exception + */ + public function _validateFields($document) + { + if ($document instanceof Zend_Cloud_DocumentService_Document) { + $document = $document->getFields(); + } elseif (!is_array($document)) { + throw new Zend_Cloud_DocumentService_Exception('Cannot inspect fields; invalid type provided'); + } + + foreach (array_keys($document) as $key) { + $this->_validateFieldKey($key); + } + } + + /** + * Validate an individual field name for well-formedness + * + * Since Azure uses Atom, and fieldnames are included as part of XML + * element tag names, the field names must be valid XML names. + * + * While we could potentially normalize names, this could also lead to + * conflict with other field names -- which we should avoid. As such, + * invalid field names will raise an exception. + * + * @param string $key + * @return void + * @throws Zend_Cloud_DocumentService_Exception + */ + public function _validateFieldKey($key) + { + if (!preg_match('/^[_A-Za-z][-._A-Za-z0-9]*$/', $key)) { + throw new Zend_Cloud_DocumentService_Exception('Field keys must conform to XML names (^[_A-Za-z][-._A-Za-z0-9]*$); key "' . $key . '" does not match'); + } + } +} diff --git a/library/Zend/Cloud/DocumentService/Adapter/WindowsAzure/Query.php b/library/Zend/Cloud/DocumentService/Adapter/WindowsAzure/Query.php new file mode 100644 index 0000000..c6fe8c4 --- /dev/null +++ b/library/Zend/Cloud/DocumentService/Adapter/WindowsAzure/Query.php @@ -0,0 +1,171 @@ +_azureSelect = $select; + } + + /** + * SELECT clause (fields to be selected) + * + * Does nothing for Azure. + * + * @param string $select + * @return Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query + */ + public function select($select) + { + return $this; + } + + /** + * FROM clause (table name) + * + * @param string $from + * @return Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query + */ + public function from($from) + { + $this->_azureSelect->from($from); + return $this; + } + + /** + * WHERE clause (conditions to be used) + * + * @param string $where + * @param mixed $value Value or array of values to be inserted instead of ? + * @param string $op Operation to use to join where clauses (AND/OR) + * @return Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query + */ + public function where($where, $value = null, $op = 'and') + { + if (!empty($value) && !is_array($value)) { + // fix buglet in Azure - numeric values are quoted unless passed as an array + $value = array($value); + } + $this->_azureSelect->where($where, $value, $op); + return $this; + } + + /** + * WHERE clause for item ID + * + * This one should be used when fetching specific rows since some adapters + * have special syntax for primary keys + * + * @param array $value Row ID for the document (PartitionKey, RowKey) + * @return Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query + */ + public function whereId($value) + { + if (!is_array($value)) { + require_once 'Zend/Cloud/DocumentService/Exception.php'; + throw new Zend_Cloud_DocumentService_Exception('Invalid document key'); + } + $this->_azureSelect->wherePartitionKey($value[0])->whereRowKey($value[1]); + return $this; + } + + /** + * LIMIT clause (how many rows to return) + * + * @param int $limit + * @return Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query + */ + public function limit($limit) + { + $this->_azureSelect->top($limit); + return $this; + } + + /** + * ORDER BY clause (sorting) + * + * @todo Azure service doesn't seem to support this yet; emulate? + * @param string $sort Column to sort by + * @param string $direction Direction - asc/desc + * @return Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query + * @throws Zend_Cloud_OperationNotAvailableException + */ + public function order($sort, $direction = 'asc') + { + require_once 'Zend/Cloud/OperationNotAvailableException.php'; + throw new Zend_Cloud_OperationNotAvailableException('No support for sorting for Azure yet'); + } + + /** + * Get Azure select query + * + * @return Zend_Service_WindowsAzure_Storage_TableEntityQuery + */ + public function getAzureSelect() + { + return $this->_azureSelect; + } + + /** + * Assemble query + * + * Simply return the WindowsAzure table entity query object + * + * @return Zend_Service_WindowsAzure_Storage_TableEntityQuery + */ + public function assemble() + { + return $this->getAzureSelect(); + } +} diff --git a/library/Zend/Cloud/DocumentService/Document.php b/library/Zend/Cloud/DocumentService/Document.php new file mode 100644 index 0000000..9b8c5e5 --- /dev/null +++ b/library/Zend/Cloud/DocumentService/Document.php @@ -0,0 +1,248 @@ +_fields = $fields; + $this->setId($id); + } + + /** + * Set document identifier + * + * @param mixed $id + * @return Zend_Cloud_DocumentService_Document + */ + public function setId($id) + { + $this->_id = $id; + return $this; + } + + /** + * Get ID name. + * + * @return string + */ + public function getId() + { + return $this->_id; + } + + /** + * Get fields as array. + * + * @return array + */ + public function getFields() + { + return $this->_fields; + } + + /** + * Get field by name. + * + * @param string $name + * @return mixed + */ + public function getField($name) + { + if (isset($this->_fields[$name])) { + return $this->_fields[$name]; + } + return null; + } + + /** + * Set field by name. + * + * @param string $name + * @param mixed $value + * @return Zend_Cloud_DocumentService_Document + */ + public function setField($name, $value) + { + $this->_fields[$name] = $value; + return $this; + } + + /** + * Overloading: get value + * + * @param string $name + * @return mixed + */ + public function __get($name) + { + return $this->getField($name); + } + + /** + * Overloading: set field + * + * @param string $name + * @param mixed $value + * @return void + */ + public function __set($name, $value) + { + $this->setField($name, $value); + } + + /** + * ArrayAccess: does field exist? + * + * @param string $name + * @return bool + */ + public function offsetExists($name) + { + return isset($this->_fields[$name]); + } + + /** + * ArrayAccess: get field by name + * + * @param string $name + * @return mixed + */ + public function offsetGet($name) + { + return $this->getField($name); + } + + /** + * ArrayAccess: set field to value + * + * @param string $name + * @param mixed $value + * @return void + */ + public function offsetSet($name, $value) + { + $this->setField($name, $value); + } + + /** + * ArrayAccess: remove field from document + * + * @param string $name + * @return void + */ + public function offsetUnset($name) + { + if ($this->offsetExists($name)) { + unset($this->_fields[$name]); + } + } + + /** + * Overloading: retrieve and set fields by name + * + * @param string $name + * @param mixed $args + * @return mixed + */ + public function __call($name, $args) + { + $prefix = substr($name, 0, 3); + if ($prefix == 'get') { + // Get value + $option = substr($name, 3); + return $this->getField($option); + } elseif ($prefix == 'set') { + // set value + $option = substr($name, 3); + return $this->setField($option, $args[0]); + } + + require_once 'Zend/Cloud/OperationNotAvailableException.php'; + throw new Zend_Cloud_OperationNotAvailableException("Unknown operation $name"); + } + + /** + * Countable: return count of fields in document + * + * @return int + */ + public function count() + { + return count($this->_fields); + } + + /** + * IteratorAggregate: return iterator for iterating over fields + * + * @return Iterator + */ + public function getIterator() + { + return new ArrayIterator($this->_fields); + } +} diff --git a/library/Zend/Cloud/DocumentService/DocumentSet.php b/library/Zend/Cloud/DocumentService/DocumentSet.php new file mode 100644 index 0000000..156b2dd --- /dev/null +++ b/library/Zend/Cloud/DocumentService/DocumentSet.php @@ -0,0 +1,68 @@ +_documentCount = count($documents); + $this->_documents = new ArrayIterator($documents); + } + + /** + * Countable: number of documents in set + * + * @return int + */ + public function count() + { + return $this->_documentCount; + } + + /** + * IteratorAggregate: retrieve iterator + * + * @return Traversable + */ + public function getIterator() + { + return $this->_documents; + } +} diff --git a/library/Zend/Cloud/DocumentService/Exception.php b/library/Zend/Cloud/DocumentService/Exception.php new file mode 100644 index 0000000..0c5b173 --- /dev/null +++ b/library/Zend/Cloud/DocumentService/Exception.php @@ -0,0 +1,38 @@ +foo('bar') + * but concrete adapters should be able to recognise it + * + * The call will be iterpreted as clause 'foo' with argument 'bar' + * + * @param string $name Clause/method name + * @param mixed $args + * @return Zend_Cloud_DocumentService_Query + */ + public function __call($name, $args) + { + $this->_clauses[] = array(strtolower($name), $args); + return $this; + } + + /** + * SELECT clause (fields to be selected) + * + * @param null|string|array $select + * @return Zend_Cloud_DocumentService_Query + */ + public function select($select) + { + if (empty($select)) { + return $this; + } + if (!is_string($select) && !is_array($select)) { + require_once 'Zend/Cloud/DocumentService/Exception.php'; + throw new Zend_Cloud_DocumentService_Exception("SELECT argument must be a string or an array of strings"); + } + $this->_clauses[] = array(self::QUERY_SELECT, $select); + return $this; + } + + /** + * FROM clause + * + * @param string $name Field names + * @return Zend_Cloud_DocumentService_Query + */ + public function from($name) + { + if(!is_string($name)) { + require_once 'Zend/Cloud/DocumentService/Exception.php'; + throw new Zend_Cloud_DocumentService_Exception("FROM argument must be a string"); + } + $this->_clauses[] = array(self::QUERY_FROM, $name); + return $this; + } + + /** + * WHERE query + * + * @param string $cond Condition + * @param array $args Arguments to substitute instead of ?'s in condition + * @param string $op relation to other clauses - and/or + * @return Zend_Cloud_DocumentService_Query + */ + public function where($cond, $value = null, $op = 'and') + { + if (!is_string($cond)) { + require_once 'Zend/Cloud/DocumentService/Exception.php'; + throw new Zend_Cloud_DocumentService_Exception("WHERE argument must be a string"); + } + $this->_clauses[] = array(self::QUERY_WHERE, array($cond, $value, $op)); + return $this; + } + + /** + * Select record or fields by ID + * + * @param string|int $value Identifier to select by + * @return Zend_Cloud_DocumentService_Query + */ + public function whereId($value) + { + if (!is_scalar($value)) { + require_once 'Zend/Cloud/DocumentService/Exception.php'; + throw new Zend_Cloud_DocumentService_Exception("WHEREID argument must be a scalar"); + } + $this->_clauses[] = array(self::QUERY_WHEREID, $value); + return $this; + } + + /** + * LIMIT clause (how many items to return) + * + * @param int $limit + * @return Zend_Cloud_DocumentService_Query + */ + public function limit($limit) + { + if ($limit != (int) $limit) { + require_once 'Zend/Cloud/DocumentService/Exception.php'; + throw new Zend_Cloud_DocumentService_Exception("LIMIT argument must be an integer"); + } + $this->_clauses[] = array(self::QUERY_LIMIT, $limit); + return $this; + } + + /** + * ORDER clause; field or fields to sort by, and direction to sort + * + * @param string|int|array $sort + * @param string $direction + * @return Zend_Cloud_DocumentService_Query + */ + public function order($sort, $direction = 'asc') + { + $this->_clauses[] = array(self::QUERY_ORDER, array($sort, $direction)); + return $this; + } + + /** + * "Assemble" the query + * + * Simply returns the clauses present. + * + * @return array + */ + public function assemble() + { + return $this->getClauses(); + } + + /** + * Return query clauses as an array + * + * @return array Clauses in the query + */ + public function getClauses() + { + return $this->_clauses; + } +} diff --git a/library/Zend/Cloud/DocumentService/QueryAdapter.php b/library/Zend/Cloud/DocumentService/QueryAdapter.php new file mode 100644 index 0000000..cfb1c4f --- /dev/null +++ b/library/Zend/Cloud/DocumentService/QueryAdapter.php @@ -0,0 +1,102 @@ +_clientException = $clientException; + parent::__construct($message, $code, $clientException); + } + + public function getClientException() { + return $this->_getPrevious(); + } +} + diff --git a/library/Zend/Cloud/Infrastructure/Adapter.php b/library/Zend/Cloud/Infrastructure/Adapter.php new file mode 100644 index 0000000..b23c718 --- /dev/null +++ b/library/Zend/Cloud/Infrastructure/Adapter.php @@ -0,0 +1,167 @@ +adapterResult; + } + + /** + * Wait for status $status with a timeout of $timeout seconds + * + * @param string $id + * @param string $status + * @param integer $timeout + * @return boolean + */ + public function waitStatusInstance($id, $status, $timeout = self::TIMEOUT_STATUS_CHANGE) + { + if (empty($id) || empty($status)) { + return false; + } + + $num = 0; + while (($num<$timeout) && ($this->statusInstance($id) != $status)) { + sleep(self::TIME_STEP_STATUS_CHANGE); + $num += self::TIME_STEP_STATUS_CHANGE; + } + return ($num < $timeout); + } + + /** + * Run arbitrary shell script on an instance + * + * @param string $id + * @param array $param + * @param string|array $cmd + * @return string|array + */ + public function deployInstance($id, $params, $cmd) + { + if (!function_exists("ssh2_connect")) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('Deployment requires the PHP "SSH" extension (ext/ssh2)'); + } + + if (empty($id)) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('You must specify the instance where to deploy'); + } + + if (empty($cmd)) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('You must specify the shell commands to run on the instance'); + } + + if (empty($params) + || empty($params[Zend_Cloud_Infrastructure_Instance::SSH_USERNAME]) + || (empty($params[Zend_Cloud_Infrastructure_Instance::SSH_PASSWORD]) + && empty($params[Zend_Cloud_Infrastructure_Instance::SSH_KEY])) + ) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('You must specify the params for the SSH connection'); + } + + $host = $this->publicDnsInstance($id); + if (empty($host)) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception(sprintf( + 'The instance identified by "%s" does not exist', + $id + )); + } + + $conn = ssh2_connect($host); + if (!ssh2_auth_password($conn, $params[Zend_Cloud_Infrastructure_Instance::SSH_USERNAME], + $params[Zend_Cloud_Infrastructure_Instance::SSH_PASSWORD])) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('SSH authentication failed'); + } + + if (is_array($cmd)) { + $result = array(); + foreach ($cmd as $command) { + $stream = ssh2_exec($conn, $command); + $errorStream = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR); + + stream_set_blocking($errorStream, true); + stream_set_blocking($stream, true); + + $output = stream_get_contents($stream); + $error = stream_get_contents($errorStream); + + if (empty($error)) { + $result[$command] = $output; + } else { + $result[$command] = $error; + } + } + } else { + $stream = ssh2_exec($conn, $cmd); + $result = stream_set_blocking($stream, true); + $errorStream = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR); + + stream_set_blocking($errorStream, true); + stream_set_blocking($stream, true); + + $output = stream_get_contents($stream); + $error = stream_get_contents($errorStream); + + if (empty($error)) { + $result = $output; + } else { + $result = $error; + } + } + return $result; + } +} diff --git a/library/Zend/Cloud/Infrastructure/Adapter/Ec2.php b/library/Zend/Cloud/Infrastructure/Adapter/Ec2.php new file mode 100644 index 0000000..694bc11 --- /dev/null +++ b/library/Zend/Cloud/Infrastructure/Adapter/Ec2.php @@ -0,0 +1,496 @@ + Zend_Cloud_Infrastructure_Instance::STATUS_RUNNING, + 'terminated' => Zend_Cloud_Infrastructure_Instance::STATUS_TERMINATED, + 'pending' => Zend_Cloud_Infrastructure_Instance::STATUS_PENDING, + 'shutting-down' => Zend_Cloud_Infrastructure_Instance::STATUS_SHUTTING_DOWN, + 'stopping' => Zend_Cloud_Infrastructure_Instance::STATUS_PENDING, + 'stopped' => Zend_Cloud_Infrastructure_Instance::STATUS_STOPPED, + 'rebooting' => Zend_Cloud_Infrastructure_Instance::STATUS_REBOOTING, + ); + + /** + * Map monitor metrics between Infrastructure and EC2 + * + * @var array + */ + protected $mapMetrics= array ( + Zend_Cloud_Infrastructure_Instance::MONITOR_CPU => 'CPUUtilization', + Zend_Cloud_Infrastructure_Instance::MONITOR_DISK_READ => 'DiskReadBytes', + Zend_Cloud_Infrastructure_Instance::MONITOR_DISK_WRITE => 'DiskWriteBytes', + Zend_Cloud_Infrastructure_Instance::MONITOR_NETWORK_IN => 'NetworkIn', + Zend_Cloud_Infrastructure_Instance::MONITOR_NETWORK_OUT => 'NetworkOut', + ); + + /** + * Constructor + * + * @param array|Zend_Config $options + * @return void + */ + public function __construct($options = array()) + { + if (is_object($options)) { + if (method_exists($options, 'toArray')) { + $options= $options->toArray(); + } elseif ($options instanceof Traversable) { + $options = iterator_to_array($options); + } + } + + if (empty($options) || !is_array($options)) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('Invalid options provided'); + } + + if (!isset($options[self::AWS_ACCESS_KEY]) + || !isset($options[self::AWS_SECRET_KEY]) + ) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('AWS keys not specified!'); + } + + $this->accessKey = $options[self::AWS_ACCESS_KEY]; + $this->accessSecret = $options[self::AWS_SECRET_KEY]; + $this->region = ''; + + if (isset($options[self::AWS_REGION])) { + $this->region= $options[self::AWS_REGION]; + } + + try { + $this->ec2 = new Zend_Service_Amazon_Ec2_Instance($options[self::AWS_ACCESS_KEY], $options[self::AWS_SECRET_KEY], $this->region); + } catch (Exception $e) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('Error on create: ' . $e->getMessage(), $e->getCode(), $e); + } + + if (isset($options[self::HTTP_ADAPTER])) { + $this->ec2->getHttpClient()->setAdapter($options[self::HTTP_ADAPTER]); + } + } + + /** + * Convert the attributes of EC2 into attributes of Infrastructure + * + * @param array $attr + * @return array|boolean + */ + private function convertAttributes($attr) + { + $result = array(); + if (!empty($attr) && is_array($attr)) { + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_ID] = $attr['instanceId']; + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_STATUS] = $this->mapStatus[$attr['instanceState']['name']]; + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_IMAGEID] = $attr['imageId']; + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_ZONE] = $attr['availabilityZone']; + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_LAUNCHTIME] = $attr['launchTime']; + + switch ($attr['instanceType']) { + case Zend_Service_Amazon_Ec2_Instance::MICRO: + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_CPU] = '1 virtual core'; + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_RAM] = '613MB'; + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_STORAGE] = '0GB'; + break; + case Zend_Service_Amazon_Ec2_Instance::SMALL: + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_CPU] = '1 virtual core'; + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_RAM] = '1.7GB'; + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_STORAGE] = '160GB'; + break; + case Zend_Service_Amazon_Ec2_Instance::LARGE: + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_CPU] = '2 virtual core'; + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_RAM] = '7.5GB'; + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_STORAGE] = '850GB'; + break; + case Zend_Service_Amazon_Ec2_Instance::XLARGE: + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_CPU] = '4 virtual core'; + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_RAM] = '15GB'; + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_STORAGE] = '1690GB'; + break; + case Zend_Service_Amazon_Ec2_Instance::HCPU_MEDIUM: + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_CPU] = '2 virtual core'; + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_RAM] = '1.7GB'; + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_STORAGE] = '350GB'; + break; + case Zend_Service_Amazon_Ec2_Instance::HCPU_XLARGE: + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_CPU] = '8 virtual core'; + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_RAM] = '7GB'; + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_STORAGE] = '1690GB'; + break; + } + } + return $result; + } + + /** + * Return a list of the available instancies + * + * @return Zend_Cloud_Infrastructure_InstanceList + */ + public function listInstances() + { + $this->adapterResult = $this->ec2->describe(); + + $result = array(); + foreach ($this->adapterResult['instances'] as $instance) { + $result[]= $this->convertAttributes($instance); + } + return new Zend_Cloud_Infrastructure_InstanceList($this, $result); + } + + /** + * Return the status of an instance + * + * @param string + * @return string|boolean + */ + public function statusInstance($id) + { + $this->adapterResult = $this->ec2->describe($id); + if (empty($this->adapterResult['instances'])) { + return false; + } + $result = $this->adapterResult['instances'][0]; + return $this->mapStatus[$result['instanceState']['name']]; + } + + /** + * Return the public DNS name of the instance + * + * @param string $id + * @return string|boolean + */ + public function publicDnsInstance($id) + { + $this->adapterResult = $this->ec2->describe($id); + if (empty($this->adapterResult['instances'])) { + return false; + } + $result = $this->adapterResult['instances'][0]; + return $result['dnsName']; + } + + /** + * Reboot an instance + * + * @param string $id + * @return boolean + */ + public function rebootInstance($id) + { + $this->adapterResult= $this->ec2->reboot($id); + return $this->adapterResult; + } + + /** + * Create a new instance + * + * @param string $name + * @param array $options + * @return Instance|boolean + */ + public function createInstance($name, $options) + { + // @todo instance's name management? + $this->adapterResult = $this->ec2->run($options); + if (empty($this->adapterResult['instances'])) { + return false; + } + $this->error= false; + return new Zend_Cloud_Infrastructure_Instance($this, $this->convertAttributes($this->adapterResult['instances'][0])); + } + + /** + * Stop an instance + * + * @param string $id + * @return boolean + */ + public function stopInstance($id) + { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('The stopInstance method is not implemented in the adapter'); + } + + /** + * Start an instance + * + * @param string $id + * @return boolean + */ + public function startInstance($id) + { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('The startInstance method is not implemented in the adapter'); + } + + /** + * Destroy an instance + * + * @param string $id + * @return boolean + */ + public function destroyInstance($id) + { + $this->adapterResult = $this->ec2->terminate($id); + return (!empty($this->adapterResult)); + } + + /** + * Return a list of all the available instance images + * + * @return ImageList + */ + public function imagesInstance() + { + if (!isset($this->ec2Image)) { + $this->ec2Image = new Zend_Service_Amazon_Ec2_Image($this->accessKey, $this->accessSecret, $this->region); + } + + $this->adapterResult = $this->ec2Image->describe(); + + $images = array(); + + foreach ($this->adapterResult as $result) { + switch (strtolower($result['platform'])) { + case 'windows' : + $platform = Zend_Cloud_Infrastructure_Image::IMAGE_WINDOWS; + break; + default: + $platform = Zend_Cloud_Infrastructure_Image::IMAGE_LINUX; + break; + } + + $images[]= array ( + Zend_Cloud_Infrastructure_Image::IMAGE_ID => $result['imageId'], + Zend_Cloud_Infrastructure_Image::IMAGE_NAME => '', + Zend_Cloud_Infrastructure_Image::IMAGE_DESCRIPTION => $result['imageLocation'], + Zend_Cloud_Infrastructure_Image::IMAGE_OWNERID => $result['imageOwnerId'], + Zend_Cloud_Infrastructure_Image::IMAGE_ARCHITECTURE => $result['architecture'], + Zend_Cloud_Infrastructure_Image::IMAGE_PLATFORM => $platform, + ); + } + return new Zend_Cloud_Infrastructure_ImageList($images,$this->ec2Image); + } + + /** + * Return all the available zones + * + * @return array + */ + public function zonesInstance() + { + if (!isset($this->ec2Zone)) { + $this->ec2Zone = new Zend_Service_Amazon_Ec2_AvailabilityZones($this->accessKey,$this->accessSecret,$this->region); + } + $this->adapterResult = $this->ec2Zone->describe(); + + $zones = array(); + foreach ($this->adapterResult as $zone) { + if (strtolower($zone['zoneState']) === 'available') { + $zones[] = array ( + Zend_Cloud_Infrastructure_Instance::INSTANCE_ZONE => $zone['zoneName'], + ); + } + } + return $zones; + } + + /** + * Return the system information about the $metric of an instance + * + * @param string $id + * @param string $metric + * @param null|array $options + * @return array + */ + public function monitorInstance($id, $metric, $options = null) + { + if (empty($id) || empty($metric)) { + return false; + } + + if (!in_array($metric,$this->validMetrics)) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception(sprintf( + 'The metric "%s" is not valid', + $metric + )); + } + + if (!empty($options) && !is_array($options)) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('The options must be an array'); + } + + if (!empty($options) + && (empty($options[Zend_Cloud_Infrastructure_Instance::MONITOR_START_TIME]) + || empty($options[Zend_Cloud_Infrastructure_Instance::MONITOR_END_TIME])) + ) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception(sprintf( + 'The options array must contain: "%s" and "%s"', + $options[Zend_Cloud_Infrastructure_Instance::MONITOR_START_TIME], + $options[Zend_Cloud_Infrastructure_Instance::MONITOR_END_TIME] + )); + } + + if (!isset($this->ec2Monitor)) { + $this->ec2Monitor = new Zend_Service_Amazon_Ec2_CloudWatch($this->accessKey, $this->accessSecret, $this->region); + } + + $param = array( + 'MeasureName' => $this->mapMetrics[$metric], + 'Statistics' => array('Average'), + 'Dimensions' => array('InstanceId' => $id), + ); + + if (!empty($options)) { + $param['StartTime'] = $options[Zend_Cloud_Infrastructure_Instance::MONITOR_START_TIME]; + $param['EndTime'] = $options[Zend_Cloud_Infrastructure_Instance::MONITOR_END_TIME]; + } + + $this->adapterResult = $this->ec2Monitor->getMetricStatistics($param); + + $monitor = array(); + $num = 0; + $average = 0; + + if (!empty($this->adapterResult['datapoints'])) { + foreach ($this->adapterResult['datapoints'] as $result) { + $monitor['series'][] = array ( + 'timestamp' => $result['Timestamp'], + 'value' => $result['Average'], + ); + $average += $result['Average']; + $num++; + } + } + + if ($num > 0) { + $monitor['average'] = $average / $num; + } + + return $monitor; + } + + /** + * Get the adapter + * + * @return Zend_Service_Amazon_Ec2_Instance + */ + public function getAdapter() + { + return $this->ec2; + } + + /** + * Get last HTTP request + * + * @return string + */ + public function getLastHttpRequest() + { + return $this->ec2->getHttpClient()->getLastRequest(); + } + + /** + * Get the last HTTP response + * + * @return Zend_Http_Response + */ + public function getLastHttpResponse() + { + return $this->ec2->getHttpClient()->getLastResponse(); + } +} diff --git a/library/Zend/Cloud/Infrastructure/Adapter/Rackspace.php b/library/Zend/Cloud/Infrastructure/Adapter/Rackspace.php new file mode 100644 index 0000000..bf00427 --- /dev/null +++ b/library/Zend/Cloud/Infrastructure/Adapter/Rackspace.php @@ -0,0 +1,483 @@ + Zend_Cloud_Infrastructure_Instance::STATUS_RUNNING, + 'SUSPENDED' => Zend_Cloud_Infrastructure_Instance::STATUS_STOPPED, + 'BUILD' => Zend_Cloud_Infrastructure_Instance::STATUS_REBUILD, + 'REBUILD' => Zend_Cloud_Infrastructure_Instance::STATUS_REBUILD, + 'QUEUE_RESIZE' => Zend_Cloud_Infrastructure_Instance::STATUS_PENDING, + 'PREP_RESIZE' => Zend_Cloud_Infrastructure_Instance::STATUS_PENDING, + 'RESIZE' => Zend_Cloud_Infrastructure_Instance::STATUS_REBUILD, + 'VERIFY_RESIZE' => Zend_Cloud_Infrastructure_Instance::STATUS_REBUILD, + 'PASSWORD' => Zend_Cloud_Infrastructure_Instance::STATUS_PENDING, + 'RESCUE' => Zend_Cloud_Infrastructure_Instance::STATUS_PENDING, + 'REBOOT' => Zend_Cloud_Infrastructure_Instance::STATUS_REBOOTING, + 'HARD_REBOOT' => Zend_Cloud_Infrastructure_Instance::STATUS_REBOOTING, + 'SHARE_IP' => Zend_Cloud_Infrastructure_Instance::STATUS_PENDING, + 'SHARE_IP_NO_CONFIG' => Zend_Cloud_Infrastructure_Instance::STATUS_PENDING, + 'DELETE_IP' => Zend_Cloud_Infrastructure_Instance::STATUS_PENDING, + 'UNKNOWN' => Zend_Cloud_Infrastructure_Instance::STATUS_PENDING + ); + /** + * Constructor + * + * @param array|Zend_Config $options + * @return void + */ + public function __construct($options = array()) + { + if (is_object($options)) { + if (method_exists($options, 'toArray')) { + $options= $options->toArray(); + } elseif ($options instanceof Traversable) { + $options = iterator_to_array($options); + } + } + + if (empty($options) || !is_array($options)) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('Invalid options provided'); + } + + if (!isset($options[self::RACKSPACE_USER])) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('Rackspace access user not specified!'); + } + + if (!isset($options[self::RACKSPACE_KEY])) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('Rackspace access key not specified!'); + } + + $this->accessUser = $options[self::RACKSPACE_USER]; + $this->accessKey = $options[self::RACKSPACE_KEY]; + + if (isset($options[self::RACKSPACE_REGION])) { + switch ($options[self::RACKSPACE_REGION]) { + case self::RACKSPACE_ZONE_UK: + $this->region= Zend_Service_Rackspace_Servers::UK_AUTH_URL; + break; + case self::RACKSPACE_ZONE_USA: + $this->region = Zend_Service_Rackspace_Servers::US_AUTH_URL; + break; + default: + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('The region is not valid'); + } + } else { + $this->region = Zend_Service_Rackspace_Servers::US_AUTH_URL; + } + + try { + $this->rackspace = new Zend_Service_Rackspace_Servers($this->accessUser,$this->accessKey, $this->region); + } catch (Exception $e) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('Error on create: ' . $e->getMessage(), $e->getCode(), $e); + } + + if (isset($options[self::HTTP_ADAPTER])) { + $this->rackspace->getHttpClient()->setAdapter($options[self::HTTP_ADAPTER]); + } + + } + /** + * Convert the attributes of Rackspace server into attributes of Infrastructure + * + * @param array $attr + * @return array|boolean + */ + protected function convertAttributes($attr) + { + $result = array(); + if (!empty($attr) && is_array($attr)) { + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_ID] = $attr['id']; + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_NAME] = $attr['name']; + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_STATUS] = $this->mapStatus[$attr['status']]; + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_IMAGEID] = $attr['imageId']; + if ($this->region==Zend_Service_Rackspace_Servers::US_AUTH_URL) { + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_ZONE] = self::RACKSPACE_ZONE_USA; + } else { + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_ZONE] = self::RACKSPACE_ZONE_UK; + } + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_RAM] = $this->flavors[$attr['flavorId']]['ram']; + $result[Zend_Cloud_Infrastructure_Instance::INSTANCE_STORAGE] = $this->flavors[$attr['flavorId']]['disk']; + } + return $result; + } + /** + * Return a list of the available instancies + * + * @return InstanceList|boolean + */ + public function listInstances() + { + $this->adapterResult = $this->rackspace->listServers(true); + if ($this->adapterResult===false) { + return false; + } + $array= $this->adapterResult->toArray(); + $result = array(); + foreach ($array as $instance) { + $result[]= $this->convertAttributes($instance); + } + return new Zend_Cloud_Infrastructure_InstanceList($this, $result); + } + /** + * Return the status of an instance + * + * @param string + * @return string|boolean + */ + public function statusInstance($id) + { + $this->adapterResult = $this->rackspace->getServer($id); + if ($this->adapterResult===false) { + return false; + } + $array= $this->adapterResult->toArray(); + return $this->mapStatus[$array['status']]; + } + /** + * Return the public DNS name/Ip address of the instance + * + * @param string $id + * @return string|boolean + */ + public function publicDnsInstance($id) + { + $this->adapterResult = $this->rackspace->getServerPublicIp($id); + if (empty($this->adapterResult)) { + return false; + } + return $this->adapterResult[0]; + } + /** + * Reboot an instance + * + * @param string $id + * @return boolean + */ + public function rebootInstance($id) + { + return $this->rackspace->rebootServer($id,true); + } + /** + * Create a new instance + * + * @param string $name + * @param array $options + * @return Instance|boolean + */ + public function createInstance($name, $options) + { + if (empty($name)) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('You must specify the name of the instance'); + } + if (empty($options) || !is_array($options)) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('The options must be an array'); + } + // @todo create an generic abstract definition for an instance? + $metadata= array(); + if (isset($options['metadata'])) { + $metadata= $options['metadata']; + unset($options['metadata']); + } + $files= array(); + if (isset($options['files'])) { + $files= $options['files']; + unset($options['files']); + } + $options['name']= $name; + $this->adapterResult = $this->rackspace->createServer($options,$metadata,$files); + if ($this->adapterResult===false) { + return false; + } + return new Zend_Cloud_Infrastructure_Instance($this, $this->convertAttributes($this->adapterResult->toArray())); + } + /** + * Stop an instance + * + * @param string $id + * @return boolean + */ + public function stopInstance($id) + { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('The stopInstance method is not implemented in the adapter'); + } + + /** + * Start an instance + * + * @param string $id + * @return boolean + */ + public function startInstance($id) + { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('The startInstance method is not implemented in the adapter'); + } + + /** + * Destroy an instance + * + * @param string $id + * @return boolean + */ + public function destroyInstance($id) + { + $this->adapterResult= $this->rackspace->deleteServer($id); + return $this->adapterResult; + } + /** + * Return a list of all the available instance images + * + * @return ImageList|boolean + */ + public function imagesInstance() + { + $this->adapterResult = $this->rackspace->listImages(true); + if ($this->adapterResult===false) { + return false; + } + + $images= $this->adapterResult->toArray(); + $result= array(); + + foreach ($images as $image) { + if (strtolower($image['status'])==='active') { + if (strpos($image['name'],'Windows')!==false) { + $platform = Zend_Cloud_Infrastructure_Image::IMAGE_WINDOWS; + } else { + $platform = Zend_Cloud_Infrastructure_Image::IMAGE_LINUX; + } + if (strpos($image['name'],'x64')!==false) { + $arch = Zend_Cloud_Infrastructure_Image::ARCH_64BIT; + } else { + $arch = Zend_Cloud_Infrastructure_Image::ARCH_32BIT; + } + $result[]= array ( + Zend_Cloud_Infrastructure_Image::IMAGE_ID => $image['id'], + Zend_Cloud_Infrastructure_Image::IMAGE_NAME => $image['name'], + Zend_Cloud_Infrastructure_Image::IMAGE_DESCRIPTION => $image['name'], + Zend_Cloud_Infrastructure_Image::IMAGE_ARCHITECTURE => $arch, + Zend_Cloud_Infrastructure_Image::IMAGE_PLATFORM => $platform, + ); + } + } + return new Zend_Cloud_Infrastructure_ImageList($result,$this->adapterResult); + } + /** + * Return all the available zones + * + * @return array + */ + public function zonesInstance() + { + return array(self::RACKSPACE_ZONE_USA,self::RACKSPACE_ZONE_UK); + } + /** + * Return the system information about the $metric of an instance + * NOTE: it works only for Linux servers + * + * @param string $id + * @param string $metric + * @param null|array $options + * @return array|boolean + */ + public function monitorInstance($id, $metric, $options = null) + { + if (!function_exists("ssh2_connect")) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('Monitor requires the PHP "SSH" extension (ext/ssh2)'); + } + if (empty($id)) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('You must specify the id of the instance to monitor'); + } + if (empty($metric)) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('You must specify the metric to monitor'); + } + if (!in_array($metric,$this->validMetrics)) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception(sprintf('The metric "%s" is not valid', $metric)); + } + if (!empty($options) && !is_array($options)) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('The options must be an array'); + } + + switch ($metric) { + case Zend_Cloud_Infrastructure_Instance::MONITOR_CPU: + $cmd= 'top -b -n '.self::MONITOR_CPU_SAMPLES.' | grep \'Cpu\''; + break; + case Zend_Cloud_Infrastructure_Instance::MONITOR_RAM: + $cmd= 'top -b -n 1 | grep \'Mem\''; + break; + case Zend_Cloud_Infrastructure_Instance::MONITOR_DISK: + $cmd= 'df --total | grep total'; + break; + } + if (empty($cmd)) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('The metric specified is not supported by the adapter'); + } + + $params= array( + Zend_Cloud_Infrastructure_Instance::SSH_USERNAME => $options['username'], + Zend_Cloud_Infrastructure_Instance::SSH_PASSWORD => $options['password'] + ); + $exec_time= time(); + $result= $this->deployInstance($id,$params,$cmd); + + if (empty($result)) { + return false; + } + + $monitor = array(); + $num = 0; + $average = 0; + + $outputs= explode("\n",$result); + foreach ($outputs as $output) { + if (!empty($output)) { + switch ($metric) { + case Zend_Cloud_Infrastructure_Instance::MONITOR_CPU: + if (preg_match('/(\d+\.\d)%us/', $output,$match)) { + $usage = (float) $match[1]; + } + break; + case Zend_Cloud_Infrastructure_Instance::MONITOR_RAM: + if (preg_match('/(\d+)k total/', $output,$match)) { + $total = (integer) $match[1]; + } + if (preg_match('/(\d+)k used/', $output,$match)) { + $used = (integer) $match[1]; + } + if ($total>0) { + $usage= (float) $used/$total; + } + break; + case Zend_Cloud_Infrastructure_Instance::MONITOR_DISK: + if (preg_match('/(\d+)%/', $output,$match)) { + $usage = (float) $match[1]; + } + break; + } + + $monitor['series'][] = array ( + 'timestamp' => $exec_time, + 'value' => number_format($usage,2).'%' + ); + + $average += $usage; + $exec_time+= 60; // seconds + $num++; + } + } + + if ($num>0) { + $monitor['average'] = number_format($average/$num,2).'%'; + } + return $monitor; + } + /** + * Get the adapter + * + * @return Zend_Service_Rackspace_Servers + */ + public function getAdapter() + { + return $this->rackspace; + } + /** + * Get last HTTP request + * + * @return string + */ + public function getLastHttpRequest() + { + return $this->rackspace->getHttpClient()->getLastRequest(); + } + /** + * Get the last HTTP response + * + * @return Zend_Http_Response + */ + public function getLastHttpResponse() + { + return $this->rackspace->getHttpClient()->getLastResponse(); + } +} diff --git a/library/Zend/Cloud/Infrastructure/Exception.php b/library/Zend/Cloud/Infrastructure/Exception.php new file mode 100644 index 0000000..a7e5da1 --- /dev/null +++ b/library/Zend/Cloud/Infrastructure/Exception.php @@ -0,0 +1,25 @@ +toArray(); + } elseif ($data instanceof Traversable) { + $data = iterator_to_array($data); + } + } + + if (empty($data) || !is_array($data)) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('You must pass an array of parameters'); + } + + foreach ($this->attributeRequired as $key) { + if (empty($data[$key])) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception(sprintf( + 'The param "%s" is a required parameter for class %s', + $key, + __CLASS__ + )); + } + } + + $this->attributes = $data; + $this->adapter = $adapter; + } + + /** + * Get Attribute with a specific key + * + * @param array $data + * @return misc|boolean + */ + public function getAttribute($key) + { + if (!empty($this->attributes[$key])) { + return $this->attributes[$key]; + } + return false; + } + + /** + * Get all the attributes + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Get the image ID + * + * @return string + */ + public function getId() + { + return $this->attributes[self::IMAGE_ID]; + } + + /** + * Get the Owner ID + * + * @return string + */ + public function getOwnerId() + { + return $this->attributes[self::IMAGE_OWNERID]; + } + + /** + * Get the name + * + * @return string + */ + public function getName() + { + return $this->attributes[self::IMAGE_NAME]; + } + + /** + * Get the description + * + * @return string + */ + public function getDescription() + { + return $this->attributes[self::IMAGE_DESCRIPTION]; + } + + /** + * Get the platform + * + * @return string + */ + public function getPlatform() + { + return $this->attributes[self::IMAGE_PLATFORM]; + } + + /** + * Get the architecture + * + * @return string + */ + public function getArchitecture() + { + return $this->attributes[self::IMAGE_ARCHITECTURE]; + } +} diff --git a/library/Zend/Cloud/Infrastructure/ImageList.php b/library/Zend/Cloud/Infrastructure/ImageList.php new file mode 100644 index 0000000..7692bde --- /dev/null +++ b/library/Zend/Cloud/Infrastructure/ImageList.php @@ -0,0 +1,218 @@ +adapter = $adapter; + $this->constructFromArray($images); + } + + /** + * Transforms the Array to array of Instances + * + * @param array $list + * @return void + */ + protected function constructFromArray(array $list) + { + foreach ($list as $image) { + $this->addImage(new Zend_Cloud_Infrastructure_Image($image, $this->adapter)); + } + } + + /** + * Add an image + * + * @param Image + * @return ImageList + */ + protected function addImage(Zend_Cloud_Infrastructure_Image $image) + { + $this->images[] = $image; + return $this; + } + + /** + * Return number of images + * + * Implement Countable::count() + * + * @return int + */ + public function count() + { + return count($this->images); + } + + /** + * Return the current element + * + * Implement Iterator::current() + * + * @return Image + */ + public function current() + { + return $this->images[$this->iteratorKey]; + } + + /** + * Return the key of the current element + * + * Implement Iterator::key() + * + * @return int + */ + public function key() + { + return $this->iteratorKey; + } + + /** + * Move forward to next element + * + * Implement Iterator::next() + * + * @return void + */ + public function next() + { + $this->iteratorKey++; + } + + /** + * Rewind the Iterator to the first element + * + * Implement Iterator::rewind() + * + * @return void + */ + public function rewind() + { + $this->iteratorKey = 0; + } + + /** + * Check if there is a current element after calls to rewind() or next() + * + * Implement Iterator::valid() + * + * @return bool + */ + public function valid() + { + $numItems = $this->count(); + if ($numItems > 0 && $this->iteratorKey < $numItems) { + return true; + } + return false; + } + + /** + * Whether the offset exists + * + * Implement ArrayAccess::offsetExists() + * + * @param int $offset + * @return bool + */ + public function offsetExists($offset) + { + return ($offset < $this->count()); + } + + /** + * Return value at given offset + * + * Implement ArrayAccess::offsetGet() + * + * @param int $offset + * @throws Zend_Cloud_Infrastructure_Exception + * @return Image + */ + public function offsetGet($offset) + { + if (!$this->offsetExists($offset)) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('Illegal index'); + } + return $this->images[$offset]; + } + + /** + * Throws exception because all values are read-only + * + * Implement ArrayAccess::offsetSet() + * + * @param int $offset + * @param string $value + * @throws Zend_Cloud_Infrastructure_Exception + */ + public function offsetSet($offset, $value) + { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('You are trying to set read-only property'); + } + + /** + * Throws exception because all values are read-only + * + * Implement ArrayAccess::offsetUnset() + * + * @param int $offset + * @throws Zend_Cloud_Infrastructure_Exception + */ + public function offsetUnset($offset) + { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('You are trying to unset read-only property'); + } +} diff --git a/library/Zend/Cloud/Infrastructure/Instance.php b/library/Zend/Cloud/Infrastructure/Instance.php new file mode 100644 index 0000000..6830925 --- /dev/null +++ b/library/Zend/Cloud/Infrastructure/Instance.php @@ -0,0 +1,320 @@ +toArray(); + } elseif ($data instanceof Traversable) { + $data = iterator_to_array($data); + } + } + + if (empty($data) || !is_array($data)) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception("You must pass an array of parameters"); + } + + foreach ($this->attributeRequired as $key) { + if (empty($data[$key])) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception(sprintf( + 'The param "%s" is a required param for %s', + $key, + __CLASS__ + )); + } + } + + $this->adapter = $adapter; + $this->attributes = $data; + } + + /** + * Get Attribute with a specific key + * + * @param array $data + * @return misc|false + */ + public function getAttribute($key) + { + if (!empty($this->attributes[$key])) { + return $this->attributes[$key]; + } + return false; + } + + /** + * Get all the attributes + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Get the instance's id + * + * @return string + */ + public function getId() + { + return $this->attributes[self::INSTANCE_ID]; + } + + /** + * Get the instance's image id + * + * @return string + */ + public function getImageId() + { + return $this->attributes[self::INSTANCE_IMAGEID]; + } + + /** + * Get the instance's name + * + * @return string + */ + public function getName() + { + return $this->attributes[self::INSTANCE_NAME]; + } + + /** + * Get the status of the instance + * + * @return string|boolean + */ + public function getStatus() + { + return $this->adapter->statusInstance($this->attributes[self::INSTANCE_ID]); + } + + /** + * Wait for status $status with a timeout of $timeout seconds + * + * @param string $status + * @param integer $timeout + * @return boolean + */ + public function waitStatus($status, $timeout = Adapter::TIMEOUT_STATUS_CHANGE) + { + return $this->adapter->waitStatusInstance($this->attributes[self::INSTANCE_ID], $status, $timeout); + } + + /** + * Get the public DNS of the instance + * + * @return string + */ + public function getPublicDns() + { + if (!isset($this->attributes[self::INSTANCE_PUBLICDNS])) { + $this->attributes[self::INSTANCE_PUBLICDNS] = $this->adapter->publicDnsInstance($this->attributes[self::INSTANCE_ID]); + } + return $this->attributes[self::INSTANCE_PUBLICDNS]; + } + + /** + * Get the instance's CPU + * + * @return string + */ + public function getCpu() + { + return $this->attributes[self::INSTANCE_CPU]; + } + + /** + * Get the instance's RAM size + * + * @return string + */ + public function getRamSize() + { + return $this->attributes[self::INSTANCE_RAM]; + } + + /** + * Get the instance's storage size + * + * @return string + */ + public function getStorageSize() + { + return $this->attributes[self::INSTANCE_STORAGE]; + } + + /** + * Get the instance's zone + * + * @return string + */ + public function getZone() + { + return $this->attributes[self::INSTANCE_ZONE]; + } + + /** + * Get the instance's launch time + * + * @return string + */ + public function getLaunchTime() + { + return $this->attributes[self::INSTANCE_LAUNCHTIME]; + } + + /** + * Reboot the instance + * + * @return boolean + */ + public function reboot() + { + return $this->adapter->rebootInstance($this->attributes[self::INSTANCE_ID]); + } + + /** + * Stop the instance + * + * @return boolean + */ + public function stop() + { + return $this->adapter->stopInstance($this->attributes[self::INSTANCE_ID]); + } + + /** + * Start the instance + * + * @return boolean + */ + public function start() + { + return $this->adapter->startInstance($this->attributes[self::INSTANCE_ID]); + } + + /** + * Destroy the instance + * + * @return boolean + */ + public function destroy() + { + return $this->adapter->destroyInstance($this->attributes[self::INSTANCE_ID]); + } + + /** + * Return the system informations about the $metric of an instance + * + * @param string $metric + * @param null|array $options + * @return array|boolean + */ + public function monitor($metric, $options = null) + { + return $this->adapter->monitorInstance($this->attributes[self::INSTANCE_ID], $metric, $options); + } + + /** + * Run arbitrary shell script on the instance + * + * @param array $param + * @param string|array $cmd + * @return string|array + */ + public function deploy($params, $cmd) + { + return $this->adapter->deployInstance($this->attributes[self::INSTANCE_ID], $params, $cmd); + } +} diff --git a/library/Zend/Cloud/Infrastructure/InstanceList.php b/library/Zend/Cloud/Infrastructure/InstanceList.php new file mode 100644 index 0000000..0cda88c --- /dev/null +++ b/library/Zend/Cloud/Infrastructure/InstanceList.php @@ -0,0 +1,219 @@ +adapter = $adapter; + $this->constructFromArray($instances); + } + + /** + * Transforms the Array to array of Instances + * + * @param array $list + * @return void + */ + protected function constructFromArray(array $list) + { + foreach ($list as $instance) { + $this->addInstance(new Zend_Cloud_Infrastructure_Instance($this->adapter,$instance)); + } + } + + /** + * Add an instance + * + * @param Instance + * @return InstanceList + */ + protected function addInstance(Zend_Cloud_Infrastructure_Instance $instance) + { + $this->instances[] = $instance; + return $this; + } + + /** + * Return number of instances + * + * Implement Countable::count() + * + * @return int + */ + public function count() + { + return count($this->instances); + } + + /** + * Return the current element + * + * Implement Iterator::current() + * + * @return Instance + */ + public function current() + { + return $this->instances[$this->iteratorKey]; + } + + /** + * Return the key of the current element + * + * Implement Iterator::key() + * + * @return int + */ + public function key() + { + return $this->iteratorKey; + } + + /** + * Move forward to next element + * + * Implement Iterator::next() + * + * @return void + */ + public function next() + { + $this->iteratorKey++; + } + + /** + * Rewind the Iterator to the first element + * + * Implement Iterator::rewind() + * + * @return void + */ + public function rewind() + { + $this->iteratorKey = 0; + } + + /** + * Check if there is a current element after calls to rewind() or next() + * + * Implement Iterator::valid() + * + * @return bool + */ + public function valid() + { + $numItems = $this->count(); + if ($numItems > 0 && $this->iteratorKey < $numItems) { + return true; + } + return false; + } + + /** + * Whether the offset exists + * + * Implement ArrayAccess::offsetExists() + * + * @param int $offset + * @return bool + */ + public function offsetExists($offset) + { + return ($offset < $this->count()); + } + + /** + * Return value at given offset + * + * Implement ArrayAccess::offsetGet() + * + * @param int $offset + * @return Instance + * @throws Zend_Cloud_Infrastructure_Exception + */ + public function offsetGet($offset) + { + if (!$this->offsetExists($offset)) { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('Illegal index'); + } + return $this->instances[$offset]; + } + + /** + * Throws exception because all values are read-only + * + * Implement ArrayAccess::offsetSet() + * + * @param int $offset + * @param string $value + * @throws Zend_Cloud_Infrastructure_Exception + */ + public function offsetSet($offset, $value) + { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('You are trying to set read-only property'); + } + + /** + * Throws exception because all values are read-only + * + * Implement ArrayAccess::offsetUnset() + * + * @param int $offset + * @throws Zend_Cloud_Infrastructure_Exception + */ + public function offsetUnset($offset) + { + require_once 'Zend/Cloud/Infrastructure/Exception.php'; + throw new Zend_Cloud_Infrastructure_Exception('You are trying to unset read-only property'); + } +} diff --git a/library/Zend/Cloud/OperationNotAvailableException.php b/library/Zend/Cloud/OperationNotAvailableException.php new file mode 100644 index 0000000..18a1462 --- /dev/null +++ b/library/Zend/Cloud/OperationNotAvailableException.php @@ -0,0 +1,34 @@ +_messageClass = (string) $class; + return $this; + } + + /** + * Get class to use for message objects + * + * @return string + */ + public function getMessageClass() + { + return $this->_messageClass; + } + + /** + * Set class to use for message collection objects + * + * @param string $class + * @return Zend_Cloud_QueueService_Adapter_AbstractAdapter + */ + public function setMessageSetClass($class) + { + $this->_messageSetClass = (string) $class; + return $this; + } + + /** + * Get class to use for message collection objects + * + * @return string + */ + public function getMessageSetClass() + { + return $this->_messageSetClass; + } +} diff --git a/library/Zend/Cloud/QueueService/Adapter/Sqs.php b/library/Zend/Cloud/QueueService/Adapter/Sqs.php new file mode 100644 index 0000000..13d05d3 --- /dev/null +++ b/library/Zend/Cloud/QueueService/Adapter/Sqs.php @@ -0,0 +1,278 @@ +toArray(); + } + + if (!is_array($options)) { + throw new Zend_Cloud_QueueService_Exception('Invalid options provided'); + } + + if (isset($options[self::MESSAGE_CLASS])) { + $this->setMessageClass($options[self::MESSAGE_CLASS]); + } + + if (isset($options[self::MESSAGESET_CLASS])) { + $this->setMessageSetClass($options[self::MESSAGESET_CLASS]); + } + + try { + $this->_sqs = new Zend_Service_Amazon_Sqs( + $options[self::AWS_ACCESS_KEY], $options[self::AWS_SECRET_KEY] + ); + } catch(Zend_Service_Amazon_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on create: '.$e->getMessage(), $e->getCode(), $e); + } + + if(isset($options[self::HTTP_ADAPTER])) { + $this->_sqs->getHttpClient()->setAdapter($options[self::HTTP_ADAPTER]); + } + } + + /** + * Create a queue. Returns the ID of the created queue (typically the URL). + * It may take some time to create the queue. Check your vendor's + * documentation for details. + * + * @param string $name + * @param array $options + * @return string Queue ID (typically URL) + */ + public function createQueue($name, $options = null) + { + try { + return $this->_sqs->create($name, $options[self::CREATE_TIMEOUT]); + } catch(Zend_Service_Amazon_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on queue creation: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Delete a queue. All messages in the queue will also be deleted. + * + * @param string $queueId + * @param array $options + * @return boolean true if successful, false otherwise + */ + public function deleteQueue($queueId, $options = null) +{ + try { + return $this->_sqs->delete($queueId); + } catch(Zend_Service_Amazon_Exception $e) { + throw Zend_Cloud_QueueService_Exception('Error on queue deletion: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * List all queues. + * + * @param array $options + * @return array Queue IDs + */ + public function listQueues($options = null) + { + try { + return $this->_sqs->getQueues(); + } catch(Zend_Service_Amazon_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on listing queues: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Get a key/value array of metadata for the given queue. + * + * @param string $queueId + * @param array $options + * @return array + */ + public function fetchQueueMetadata($queueId, $options = null) + { + try { + // TODO: ZF-9050 Fix the SQS client library in trunk to return all attribute values + $attributes = $this->_sqs->getAttribute($queueId, 'All'); + if(is_array($attributes)) { + return $attributes; + } else { + return array('All' => $this->_sqs->getAttribute($queueId, 'All')); + } + } catch(Zend_Service_Amazon_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on fetching queue metadata: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Store a key/value array of metadata for the specified queue. + * WARNING: This operation overwrites any metadata that is located at + * $destinationPath. Some adapters may not support this method. + * + * @param array $metadata + * @param string $queueId + * @param array $options + * @return void + */ + public function storeQueueMetadata($queueId, $metadata, $options = null) + { + // TODO Add support for SetQueueAttributes to client library + require_once 'Zend/Cloud/OperationNotAvailableException.php'; + throw new Zend_Cloud_OperationNotAvailableException('Amazon SQS doesn\'t currently support storing metadata'); + } + + /** + * Send a message to the specified queue. + * + * @param string $message + * @param string $queueId + * @param array $options + * @return string Message ID + */ + public function sendMessage($queueId, $message, $options = null) + { + try { + return $this->_sqs->send($queueId, $message); + } catch(Zend_Service_Amazon_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on sending message: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Recieve at most $max messages from the specified queue and return the + * message IDs for messages recieved. + * + * @param string $queueId + * @param int $max + * @param array $options + * @return array + */ + public function receiveMessages($queueId, $max = 1, $options = null) + { + try { + return $this->_makeMessages($this->_sqs->receive($queueId, $max, $options[self::VISIBILITY_TIMEOUT])); + } catch(Zend_Service_Amazon_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on recieving messages: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Create Zend_Cloud_QueueService_Message array for + * Sqs messages. + * + * @param array $messages + * @return Zend_Cloud_QueueService_Message[] + */ + protected function _makeMessages($messages) + { + $messageClass = $this->getMessageClass(); + $setClass = $this->getMessageSetClass(); + $result = array(); + foreach($messages as $message) { + $result[] = new $messageClass($message['body'], $message); + } + return new $setClass($result); + } + + /** + * Delete the specified message from the specified queue. + * + * @param string $queueId + * @param Zend_Cloud_QueueService_Message $message + * @param array $options + * @return void + */ + public function deleteMessage($queueId, $message, $options = null) + { + try { + if($message instanceof Zend_Cloud_QueueService_Message) { + $message = $message->getMessage(); + } + $messageId = $message['handle']; + return $this->_sqs->deleteMessage($queueId, $messageId); + } catch(Zend_Service_Amazon_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on deleting a message: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Peek at the messages from the specified queue without removing them. + * + * @param string $queueId + * @param int $num How many messages + * @param array $options + * @return Zend_Cloud_QueueService_Message[] + */ + public function peekMessages($queueId, $num = 1, $options = null) + { + try { + return $this->_makeMessages($this->_sqs->receive($queueId, $num, 0)); + } catch(Zend_Service_Amazon_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on peeking messages: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Get SQS implementation + * @return Zend_Service_Amazon_Sqs + */ + public function getClient() + { + return $this->_sqs; + } +} diff --git a/library/Zend/Cloud/QueueService/Adapter/WindowsAzure.php b/library/Zend/Cloud/QueueService/Adapter/WindowsAzure.php new file mode 100644 index 0000000..3654a96 --- /dev/null +++ b/library/Zend/Cloud/QueueService/Adapter/WindowsAzure.php @@ -0,0 +1,343 @@ +toArray(); + } + + if (!is_array($options)) { + throw new Zend_Cloud_QueueService_Exception('Invalid options provided'); + } + + if (isset($options[self::MESSAGE_CLASS])) { + $this->setMessageClass($options[self::MESSAGE_CLASS]); + } + + if (isset($options[self::MESSAGESET_CLASS])) { + $this->setMessageSetClass($options[self::MESSAGESET_CLASS]); + } + + // Build Zend_Service_WindowsAzure_Storage_Blob instance + if (!isset($options[self::HOST])) { + $host = self::DEFAULT_HOST; + } else { + $host = $options[self::HOST]; + } + if (! isset($options[self::ACCOUNT_NAME])) { + throw new Zend_Cloud_Storage_Exception('No Windows Azure account name provided.'); + } + if (! isset($options[self::ACCOUNT_KEY])) { + throw new Zend_Cloud_Storage_Exception('No Windows Azure account key provided.'); + } + try { + // TODO: support $usePathStyleUri and $retryPolicy + $this->_storageClient = new Zend_Service_WindowsAzure_Storage_Queue( + $host, $options[self::ACCOUNT_NAME], $options[self::ACCOUNT_KEY]); + // Parse other options + if (! empty($options[self::PROXY_HOST])) { + $proxyHost = $options[self::PROXY_HOST]; + $proxyPort = isset($options[self::PROXY_PORT]) ? $options[self::PROXY_PORT] : 8080; + $proxyCredentials = isset($options[self::PROXY_CREDENTIALS]) ? $options[self::PROXY_CREDENTIALS] : ''; + $this->_storageClient->setProxy(true, $proxyHost, $proxyPort, $proxyCredentials); + } + if (isset($options[self::HTTP_ADAPTER])) { + $this->_storageClient->setHttpClientChannel($httpAdapter); + } + } catch(Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on create: '.$e->getMessage(), $e->getCode(), $e); + } + + } + + /** + * Create a queue. Returns the ID of the created queue (typically the URL). + * It may take some time to create the queue. Check your vendor's + * documentation for details. + * + * @param string $name + * @param array $options + * @return string Queue ID (typically URL) + */ + public function createQueue($name, $options = null) + { + try { + $queue = $this->_storageClient->createQueue($name, $options); + return $queue->Name; + } catch (Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on queue creation: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Delete a queue. All messages in the queue will also be deleted. + * + * @param string $queueId + * @param array $options + * @return boolean true if successful, false otherwise + */ + public function deleteQueue($queueId, $options = null) + { + try { + if ($queueId instanceof Zend_Service_WindowsAzure_Storage_QueueInstance) { + $queueId = $queueId->Name; + } + return $this->_storageClient->deleteQueue($queueId); + } catch (Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on queue deletion: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * List all queues. + * + * @param array $options + * @return array Queue IDs + */ + public function listQueues($options = null) + { + $prefix = $maxResults = null; + if (is_array($options)) { + isset($options[self::LIST_PREFIX]) ? $prefix = $options[self::LIST_PREFIX] : null; + isset($options[self::LIST_MAX_RESULTS]) ? $maxResults = $options[self::LIST_MAX_RESULTS] : null; + } + try { + $queues = $this->_storageClient->listQueues($prefix, $maxResults); + $result = array(); + foreach ($queues as $queue) { + $result[] = $queue->Name; + } + return $result; + } catch (Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on listing queues: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Get a key/value array of metadata for the given queue. + * + * @param string $queueId + * @param array $options + * @return array + */ + public function fetchQueueMetadata($queueId, $options = null) + { + try { + if ($queueId instanceof Zend_Service_WindowsAzure_Storage_QueueInstance) { + $queueId = $queueId->Name; + } + return $this->_storageClient->getQueueMetadata($queueId); + } catch (Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on fetching queue metadata: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Store a key/value array of metadata for the specified queue. + * WARNING: This operation overwrites any metadata that is located at + * $destinationPath. Some adapters may not support this method. + * + * @param string $queueId + * @param array $metadata + * @param array $options + * @return void + */ + public function storeQueueMetadata($queueId, $metadata, $options = null) + { + try { + if ($queueId instanceof Zend_Service_WindowsAzure_Storage_QueueInstance) { + $queueId = $queueId->Name; + } + return $this->_storageClient->setQueueMetadata($queueId, $metadata); + } catch (Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on setting queue metadata: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Send a message to the specified queue. + * + * @param string $queueId + * @param string $message + * @param array $options + * @return string Message ID + */ + public function sendMessage($queueId, $message, $options = null) + { + try { + if ($queueId instanceof Zend_Service_WindowsAzure_Storage_QueueInstance) { + $queueId = $queueId->Name; + } + return $this->_storageClient->putMessage( + $queueId, $message, $options[self::MESSAGE_TTL] + ); + } catch (Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on sending message: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Recieve at most $max messages from the specified queue and return the + * message IDs for messages recieved. + * + * @param string $queueId + * @param int $max + * @param array $options + * @return Zend_Cloud_QueueService_Message[] + */ + public function receiveMessages($queueId, $max = 1, $options = null) + { + try { + if ($queueId instanceof Zend_Service_WindowsAzure_Storage_QueueInstance) { + $queueId = $queueId->Name; + } + if (isset($options[self::VISIBILITY_TIMEOUT])) { + $visibility = $options[self::VISIBILITY_TIMEOUT]; + } else { + $visibility = self::DEFAULT_TIMEOUT; + } + return $this->_makeMessages($this->_storageClient->getMessages($queueId, $max, $visibility, false)); + } catch (Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on recieving messages: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Create Zend_Cloud_QueueService_Message array for + * Azure messages. + * + * @param array $messages + * @return Zend_Cloud_QueueService_Message[] + */ + protected function _makeMessages($messages) + { + $messageClass = $this->getMessageClass(); + $setClass = $this->getMessageSetClass(); + $result = array(); + foreach ($messages as $message) { + $result[] = new $messageClass($message->MessageText, $message); + } + return new $setClass($result); + } + + /** + * Delete the specified message from the specified queue. + * + * @param string $queueId + * @param Zend_Cloud_QueueService_Message $message Message ID or message + * @param array $options + * @return void + */ + public function deleteMessage($queueId, $message, $options = null) + { + try { + if ($queueId instanceof Zend_Service_WindowsAzure_Storage_QueueInstance) { + $queueId = $queueId->Name; + } + if ($message instanceof Zend_Cloud_QueueService_Message) { + $message = $message->getMessage(); + } + if ($message instanceof Zend_Service_WindowsAzure_Storage_QueueMessage) { + return $this->_storageClient->deleteMessage($queueId, $message); + } else { + throw new Zend_Cloud_QueueService_Exception('Cannot delete the message: message object required'); + } + } catch (Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on deleting a message: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Peek at the messages from the specified queue without removing them. + * + * @param string $queueId + * @param int $num How many messages + * @param array $options + * @return Zend_Cloud_QueueService_Message[] + */ + public function peekMessages($queueId, $num = 1, $options = null) + { + try { + if ($queueId instanceof Zend_Service_WindowsAzure_Storage_QueueInstance) { + $queueId = $queueId->Name; + } + return $this->_makeMessages($this->_storageClient->peekMessages($queueId, $num)); + } catch (Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on peeking messages: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Get Azure implementation + * @return Zend_Service_Azure_Storage_Queue + */ + public function getClient() + { + return $this->_storageClient; + } +} diff --git a/library/Zend/Cloud/QueueService/Adapter/ZendQueue.php b/library/Zend/Cloud/QueueService/Adapter/ZendQueue.php new file mode 100644 index 0000000..5349775 --- /dev/null +++ b/library/Zend/Cloud/QueueService/Adapter/ZendQueue.php @@ -0,0 +1,301 @@ +toArray(); + } + + if (!is_array($options)) { + throw new Zend_Cloud_QueueService_Exception('Invalid options provided'); + } + + if (isset($options[self::MESSAGE_CLASS])) { + $this->setMessageClass($options[self::MESSAGE_CLASS]); + } + + if (isset($options[self::MESSAGESET_CLASS])) { + $this->setMessageSetClass($options[self::MESSAGESET_CLASS]); + } + + // Build Zend_Service_WindowsAzure_Storage_Blob instance + if (!isset($options[self::ADAPTER])) { + throw new Zend_Cloud_QueueService_Exception('No Zend_Queue adapter provided'); + } else { + $adapter = $options[self::ADAPTER]; + unset($options[self::ADAPTER]); + } + try { + $this->_queue = new Zend_Queue($adapter, $options); + } catch (Zend_Queue_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on create: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Create a queue. Returns the ID of the created queue (typically the URL). + * It may take some time to create the queue. Check your vendor's + * documentation for details. + * + * @param string $name + * @param array $options + * @return string Queue ID (typically URL) + */ + public function createQueue($name, $options = null) + { + try { + $this->_queues[$name] = $this->_queue->createQueue($name, isset($options[Zend_Queue::TIMEOUT])?$options[Zend_Queue::TIMEOUT]:null); + return $name; + } catch (Zend_Queue_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on queue creation: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Delete a queue. All messages in the queue will also be deleted. + * + * @param string $queueId + * @param array $options + * @return boolean true if successful, false otherwise + */ + public function deleteQueue($queueId, $options = null) + { + if (!isset($this->_queues[$queueId])) { + return false; + } + try { + if ($this->_queues[$queueId]->deleteQueue()) { + unset($this->_queues[$queueId]); + return true; + } + } catch (Zend_Queue_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on queue deletion: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * List all queues. + * + * @param array $options + * @return array Queue IDs + */ + public function listQueues($options = null) + { + try { + return $this->_queue->getQueues(); + } catch (Zend_Queue_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on listing queues: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Get a key/value array of metadata for the given queue. + * + * @param string $queueId + * @param array $options + * @return array + */ + public function fetchQueueMetadata($queueId, $options = null) + { + if (!isset($this->_queues[$queueId])) { + return false; + } + try { + return $this->_queues[$queueId]->getOptions(); + } catch (Zend_Queue_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on fetching queue metadata: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Store a key/value array of metadata for the specified queue. + * WARNING: This operation overwrites any metadata that is located at + * $destinationPath. Some adapters may not support this method. + * + * @param string $queueId + * @param array $metadata + * @param array $options + * @return void + */ + public function storeQueueMetadata($queueId, $metadata, $options = null) + { + if (!isset($this->_queues[$queueId])) { + throw new Zend_Cloud_QueueService_Exception("No such queue: $queueId"); + } + try { + return $this->_queues[$queueId]->setOptions($metadata); + } catch (Zend_Queue_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on setting queue metadata: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Send a message to the specified queue. + * + * @param string $queueId + * @param string $message + * @param array $options + * @return string Message ID + */ + public function sendMessage($queueId, $message, $options = null) + { + if (!isset($this->_queues[$queueId])) { + throw new Zend_Cloud_QueueService_Exception("No such queue: $queueId"); + } + try { + return $this->_queues[$queueId]->send($message); + } catch (Zend_Queue_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on sending message: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Recieve at most $max messages from the specified queue and return the + * message IDs for messages recieved. + * + * @param string $queueId + * @param int $max + * @param array $options + * @return array + */ + public function receiveMessages($queueId, $max = 1, $options = null) + { + if (!isset($this->_queues[$queueId])) { + throw new Zend_Cloud_QueueService_Exception("No such queue: $queueId"); + } + try { + $res = $this->_queues[$queueId]->receive($max, isset($options[Zend_Queue::TIMEOUT])?$options[Zend_Queue::TIMEOUT]:null); + if ($res instanceof Iterator) { + return $this->_makeMessages($res); + } else { + return $this->_makeMessages(array($res)); + } + } catch (Zend_Queue_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on recieving messages: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Create Zend_Cloud_QueueService_Message array for + * Azure messages. + * + * @param array $messages + * @return Zend_Cloud_QueueService_Message[] + */ + protected function _makeMessages($messages) + { + $messageClass = $this->getMessageClass(); + $setClass = $this->getMessageSetClass(); + $result = array(); + foreach ($messages as $message) { + $result[] = new $messageClass($message->body, $message); + } + return new $setClass($result); + } + + /** + * Delete the specified message from the specified queue. + * + * @param string $queueId + * @param Zend_Cloud_QueueService_Message $message Message ID or message + * @param array $options + * @return void + */ + public function deleteMessage($queueId, $message, $options = null) + { + if (!isset($this->_queues[$queueId])) { + throw new Zend_Cloud_QueueService_Exception("No such queue: $queueId"); + } + try { + if ($message instanceof Zend_Cloud_QueueService_Message) { + $message = $message->getMessage(); + } + if (!($message instanceof Zend_Queue_Message)) { + throw new Zend_Cloud_QueueService_Exception('Cannot delete the message: Zend_Queue_Message object required'); + } + + return $this->_queues[$queueId]->deleteMessage($message); + } catch (Zend_Queue_Exception $e) { + throw new Zend_Cloud_QueueService_Exception('Error on deleting a message: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Peek at the messages from the specified queue without removing them. + * + * @param string $queueId + * @param int $num How many messages + * @param array $options + * @return Zend_Cloud_QueueService_Message[] + */ + public function peekMessages($queueId, $num = 1, $options = null) + { + require_once 'Zend/Cloud/OperationNotAvailableException.php'; + throw new Zend_Cloud_OperationNotAvailableException('ZendQueue doesn\'t currently support message peeking'); + } + + /** + * Get Azure implementation + * @return Zend_Queue + */ + public function getClient() + { + return $this->_queue; + } +} diff --git a/library/Zend/Cloud/QueueService/Exception.php b/library/Zend/Cloud/QueueService/Exception.php new file mode 100644 index 0000000..1e8b9a3 --- /dev/null +++ b/library/Zend/Cloud/QueueService/Exception.php @@ -0,0 +1,37 @@ +_body = $body; + $this->_clientMessage = $message; + } + + /** + * Get the message body + * @return string + */ + public function getBody() + { + return $this->_body; + } + + /** + * Get the original adapter-specific message + */ + public function getMessage() + { + return $this->_clientMessage; + } +} diff --git a/library/Zend/Cloud/QueueService/MessageSet.php b/library/Zend/Cloud/QueueService/MessageSet.php new file mode 100644 index 0000000..f622680 --- /dev/null +++ b/library/Zend/Cloud/QueueService/MessageSet.php @@ -0,0 +1,68 @@ +_messageCount = count($messages); + $this->_messages = new ArrayIterator($messages); + } + + /** + * Countable: number of messages in collection + * + * @return int + */ + public function count() + { + return $this->_messageCount; + } + + /** + * IteratorAggregate: return iterable object + * + * @return Traversable + */ + public function getIterator() + { + return $this->_messages; + } +} diff --git a/library/Zend/Cloud/StorageService/Adapter.php b/library/Zend/Cloud/StorageService/Adapter.php new file mode 100644 index 0000000..e06d119 --- /dev/null +++ b/library/Zend/Cloud/StorageService/Adapter.php @@ -0,0 +1,145 @@ +toArray(); + } + + if (!is_array($options)) { + throw new Zend_Cloud_StorageService_Exception('Invalid options provided'); + } + + if (isset($options[self::LOCAL_DIRECTORY])) { + $this->_directory = $options[self::LOCAL_DIRECTORY]; + } else { + $this->_directory = realpath(sys_get_temp_dir()); + } + } + + /** + * Get an item from the storage service. + * + * TODO: Support streaming + * + * @param string $path + * @param array $options + * @return false|string + */ + public function fetchItem($path, $options = array()) + { + $filepath = $this->_getFullPath($path); + $path = realpath($filepath); + + if (!$path || !file_exists($path)) { + return false; + } + + return file_get_contents($path); + } + + /** + * Store an item in the storage service. + * + * WARNING: This operation overwrites any item that is located at + * $destinationPath. + * + * @TODO Support streams + * + * @param string $destinationPath + * @param mixed $data + * @param array $options + * @return void + */ + public function storeItem($destinationPath, $data, $options = array()) + { + $path = $this->_getFullPath($destinationPath); + file_put_contents($path, $data); + chmod($path, 0777); + } + + /** + * Delete an item in the storage service. + * + * @param string $path + * @param array $options + * @return void + */ + public function deleteItem($path, $options = array()) + { + if (!isset($path)) { + return; + } + + $filepath = $this->_getFullPath($path); + if (file_exists($filepath)) { + unlink($filepath); + } + } + + /** + * Copy an item in the storage service to a given path. + * + * WARNING: This operation is *very* expensive for services that do not + * support copying an item natively. + * + * @TODO Support streams for those services that don't support natively + * + * @param string $sourcePath + * @param string $destination path + * @param array $options + * @return void + */ + public function copyItem($sourcePath, $destinationPath, $options = array()) + { + copy($this->_getFullPath($sourcePath), $this->_getFullPath($destinationPath)); + } + + /** + * Move an item in the storage service to a given path. + * + * WARNING: This operation is *very* expensive for services that do not + * support moving an item natively. + * + * @TODO Support streams for those services that don't support natively + * + * @param string $sourcePath + * @param string $destination path + * @param array $options + * @return void + */ + public function moveItem($sourcePath, $destinationPath, $options = array()) + { + rename($this->_getFullPath($sourcePath), $this->_getFullPath($destinationPath)); + } + + /** + * Rename an item in the storage service to a given name. + * + * + * @param string $path + * @param string $name + * @param array $options + * @return void + */ + public function renameItem($path, $name, $options = null) + { + rename( + $this->_getFullPath($path), + dirname($this->_getFullPath($path)) . DIRECTORY_SEPARATOR . $name + ); + } + + /** + * List items in the given directory in the storage service + * + * The $path must be a directory + * + * + * @param string $path Must be a directory + * @param array $options + * @return array A list of item names + */ + public function listItems($path, $options = null) + { + $listing = scandir($this->_getFullPath($path)); + + // Remove the hidden navigation directories + $listing = array_diff($listing, array('.', '..')); + + return $listing; + } + + /** + * Get a key/value array of metadata for the given path. + * + * @param string $path + * @param array $options + * @return array + */ + public function fetchMetadata($path, $options = array()) + { + $fullPath = $this->_getFullPath($path); + $metadata = null; + if (file_exists($fullPath)) { + $metadata = stat(realpath($fullPath)); + } + + return isset($metadata) ? $metadata : false; + } + + /** + * Store a key/value array of metadata at the given path. + * WARNING: This operation overwrites any metadata that is located at + * $destinationPath. + * + * @param string $destinationPath + * @param array $options + * @return void + */ + public function storeMetadata($destinationPath, $metadata, $options = array()) + { + require_once 'Zend/Cloud/OperationNotAvailableException.php'; + throw new Zend_Cloud_OperationNotAvailableException('Storing metadata not implemented'); + } + + /** + * Delete a key/value array of metadata at the given path. + * + * @param string $path + * @param array $options + * @return void + */ + public function deleteMetadata($path) + { + require_once 'Zend/Cloud/OperationNotAvailableException.php'; + throw new Zend_Cloud_OperationNotAvailableException('Deleting metadata not implemented'); + } + + /** + * Return the full path for the file. + * + * @param string $path + * @return string + */ + private function _getFullPath($path) + { + return $this->_directory . DIRECTORY_SEPARATOR . $path; + } + + /** + * Get the concrete client. + * @return strings + */ + public function getClient() + { + return $this->_directory; + } +} diff --git a/library/Zend/Cloud/StorageService/Adapter/Nirvanix.php b/library/Zend/Cloud/StorageService/Adapter/Nirvanix.php new file mode 100644 index 0000000..b96e33e --- /dev/null +++ b/library/Zend/Cloud/StorageService/Adapter/Nirvanix.php @@ -0,0 +1,399 @@ +toArray(); + } + + if (!is_array($options)) { + throw new Zend_Cloud_StorageService_Exception('Invalid options provided'); + } + + $auth = array( + 'username' => $options[self::USERNAME], + 'password' => $options[self::PASSWORD], + 'appKey' => $options[self::APP_KEY], + ); + $nirvanix_options = array(); + if (isset($options[self::HTTP_ADAPTER])) { + $httpc = new Zend_Http_Client(); + $httpc->setAdapter($options[self::HTTP_ADAPTER]); + $nirvanix_options['httpClient'] = $httpc; + } + try { + $this->_nirvanix = new Zend_Service_Nirvanix($auth, $nirvanix_options); + $this->_remoteDirectory = $options[self::REMOTE_DIRECTORY]; + $this->_imfNs = $this->_nirvanix->getService('IMFS'); + $this->_metadataNs = $this->_nirvanix->getService('Metadata'); + } catch (Zend_Service_Nirvanix_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on create: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Get an item from the storage service. + * + * @param string $path + * @param array $options + * @return mixed + */ + public function fetchItem($path, $options = null) + { + $path = $this->_getFullPath($path); + try { + $item = $this->_imfNs->getContents($path); + } catch (Zend_Service_Nirvanix_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on fetch: '.$e->getMessage(), $e->getCode(), $e); + } + return $item; + } + + /** + * Store an item in the storage service. + * WARNING: This operation overwrites any item that is located at + * $destinationPath. + * @param string $destinationPath + * @param mixed $data + * @param array $options + * @return void + */ + public function storeItem($destinationPath, $data, $options = null) + { + try { + $path = $this->_getFullPath($destinationPath); + $this->_imfNs->putContents($path, $data); + } catch (Zend_Service_Nirvanix_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on store: '.$e->getMessage(), $e->getCode(), $e); + } + return true; + } + + /** + * Delete an item in the storage service. + * + * @param string $path + * @param array $options + * @return void + */ + public function deleteItem($path, $options = null) + { + try { + $path = $this->_getFullPath($path); + $this->_imfNs->unlink($path); + } catch(Zend_Service_Nirvanix_Exception $e) { +// if (trim(strtoupper($e->getMessage())) != 'INVALID PATH') { +// // TODO Differentiate among errors in the Nirvanix adapter + throw new Zend_Cloud_StorageService_Exception('Error on delete: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Copy an item in the storage service to a given path. + * WARNING: This operation is *very* expensive for services that do not + * support copying an item natively. + * + * @param string $sourcePath + * @param string $destination path + * @param array $options + * @return void + */ + public function copyItem($sourcePath, $destinationPath, $options = null) + { + try { + $sourcePath = $this->_getFullPath($sourcePath); + $destinationPath = $this->_getFullPath($destinationPath); + $this->_imfNs->CopyFiles(array('srcFilePath' => $sourcePath, + 'destFolderPath' => $destinationPath)); + } catch (Zend_Service_Nirvanix_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on copy: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Move an item in the storage service to a given path. + * WARNING: This operation is *very* expensive for services that do not + * support moving an item natively. + * + * @param string $sourcePath + * @param string $destination path + * @param array $options + * @return void + */ + public function moveItem($sourcePath, $destinationPath, $options = null) + { + try { + $sourcePath = $this->_getFullPath($sourcePath); + $destinationPath = $this->_getFullPath($destinationPath); + $this->_imfNs->RenameFile(array('filePath' => $sourcePath, + 'newFileName' => $destinationPath)); + // $this->_imfNs->MoveFiles(array('srcFilePath' => $sourcePath, + // 'destFolderPath' => $destinationPath)); + } catch (Zend_Service_Nirvanix_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on move: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Rename an item in the storage service to a given name. + * + * + * @param string $path + * @param string $name + * @param array $options + * @return void + */ + public function renameItem($path, $name, $options = null) + { + require_once 'Zend/Cloud/OperationNotAvailableException.php'; + throw new Zend_Cloud_OperationNotAvailableException('Renaming not implemented'); + } + + /** + * Get a key/value array of metadata for the given path. + * + * @param string $path + * @param array $options + * @return array An associative array of key/value pairs specifying the metadata for this object. + * If no metadata exists, an empty array is returned. + */ + public function fetchMetadata($path, $options = null) + { + $path = $this->_getFullPath($path); + try { + $metadataNode = $this->_metadataNs->getMetadata(array('path' => $path)); + } catch (Zend_Service_Nirvanix_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on fetching metadata: '.$e->getMessage(), $e->getCode(), $e); + } + + $metadata = array(); + $length = count($metadataNode->Metadata); + + // Need to special case this as Nirvanix returns an array if there is + // more than one, but doesn't return an array if there is only one. + if ($length == 1) + { + $metadata[(string)$metadataNode->Metadata->Type->value] = (string)$metadataNode->Metadata->Value; + } + else if ($length > 1) + { + for ($i=0; $i<$length; $i++) + { + $metadata[(string)$metadataNode->Metadata[$i]->Type] = (string)$metadataNode->Metadata[$i]->Value; + } + } + return $metadata; + } + + /** + * Store a key/value array of metadata at the given path. + * WARNING: This operation overwrites any metadata that is located at + * $destinationPath. + * + * @param string $destinationPath + * @param array $metadata associative array specifying the key/value pairs for the metadata. + * @param array $options + * @return void + */ + public function storeMetadata($destinationPath, $metadata, $options = null) + { + $destinationPath = $this->_getFullPath($destinationPath); + if ($metadata != null) { + try { + foreach ($metadata AS $key=>$value) { + $metadataString = $key . ":" . $value; + $this->_metadataNs->SetMetadata(array( + 'path' => $destinationPath, + 'metadata' => $metadataString, + )); + } + } catch (Zend_Service_Nirvanix_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on storing metadata: '.$e->getMessage(), $e->getCode(), $e); + } + } + } + + /** + * Delete a key/value array of metadata at the given path. + * + * @param string $path + * @param array $metadata - An associative array specifying the key/value pairs for the metadata + * to be deleted. If null, all metadata associated with the object will + * be deleted. + * @param array $options + * @return void + */ + public function deleteMetadata($path, $metadata = null, $options = null) + { + $path = $this->_getFullPath($path); + try { + if ($metadata == null) { + $this->_metadataNs->DeleteAllMetadata(array('path' => $path)); + } else { + foreach ($metadata AS $key=>$value) { + $this->_metadataNs->DeleteMetadata(array( + 'path' => $path, + 'metadata' => $key, + )); + } + } + } catch (Zend_Service_Nirvanix_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on deleting metadata: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /* + * Recursively traverse all the folders and build an array that contains + * the path names for each folder. + * + * @param string $path folder path to get the list of folders from. + * @param array& $resultArray reference to the array that contains the path names + * for each folder. + */ + private function getAllFolders($path, &$resultArray) + { + $response = $this->_imfNs->ListFolder(array( + 'folderPath' => $path, + 'pageNumber' => 1, + 'pageSize' => $this->maxPageSize, + )); + $numFolders = $response->ListFolder->TotalFolderCount; + if ($numFolders == 0) { + return; + } else { + //Need to special case this as Nirvanix returns an array if there is + //more than one, but doesn't return an array if there is only one. + if ($numFolders == 1) { + $folderPath = $response->ListFolder->Folder->Path; + array_push($resultArray, $folderPath); + $this->getAllFolders('/' . $folderPath, $resultArray); + } else { + foreach ($response->ListFolder->Folder as $arrayElem) { + $folderPath = $arrayElem->Path; + array_push($resultArray, $folderPath); + $this->getAllFolders('/' . $folderPath, $resultArray); + } + } + } + } + + /** + * Return an array of the items contained in the given path. The items + * returned are the files or objects that in the specified path. + * + * @param string $path + * @param array $options + * @return array + */ + public function listItems($path, $options = null) + { + $path = $this->_getFullPath($path); + $resultArray = array(); + + if (!isset($path)) { + return false; + } else { + try { + $response = $this->_imfNs->ListFolder(array( + 'folderPath' => $path, + 'pageNumber' => 1, + 'pageSize' => $this->maxPageSize, + )); + } catch (Zend_Service_Nirvanix_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on list: '.$e->getMessage(), $e->getCode(), $e); + } + + $numFiles = $response->ListFolder->TotalFileCount; + + //Add the file names to the array + if ($numFiles != 0) { + //Need to special case this as Nirvanix returns an array if there is + //more than one, but doesn't return an array if there is only one. + if ($numFiles == 1) { + $resultArray[] = (string)$response->ListFolder->File->Name; + } + else { + foreach ($response->ListFolder->File as $arrayElem) { + $resultArray[] = (string) $arrayElem->Name; + } + } + } + } + + return $resultArray; + } + + /** + * Get full path to an object + * + * @param string $path + * @return string + */ + private function _getFullPath($path) + { + return $this->_remoteDirectory . $path; + } + + /** + * Get the concrete client. + * @return Zend_Service_Nirvanix + */ + public function getClient() + { + return $this->_nirvanix; + } +} diff --git a/library/Zend/Cloud/StorageService/Adapter/Rackspace.php b/library/Zend/Cloud/StorageService/Adapter/Rackspace.php new file mode 100644 index 0000000..1f65841 --- /dev/null +++ b/library/Zend/Cloud/StorageService/Adapter/Rackspace.php @@ -0,0 +1,332 @@ +toArray(); + } + + if (!is_array($options) || empty($options)) { + throw new Zend_Cloud_StorageService_Exception('Invalid options provided'); + } + + try { + $this->_rackspace = new Zend_Service_Rackspace_Files($options[self::USER], $options[self::API_KEY]); + } catch (Zend_Service_Rackspace_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on create: '.$e->getMessage(), $e->getCode(), $e); + } + + if (isset($options[self::HTTP_ADAPTER])) { + $this->_rackspace->getHttpClient()->setAdapter($options[self::HTTP_ADAPTER]); + } + if (!empty($options[self::REMOTE_CONTAINER])) { + $this->_container = $options[self::REMOTE_CONTAINER]; + } + } + + /** + * Get an item from the storage service. + * + * @param string $path + * @param array $options + * @return mixed + */ + public function fetchItem($path, $options = null) + { + $item = $this->_rackspace->getObject($this->_container,$path, $options); + if (!$this->_rackspace->isSuccessful() && ($this->_rackspace->getErrorCode()!='404')) { + throw new Zend_Cloud_StorageService_Exception('Error on fetch: '.$this->_rackspace->getErrorMsg()); + } + if (!empty($item)) { + return $item->getContent(); + } else { + return false; + } + } + + /** + * Store an item in the storage service. + * + * @param string $destinationPath + * @param mixed $data + * @param array $options + * @return void + */ + public function storeItem($destinationPath, $data, $options = null) + { + $this->_rackspace->storeObject($this->_container,$destinationPath,$data,$options); + if (!$this->_rackspace->isSuccessful()) { + throw new Zend_Cloud_StorageService_Exception('Error on store: '.$this->_rackspace->getErrorMsg()); + } + } + + /** + * Delete an item in the storage service. + * + * @param string $path + * @param array $options + * @return void + */ + public function deleteItem($path, $options = null) + { + $this->_rackspace->deleteObject($this->_container,$path); + if (!$this->_rackspace->isSuccessful()) { + throw new Zend_Cloud_StorageService_Exception('Error on delete: '.$this->_rackspace->getErrorMsg()); + } + } + + /** + * Copy an item in the storage service to a given path. + * + * @param string $sourcePath + * @param string $destination path + * @param array $options + * @return void + */ + public function copyItem($sourcePath, $destinationPath, $options = null) + { + $this->_rackspace->copyObject($this->_container,$sourcePath,$this->_container,$destinationPath,$options); + if (!$this->_rackspace->isSuccessful()) { + throw new Zend_Cloud_StorageService_Exception('Error on copy: '.$this->_rackspace->getErrorMsg()); + } + } + + /** + * Move an item in the storage service to a given path. + * WARNING: This operation is *very* expensive for services that do not + * support moving an item natively. + * + * @param string $sourcePath + * @param string $destination path + * @param array $options + * @return void + */ + public function moveItem($sourcePath, $destinationPath, $options = null) + { + try { + $this->copyItem($sourcePath, $destinationPath, $options); + } catch (Zend_Service_Rackspace_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on move: '.$e->getMessage()); + } + try { + $this->deleteItem($sourcePath); + } catch (Zend_Service_Rackspace_Exception $e) { + $this->deleteItem($destinationPath); + throw new Zend_Cloud_StorageService_Exception('Error on move: '.$e->getMessage()); + } + } + + /** + * Rename an item in the storage service to a given name. + * + * @param string $path + * @param string $name + * @param array $options + * @return void + */ + public function renameItem($path, $name, $options = null) + { + require_once 'Zend/Cloud/OperationNotAvailableException.php'; + throw new Zend_Cloud_OperationNotAvailableException('Renaming not implemented'); + } + + /** + * Get a key/value array of metadata for the given path. + * + * @param string $path + * @param array $options + * @return array An associative array of key/value pairs specifying the metadata for this object. + * If no metadata exists, an empty array is returned. + */ + public function fetchMetadata($path, $options = null) + { + $result = $this->_rackspace->getMetadataObject($this->_container,$path); + if (!$this->_rackspace->isSuccessful()) { + throw new Zend_Cloud_StorageService_Exception('Error on fetch metadata: '.$this->_rackspace->getErrorMsg()); + } + $metadata = array(); + if (isset($result['metadata'])) { + $metadata = $result['metadata']; + } + // delete the self::DELETE_METADATA_KEY - this is a trick to remove all + // the metadata information of an object (see deleteMetadata). + // Rackspace doesn't have an API to remove the metadata of an object + unset($metadata[self::DELETE_METADATA_KEY]); + return $metadata; + } + + /** + * Store a key/value array of metadata at the given path. + * WARNING: This operation overwrites any metadata that is located at + * $destinationPath. + * + * @param string $destinationPath + * @param array $metadata associative array specifying the key/value pairs for the metadata. + * @param array $options + * @return void + */ + public function storeMetadata($destinationPath, $metadata, $options = null) + { + $this->_rackspace->setMetadataObject($this->_container, $destinationPath, $metadata); + if (!$this->_rackspace->isSuccessful()) { + throw new Zend_Cloud_StorageService_Exception('Error on store metadata: '.$this->_rackspace->getErrorMsg()); + } + } + + /** + * Delete a key/value array of metadata at the given path. + * + * @param string $path + * @param array $metadata - An associative array specifying the key/value pairs for the metadata + * to be deleted. If null, all metadata associated with the object will + * be deleted. + * @param array $options + * @return void + */ + public function deleteMetadata($path, $metadata = null, $options = null) + { + if (empty($metadata)) { + $newMetadata = array(self::DELETE_METADATA_KEY => true); + try { + $this->storeMetadata($path, $newMetadata); + } catch (Zend_Service_Rackspace_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on delete metadata: '.$e->getMessage()); + } + } else { + try { + $oldMetadata = $this->fetchMetadata($path); + } catch (Zend_Service_Rackspace_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on delete metadata: '.$e->getMessage()); + } + $newMetadata = array_diff_assoc($oldMetadata, $metadata); + try { + $this->storeMetadata($path, $newMetadata); + } catch (Zend_Service_Rackspace_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on delete metadata: '.$e->getMessage()); + } + } + } + + /* + * Recursively traverse all the folders and build an array that contains + * the path names for each folder. + * + * @param string $path folder path to get the list of folders from. + * @param array& $resultArray reference to the array that contains the path names + * for each folder. + * @return void + */ + private function getAllFolders($path, &$resultArray) + { + if (!empty($path)) { + $options = array ( + 'prefix' => $path + ); + } + $files = $this->_rackspace->getObjects($this->_container,$options); + if (!$this->_rackspace->isSuccessful()) { + throw new Zend_Cloud_StorageService_Exception('Error on get all folders: '.$this->_rackspace->getErrorMsg()); + } + $resultArray = array(); + foreach ($files as $file) { + $resultArray[dirname($file->getName())] = true; + } + $resultArray = array_keys($resultArray); + } + + /** + * Return an array of the items contained in the given path. The items + * returned are the files or objects that in the specified path. + * + * @param string $path + * @param array $options + * @return array + */ + public function listItems($path, $options = null) + { + if (!empty($path)) { + $options = array ( + 'prefix' => $path + ); + } + + $files = $this->_rackspace->getObjects($this->_container,$options); + if (!$this->_rackspace->isSuccessful()) { + throw new Zend_Cloud_StorageService_Exception('Error on list items: '.$this->_rackspace->getErrorMsg()); + } + $resultArray = array(); + if (!empty($files)) { + foreach ($files as $file) { + $resultArray[] = $file->getName(); + } + } + return $resultArray; + } + + /** + * Get the concrete client. + * + * @return Zend_Service_Rackspace_File + */ + public function getClient() + { + return $this->_rackspace; + } +} diff --git a/library/Zend/Cloud/StorageService/Adapter/S3.php b/library/Zend/Cloud/StorageService/Adapter/S3.php new file mode 100644 index 0000000..c39b6c5 --- /dev/null +++ b/library/Zend/Cloud/StorageService/Adapter/S3.php @@ -0,0 +1,332 @@ +toArray(); + } + + if (!is_array($options)) { + throw new Zend_Cloud_StorageService_Exception('Invalid options provided'); + } + + if (!isset($options[self::AWS_ACCESS_KEY]) || !isset($options[self::AWS_SECRET_KEY])) { + throw new Zend_Cloud_StorageService_Exception('AWS keys not specified!'); + } + + try { + $this->_s3 = new Zend_Service_Amazon_S3($options[self::AWS_ACCESS_KEY], + $options[self::AWS_SECRET_KEY]); + } catch (Zend_Service_Amazon_S3_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on create: '.$e->getMessage(), $e->getCode(), $e); + } + + if (isset($options[self::HTTP_ADAPTER])) { + $this->_s3->getHttpClient()->setAdapter($options[self::HTTP_ADAPTER]); + } + + if (isset($options[self::BUCKET_NAME])) { + $this->_defaultBucketName = $options[self::BUCKET_NAME]; + } + + if (isset($options[self::BUCKET_AS_DOMAIN])) { + $this->_defaultBucketAsDomain = $options[self::BUCKET_AS_DOMAIN]; + } + } + + /** + * Get an item from the storage service. + * + * @TODO Support streams + * + * @param string $path + * @param array $options + * @return string + */ + public function fetchItem($path, $options = array()) + { + $fullPath = $this->_getFullPath($path, $options); + try { + if (!empty($options[self::FETCH_STREAM])) { + return $this->_s3->getObjectStream($fullPath, $options[self::FETCH_STREAM]); + } else { + return $this->_s3->getObject($fullPath); + } + } catch (Zend_Service_Amazon_S3_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on fetch: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Store an item in the storage service. + * + * WARNING: This operation overwrites any item that is located at + * $destinationPath. + * + * @TODO Support streams + * + * @param string $destinationPath + * @param string|resource $data + * @param array $options + * @return void + */ + public function storeItem($destinationPath, $data, $options = array()) + { + try { + $fullPath = $this->_getFullPath($destinationPath, $options); + return $this->_s3->putObject( + $fullPath, + $data, + empty($options[self::METADATA]) ? null : $options[self::METADATA] + ); + } catch (Zend_Service_Amazon_S3_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on store: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Delete an item in the storage service. + * + * @param string $path + * @param array $options + * @return void + */ + public function deleteItem($path, $options = array()) + { + try { + $this->_s3->removeObject($this->_getFullPath($path, $options)); + } catch (Zend_Service_Amazon_S3_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on delete: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Copy an item in the storage service to a given path. + * + * WARNING: This operation is *very* expensive for services that do not + * support copying an item natively. + * + * @TODO Support streams for those services that don't support natively + * + * @param string $sourcePath + * @param string $destination path + * @param array $options + * @return void + */ + public function copyItem($sourcePath, $destinationPath, $options = array()) + { + try { + $fullSourcePath = $this->_getFullPath($sourcePath, $options); + $fullDestPath = $this->_getFullPath($destinationPath, $options); + return $this->_s3->copyObject( + $fullSourcePath, + $fullDestPath, + empty($options[self::METADATA]) ? null : $options[self::METADATA] + ); + + } catch (Zend_Service_Amazon_S3_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on copy: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Move an item in the storage service to a given path. + * + * @TODO Support streams for those services that don't support natively + * + * @param string $sourcePath + * @param string $destination path + * @param array $options + * @return void + */ + public function moveItem($sourcePath, $destinationPath, $options = array()) + { + try { + $fullSourcePath = $this->_getFullPath($sourcePath, $options); + $fullDestPath = $this->_getFullPath($destinationPath, $options); + return $this->_s3->moveObject( + $fullSourcePath, + $fullDestPath, + empty($options[self::METADATA]) ? null : $options[self::METADATA] + ); + } catch (Zend_Service_Amazon_S3_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on move: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Rename an item in the storage service to a given name. + * + * + * @param string $path + * @param string $name + * @param array $options + * @return void + */ + public function renameItem($path, $name, $options = null) + { + require_once 'Zend/Cloud/OperationNotAvailableException.php'; + throw new Zend_Cloud_OperationNotAvailableException('Rename not implemented'); + } + + /** + * List items in the given directory in the storage service + * + * The $path must be a directory + * + * + * @param string $path Must be a directory + * @param array $options + * @return array A list of item names + */ + public function listItems($path, $options = null) + { + try { + // TODO Support 'prefix' parameter for Zend_Service_Amazon_S3::getObjectsByBucket() + return $this->_s3->getObjectsByBucket($this->_defaultBucketName); + } catch (Zend_Service_Amazon_S3_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on list: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Get a key/value array of metadata for the given path. + * + * @param string $path + * @param array $options + * @return array + */ + public function fetchMetadata($path, $options = array()) + { + try { + return $this->_s3->getInfo($this->_getFullPath($path, $options)); + } catch (Zend_Service_Amazon_S3_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on fetch: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Store a key/value array of metadata at the given path. + * WARNING: This operation overwrites any metadata that is located at + * $destinationPath. + * + * @param string $destinationPath + * @param array $options + * @return void + */ + public function storeMetadata($destinationPath, $metadata, $options = array()) + { + require_once 'Zend/Cloud/OperationNotAvailableException.php'; + throw new Zend_Cloud_OperationNotAvailableException('Storing separate metadata is not supported, use storeItem() with \'metadata\' option key'); + } + + /** + * Delete a key/value array of metadata at the given path. + * + * @param string $path + * @param array $options + * @return void + */ + public function deleteMetadata($path) + { + require_once 'Zend/Cloud/OperationNotAvailableException.php'; + throw new Zend_Cloud_OperationNotAvailableException('Deleting metadata not supported'); + } + + /** + * Get full path, including bucket, for an object + * + * @param string $path + * @param array $options + * @return void + */ + protected function _getFullPath($path, $options) + { + if (isset($options[self::BUCKET_NAME])) { + $bucket = $options[self::BUCKET_NAME]; + } else if (isset($this->_defaultBucketName)) { + $bucket = $this->_defaultBucketName; + } else { + require_once 'Zend/Cloud/StorageService/Exception.php'; + throw new Zend_Cloud_StorageService_Exception('Bucket name must be specified for S3 adapter.'); + } + + if (isset($options[self::BUCKET_AS_DOMAIN])) { + // TODO: support bucket domain names + require_once 'Zend/Cloud/StorageService/Exception.php'; + throw new Zend_Cloud_StorageService_Exception('The S3 adapter does not currently support buckets in domain names.'); + } + + return trim($bucket) . '/' . trim($path); + } + + /** + * Get the concrete client. + * @return Zend_Service_Amazon_S3 + */ + public function getClient() + { + return $this->_s3; + } +} diff --git a/library/Zend/Cloud/StorageService/Adapter/WindowsAzure.php b/library/Zend/Cloud/StorageService/Adapter/WindowsAzure.php new file mode 100644 index 0000000..05ac6ba --- /dev/null +++ b/library/Zend/Cloud/StorageService/Adapter/WindowsAzure.php @@ -0,0 +1,443 @@ +toArray(); + } + + if (!is_array($options)) { + throw new Zend_Cloud_StorageService_Exception('Invalid options provided'); + } + + // Build Zend_Service_WindowsAzure_Storage_Blob instance + if (!isset($options[self::HOST])) { + $host = self::DEFAULT_HOST; + } else { + $host = $options[self::HOST]; + } + + if (!isset($options[self::ACCOUNT_NAME])) { + throw new Zend_Cloud_StorageService_Exception('No Windows Azure account name provided.'); + } + if (!isset($options[self::ACCOUNT_KEY])) { + throw new Zend_Cloud_StorageService_Exception('No Windows Azure account key provided.'); + } + + $this->_storageClient = new Zend_Service_WindowsAzure_Storage_Blob($host, + $options[self::ACCOUNT_NAME], $options[self::ACCOUNT_KEY]); + + // Parse other options + if (!empty($options[self::PROXY_HOST])) { + $proxyHost = $options[self::PROXY_HOST]; + $proxyPort = isset($options[self::PROXY_PORT]) ? $options[self::PROXY_PORT] : 8080; + $proxyCredentials = isset($options[self::PROXY_CREDENTIALS]) ? $options[self::PROXY_CREDENTIALS] : ''; + + $this->_storageClient->setProxy(true, $proxyHost, $proxyPort, $proxyCredentials); + } + + if (isset($options[self::HTTP_ADAPTER])) { + $this->_storageClient->setHttpClientChannel($options[self::HTTP_ADAPTER]); + } + + // Set container + $this->_container = $options[self::CONTAINER]; + + // Make sure the container exists + if (!$this->_storageClient->containerExists($this->_container)) { + $this->_storageClient->createContainer($this->_container); + } + } + + /** + * Get an item from the storage service. + * + * @param string $path + * @param array $options + * @return mixed + */ + public function fetchItem($path, $options = null) + { + // Options + $returnType = self::RETURN_STRING; + $returnPath = tempnam('', 'azr'); + $openMode = 'r'; + + // Parse options + if (is_array($options)) { + if (isset($options[self::RETURN_TYPE])) { + $returnType = $options[self::RETURN_TYPE]; + } + + if (isset($options[self::RETURN_PATHNAME])) { + $returnPath = $options[self::RETURN_PATHNAME]; + } + + if (isset($options[self::RETURN_OPENMODE])) { + $openMode = $options[self::RETURN_OPENMODE]; + } + } + + // Fetch the blob + try { + $this->_storageClient->getBlob( + $this->_container, + $path, + $returnPath + ); + } catch (Zend_Service_WindowsAzure_Exception $e) { + if (strpos($e->getMessage(), "does not exist") !== false) { + return false; + } + throw new Zend_Cloud_StorageService_Exception('Error on fetch: '.$e->getMessage(), $e->getCode(), $e); + } + + // Return value + if ($returnType == self::RETURN_PATH) { + return $returnPath; + } + if ($returnType == self::RETURN_STRING) { + return file_get_contents($returnPath); + } + if ($returnType == self::RETURN_STREAM) { + return fopen($returnPath, $openMode); + } + } + + /** + * Store an item in the storage service. + * WARNING: This operation overwrites any item that is located at + * $destinationPath. + * @param string $destinationPath + * @param mixed $data + * @param array $options + * @return boolean + */ + public function storeItem($destinationPath, $data, $options = null) + { + // Create a temporary file that will be uploaded + $temporaryFilePath = ''; + $removeTemporaryFilePath = false; + + if (is_resource($data)) { + $temporaryFilePath = tempnam('', 'azr'); + $fpDestination = fopen($temporaryFilePath, 'w'); + + $fpSource = $data; + rewind($fpSource); + while (!feof($fpSource)) { + fwrite($fpDestination, fread($fpSource, 8192)); + } + + fclose($fpDestination); + + $removeTemporaryFilePath = true; + } elseif (file_exists($data)) { + $temporaryFilePath = $data; + $removeTemporaryFilePath = false; + } else { + $temporaryFilePath = tempnam('', 'azr'); + file_put_contents($temporaryFilePath, $data); + $removeTemporaryFilePath = true; + } + + try { + // Upload data + $this->_storageClient->putBlob( + $this->_container, + $destinationPath, + $temporaryFilePath + ); + } catch(Zend_Service_WindowsAzure_Exception $e) { + @unlink($temporaryFilePath); + throw new Zend_Cloud_StorageService_Exception('Error on store: '.$e->getMessage(), $e->getCode(), $e); + } + if ($removeTemporaryFilePath) { + @unlink($temporaryFilePath); + } + } + + /** + * Delete an item in the storage service. + * + * @param string $path + * @param array $options + * @return void + */ + public function deleteItem($path, $options = null) + { + try { + $this->_storageClient->deleteBlob( + $this->_container, + $path + ); + } catch (Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on delete: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Copy an item in the storage service to a given path. + * + * @param string $sourcePath + * @param string $destinationPath + * @param array $options + * @return void + */ + public function copyItem($sourcePath, $destinationPath, $options = null) + { + try { + $this->_storageClient->copyBlob( + $this->_container, + $sourcePath, + $this->_container, + $destinationPath + ); + } catch (Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on copy: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Move an item in the storage service to a given path. + * + * @param string $sourcePath + * @param string $destinationPath + * @param array $options + * @return void + */ + public function moveItem($sourcePath, $destinationPath, $options = null) + { + try { + $this->_storageClient->copyBlob( + $this->_container, + $sourcePath, + $this->_container, + $destinationPath + ); + + $this->_storageClient->deleteBlob( + $this->_container, + $sourcePath + ); + } catch (Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on move: '.$e->getMessage(), $e->getCode(), $e); + } + + } + + /** + * Rename an item in the storage service to a given name. + * + * + * @param string $path + * @param string $name + * @param array $options + * @return void + */ + public function renameItem($path, $name, $options = null) + { + return $this->moveItem($path, $name, $options); + } + + /** + * List items in the given directory in the storage service + * + * The $path must be a directory + * + * + * @param string $path Must be a directory + * @param array $options + * @return array A list of item names + */ + public function listItems($path, $options = null) + { + // Options + $returnType = self::RETURN_NAMES; // 1: return list of paths, 2: return raw output from underlying provider + + // Parse options + if (is_array($options)&& isset($options[self::RETURN_TYPE])) { + $returnType = $options[self::RETURN_TYPE]; + } + + try { + // Fetch list + $blobList = $this->_storageClient->listBlobs( + $this->_container, + $path + ); + } catch (Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on list: '.$e->getMessage(), $e->getCode(), $e); + } + + // Return + if ($returnType == self::RETURN_LIST) { + return $blobList; + } + + $returnValue = array(); + foreach ($blobList as $blob) { + $returnValue[] = $blob->Name; + } + + return $returnValue; + } + + /** + * Get a key/value array of metadata for the given path. + * + * @param string $path + * @param array $options + * @return array + */ + public function fetchMetadata($path, $options = null) + { + try { + return $this->_storageClient->getBlobMetaData( + $this->_container, + $path + ); + } catch (Zend_Service_WindowsAzure_Exception $e) { + if (strpos($e->getMessage(), "could not be accessed") !== false) { + return false; + } + throw new Zend_Cloud_StorageService_Exception('Error on fetch: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Store a key/value array of metadata at the given path. + * WARNING: This operation overwrites any metadata that is located at + * $destinationPath. + * + * @param string $destinationPath + * @param array $options + * @return void + */ + public function storeMetadata($destinationPath, $metadata, $options = null) + { + try { + $this->_storageClient->setBlobMetadata($this->_container, $destinationPath, $metadata); + } catch (Zend_Service_WindowsAzure_Exception $e) { + if (strpos($e->getMessage(), "could not be accessed") === false) { + throw new Zend_Cloud_StorageService_Exception('Error on store metadata: '.$e->getMessage(), $e->getCode(), $e); + } + } + } + + /** + * Delete a key/value array of metadata at the given path. + * + * @param string $path + * @param array $options + * @return void + */ + public function deleteMetadata($path, $options = null) + { + try { + $this->_storageClient->setBlobMetadata($this->_container, $destinationPath, array()); + } catch (Zend_Service_WindowsAzure_Exception $e) { + if (strpos($e->getMessage(), "could not be accessed") === false) { + throw new Zend_Cloud_StorageService_Exception('Error on delete metadata: '.$e->getMessage(), $e->getCode(), $e); + } + } + } + + /** + * Delete container + * + * @return void + */ + public function deleteContainer() + { + try { + $this->_storageClient->deleteContainer($this->_container); + } catch (Zend_Service_WindowsAzure_Exception $e) { + throw new Zend_Cloud_StorageService_Exception('Error on delete: '.$e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Get the concrete adapter. + * @return Zend_Service_Azure_Storage_Blob + */ + public function getClient() + { + return $this->_storageClient; + } +} diff --git a/library/Zend/Cloud/StorageService/Exception.php b/library/Zend/Cloud/StorageService/Exception.php new file mode 100644 index 0000000..30ea7aa --- /dev/null +++ b/library/Zend/Cloud/StorageService/Exception.php @@ -0,0 +1,38 @@ +_init(); + if ($options != null) { + // use Zend_Config objects if provided + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } + // pass arrays to setOptions + if (is_array($options)) { + $this->setOptions($options); + } + } + $this->_prepare(); + } + + /** + * setConfig() + * + * @param Zend_Config $config + * @return Zend_CodeGenerator_Abstract + */ + public function setConfig(Zend_Config $config) + { + $this->setOptions($config->toArray()); + return $this; + } + + /** + * setOptions() + * + * @param array $options + * @return Zend_CodeGenerator_Abstract + */ + public function setOptions(Array $options) + { + foreach ($options as $optionName => $optionValue) { + $methodName = 'set' . $optionName; + if (method_exists($this, $methodName)) { + $this->{$methodName}($optionValue); + } + } + return $this; + } + + /** + * setSourceContent() + * + * @param string $sourceContent + */ + public function setSourceContent($sourceContent) + { + $this->_sourceContent = $sourceContent; + return; + } + + /** + * getSourceContent() + * + * @return string + */ + public function getSourceContent() + { + return $this->_sourceContent; + } + + /** + * _init() - this is called before the constuctor + * + */ + protected function _init() + { + + } + + /** + * _prepare() - this is called at construction completion + * + */ + protected function _prepare() + { + + } + + /** + * generate() - must be implemented by the child + * + */ + abstract public function generate(); + + /** + * __toString() - casting to a string will in turn call generate() + * + * @return string + */ + final public function __toString() + { + return $this->generate(); + } + +} diff --git a/library/Zend/CodeGenerator/Exception.php b/library/Zend/CodeGenerator/Exception.php new file mode 100644 index 0000000..872790c --- /dev/null +++ b/library/Zend/CodeGenerator/Exception.php @@ -0,0 +1,35 @@ +_isSourceDirty = ($isSourceDirty) ? true : false; + return $this; + } + + /** + * isSourceDirty() + * + * @return bool + */ + public function isSourceDirty() + { + return $this->_isSourceDirty; + } + + /** + * setIndentation() + * + * @param string|int $indentation + * @return Zend_CodeGenerator_Php_Abstract + */ + public function setIndentation($indentation) + { + $this->_indentation = $indentation; + return $this; + } + + /** + * getIndentation() + * + * @return string|int + */ + public function getIndentation() + { + return $this->_indentation; + } + +} diff --git a/library/Zend/CodeGenerator/Php/Body.php b/library/Zend/CodeGenerator/Php/Body.php new file mode 100644 index 0000000..71e5d56 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Body.php @@ -0,0 +1,73 @@ +_content = $content; + return $this; + } + + /** + * getContent() + * + * @return string + */ + public function getContent() + { + return (string) $this->_content; + } + + /** + * generate() + * + * @return string + */ + public function generate() + { + return $this->getContent(); + } +} diff --git a/library/Zend/CodeGenerator/Php/Class.php b/library/Zend/CodeGenerator/Php/Class.php new file mode 100644 index 0000000..beeed63 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Class.php @@ -0,0 +1,618 @@ +setSourceContent($class->getSourceContent()); + $class->setSourceDirty(false); + + if ($reflectionClass->getDocComment() != '') { + $class->setDocblock(Zend_CodeGenerator_Php_Docblock::fromReflection($reflectionClass->getDocblock())); + } + + $class->setAbstract($reflectionClass->isAbstract()); + $class->setName($reflectionClass->getName()); + + if ($parentClass = $reflectionClass->getParentClass()) { + $class->setExtendedClass($parentClass->getName()); + $interfaces = array_diff($reflectionClass->getInterfaces(), $parentClass->getInterfaces()); + } else { + $interfaces = $reflectionClass->getInterfaces(); + } + + $interfaceNames = array(); + foreach($interfaces AS $interface) { + $interfaceNames[] = $interface->getName(); + } + + $class->setImplementedInterfaces($interfaceNames); + + $properties = array(); + foreach ($reflectionClass->getProperties() as $reflectionProperty) { + if ($reflectionProperty->getDeclaringClass()->getName() == $class->getName()) { + $properties[] = Zend_CodeGenerator_Php_Property::fromReflection($reflectionProperty); + } + } + $class->setProperties($properties); + + $methods = array(); + foreach ($reflectionClass->getMethods() as $reflectionMethod) { + if ($reflectionMethod->getDeclaringClass()->getName() == $class->getName()) { + $methods[] = Zend_CodeGenerator_Php_Method::fromReflection($reflectionMethod); + } + } + $class->setMethods($methods); + + return $class; + } + + /** + * setDocblock() Set the docblock + * + * @param Zend_CodeGenerator_Php_Docblock|array|string $docblock + * @return Zend_CodeGenerator_Php_File + */ + public function setDocblock($docblock) + { + if (is_string($docblock)) { + $docblock = array('shortDescription' => $docblock); + } + + if (is_array($docblock)) { + $docblock = new Zend_CodeGenerator_Php_Docblock($docblock); + } elseif ((!is_null($docblock)) && (!$docblock instanceof Zend_CodeGenerator_Php_Docblock)) { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('setDocblock() is expecting either a string, array or an instance of Zend_CodeGenerator_Php_Docblock'); + } + + $this->_docblock = $docblock; + return $this; + } + + /** + * getDocblock() + * + * @return Zend_CodeGenerator_Php_Docblock + */ + public function getDocblock() + { + return $this->_docblock; + } + + /** + * setName() + * + * @param string $name + * @return Zend_CodeGenerator_Php_Class + */ + public function setName($name) + { + $this->_name = $name; + return $this; + } + + /** + * getName() + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * setAbstract() + * + * @param bool $isAbstract + * @return Zend_CodeGenerator_Php_Class + */ + public function setAbstract($isAbstract) + { + $this->_isAbstract = ($isAbstract) ? true : false; + return $this; + } + + /** + * isAbstract() + * + * @return bool + */ + public function isAbstract() + { + return $this->_isAbstract; + } + + /** + * setExtendedClass() + * + * @param string $extendedClass + * @return Zend_CodeGenerator_Php_Class + */ + public function setExtendedClass($extendedClass) + { + $this->_extendedClass = $extendedClass; + return $this; + } + + /** + * getExtendedClass() + * + * @return string + */ + public function getExtendedClass() + { + return $this->_extendedClass; + } + + /** + * setImplementedInterfaces() + * + * @param array $implementedInterfaces + * @return Zend_CodeGenerator_Php_Class + */ + public function setImplementedInterfaces(Array $implementedInterfaces) + { + $this->_implementedInterfaces = $implementedInterfaces; + return $this; + } + + /** + * getImplementedInterfaces + * + * @return array + */ + public function getImplementedInterfaces() + { + return $this->_implementedInterfaces; + } + + /** + * setProperties() + * + * @param array $properties + * @return Zend_CodeGenerator_Php_Class + */ + public function setProperties(Array $properties) + { + foreach ($properties as $property) { + $this->setProperty($property); + } + + return $this; + } + + /** + * setConstants() + * + * @param array $constants + * @return Zend_CodeGenerator_Php_Class + */ + public function setConstants(Array $constants) + { + foreach ($constants as $const) { + $this->setConstant($const); + } + + return $this; + } + + /** + * setProperty() + * + * @param array|Zend_CodeGenerator_Php_Property $property + * @return Zend_CodeGenerator_Php_Class + */ + public function setProperty($property) + { + if (is_array($property)) { + $property = new Zend_CodeGenerator_Php_Property($property); + $propertyName = $property->getName(); + } elseif ($property instanceof Zend_CodeGenerator_Php_Property) { + $propertyName = $property->getName(); + } else { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('setProperty() expects either an array of property options or an instance of Zend_CodeGenerator_Php_Property'); + } + + if ($property->isConst()) { + return $this->setConstant($property); + } + if (isset($this->_properties[$propertyName])) { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('A property by name ' . $propertyName . ' already exists in this class.'); + } + + $this->_properties[$propertyName] = $property; + return $this; + } + + /** + * setConstant() + * + * @param array|Zend_CodeGenerator_Php_Property $const + * @return Zend_CodeGenerator_Php_Class + */ + public function setConstant($const) + { + if (is_array($const)) { + $const = new Zend_CodeGenerator_Php_Property($const); + $constName = $const->getName(); + } elseif ($const instanceof Zend_CodeGenerator_Php_Property) { + $constName = $const->getName(); + } else { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('setConstant() expects either an array of property options or an instance of Zend_CodeGenerator_Php_Property'); + } + + if (!$const->isConst()) { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('setProperty() expects argument to define a constant'); + } + if (isset($this->_constants[$constName])) { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('A constant by name ' . $constName . ' already exists in this class.'); + } + + $this->_constants[$constName] = $const; + return $this; + } + + /** + * getProperties() + * + * @return array + */ + public function getProperties() + { + return $this->_properties; + } + + /** + * getConstants() + * + * @return array + */ + public function getConstants() + { + return $this->_constants; + } + + /** + * getProperty() + * + * @param string $propertyName + * @return Zend_CodeGenerator_Php_Property + */ + public function getProperty($propertyName) + { + foreach ($this->_properties as $property) { + if ($property->getName() == $propertyName) { + return $property; + } + } + return false; + } + + /** + * getConstant() + * + * @param string $constName + * @return Zend_CodeGenerator_Php_Property + */ + public function getConstant($constName) + { + foreach ($this->_constants as $const) { + if ($const->getName() == $constName) { + return $const; + } + } + return false; + } + + /** + * hasProperty() + * + * @param string $propertyName + * @return bool + */ + public function hasProperty($propertyName) + { + return isset($this->_properties[$propertyName]); + } + + /** + * hasConstant() + * + * @param string $constName + * @return bool + */ + public function hasConstant($constName) + { + return isset($this->_constants[$constName]); + } + + /** + * setMethods() + * + * @param array $methods + * @return Zend_CodeGenerator_Php_Class + */ + public function setMethods(Array $methods) + { + foreach ($methods as $method) { + $this->setMethod($method); + } + return $this; + } + + /** + * setMethod() + * + * @param array|Zend_CodeGenerator_Php_Method $method + * @return Zend_CodeGenerator_Php_Class + */ + public function setMethod($method) + { + if (is_array($method)) { + $method = new Zend_CodeGenerator_Php_Method($method); + $methodName = $method->getName(); + } elseif ($method instanceof Zend_CodeGenerator_Php_Method) { + $methodName = $method->getName(); + } else { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('setMethod() expects either an array of method options or an instance of Zend_CodeGenerator_Php_Method'); + } + + if (isset($this->_methods[$methodName])) { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('A method by name ' . $methodName . ' already exists in this class.'); + } + + $this->_methods[$methodName] = $method; + return $this; + } + + /** + * getMethods() + * + * @return array + */ + public function getMethods() + { + return $this->_methods; + } + + /** + * getMethod() + * + * @param string $methodName + * @return Zend_CodeGenerator_Php_Method + */ + public function getMethod($methodName) + { + foreach ($this->_methods as $method) { + if ($method->getName() == $methodName) { + return $method; + } + } + return false; + } + + /** + * hasMethod() + * + * @param string $methodName + * @return bool + */ + public function hasMethod($methodName) + { + return isset($this->_methods[$methodName]); + } + + /** + * isSourceDirty() + * + * @return bool + */ + public function isSourceDirty() + { + if (($docblock = $this->getDocblock()) && $docblock->isSourceDirty()) { + return true; + } + + foreach ($this->_properties as $property) { + if ($property->isSourceDirty()) { + return true; + } + } + + foreach ($this->_constants as $constant) { + if ($constant->isSourceDirty()) { + return true; + } + } + + foreach ($this->_methods as $method) { + if ($method->isSourceDirty()) { + return true; + } + } + + return parent::isSourceDirty(); + } + + /** + * generate() + * + * @return string + */ + public function generate() + { + if (!$this->isSourceDirty()) { + return $this->getSourceContent(); + } + + $output = ''; + + if (null !== ($docblock = $this->getDocblock())) { + $docblock->setIndentation(''); + $output .= $docblock->generate(); + } + + if ($this->isAbstract()) { + $output .= 'abstract '; + } + + $output .= 'class ' . $this->getName(); + + if ( !empty( $this->_extendedClass) ) { + $output .= ' extends ' . $this->_extendedClass; + } + + $implemented = $this->getImplementedInterfaces(); + if (!empty($implemented)) { + $output .= ' implements ' . implode(', ', $implemented); + } + + $output .= self::LINE_FEED . '{' . self::LINE_FEED . self::LINE_FEED; + + $constants = $this->getConstants(); + if (!empty($constants)) { + foreach ($constants as $const) { + $output .= $const->generate() . self::LINE_FEED . self::LINE_FEED; + } + } + + $properties = $this->getProperties(); + if (!empty($properties)) { + foreach ($properties as $property) { + $output .= $property->generate() . self::LINE_FEED . self::LINE_FEED; + } + } + + $methods = $this->getMethods(); + if (!empty($methods)) { + foreach ($methods as $method) { + $output .= $method->generate() . self::LINE_FEED; + } + } + + $output .= self::LINE_FEED . '}' . self::LINE_FEED; + + return $output; + } + + /** + * _init() - is called at construction time + * + */ + protected function _init() + { + $this->_properties = new Zend_CodeGenerator_Php_Member_Container(Zend_CodeGenerator_Php_Member_Container::TYPE_PROPERTY); + $this->_constants = new Zend_CodeGenerator_Php_Member_Container(Zend_CodeGenerator_Php_Member_Container::TYPE_PROPERTY); + $this->_methods = new Zend_CodeGenerator_Php_Member_Container(Zend_CodeGenerator_Php_Member_Container::TYPE_METHOD); + } + +} diff --git a/library/Zend/CodeGenerator/Php/Docblock.php b/library/Zend/CodeGenerator/Php/Docblock.php new file mode 100644 index 0000000..6c0b16e --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Docblock.php @@ -0,0 +1,224 @@ +setSourceContent($reflectionDocblock->getContents()); + $docblock->setSourceDirty(false); + + $docblock->setShortDescription($reflectionDocblock->getShortDescription()); + $docblock->setLongDescription($reflectionDocblock->getLongDescription()); + + foreach ($reflectionDocblock->getTags() as $tag) { + $docblock->setTag(Zend_CodeGenerator_Php_Docblock_Tag::fromReflection($tag)); + } + + return $docblock; + } + + /** + * setShortDescription() + * + * @param string $shortDescription + * @return Zend_CodeGenerator_Php_Docblock + */ + public function setShortDescription($shortDescription) + { + $this->_shortDescription = $shortDescription; + return $this; + } + + /** + * getShortDescription() + * + * @return string + */ + public function getShortDescription() + { + return $this->_shortDescription; + } + + /** + * setLongDescription() + * + * @param string $longDescription + * @return Zend_CodeGenerator_Php_Docblock + */ + public function setLongDescription($longDescription) + { + $this->_longDescription = $longDescription; + return $this; + } + + /** + * getLongDescription() + * + * @return string + */ + public function getLongDescription() + { + return $this->_longDescription; + } + + /** + * setTags() + * + * @param array $tags + * @return Zend_CodeGenerator_Php_Docblock + */ + public function setTags(Array $tags) + { + foreach ($tags as $tag) { + $this->setTag($tag); + } + + return $this; + } + + /** + * setTag() + * + * @param array|Zend_CodeGenerator_Php_Docblock_Tag $tag + * @return Zend_CodeGenerator_Php_Docblock + */ + public function setTag($tag) + { + if (is_array($tag)) { + $tag = new Zend_CodeGenerator_Php_Docblock_Tag($tag); + } elseif (!$tag instanceof Zend_CodeGenerator_Php_Docblock_Tag) { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception( + 'setTag() expects either an array of method options or an ' + . 'instance of Zend_CodeGenerator_Php_Docblock_Tag' + ); + } + + $this->_tags[] = $tag; + return $this; + } + + /** + * getTags + * + * @return array Array of Zend_CodeGenerator_Php_Docblock_Tag + */ + public function getTags() + { + return $this->_tags; + } + + /** + * generate() + * + * @return string + */ + public function generate() + { + if (!$this->isSourceDirty()) { + return $this->_docCommentize($this->getSourceContent()); + } + + $output = ''; + if (null !== ($sd = $this->getShortDescription())) { + $output .= $sd . self::LINE_FEED . self::LINE_FEED; + } + if (null !== ($ld = $this->getLongDescription())) { + $output .= $ld . self::LINE_FEED . self::LINE_FEED; + } + + foreach ($this->getTags() as $tag) { + $output .= $tag->generate() . self::LINE_FEED; + } + + return $this->_docCommentize(trim($output)); + } + + /** + * _docCommentize() + * + * @param string $content + * @return string + */ + protected function _docCommentize($content) + { + $indent = $this->getIndentation(); + $output = $indent . '/**' . self::LINE_FEED; + $content = wordwrap($content, 80, self::LINE_FEED); + $lines = explode(self::LINE_FEED, $content); + foreach ($lines as $line) { + $output .= $indent . ' *'; + if ($line) { + $output .= " $line"; + } + $output .= self::LINE_FEED; + } + $output .= $indent . ' */' . self::LINE_FEED; + return $output; + } + +} diff --git a/library/Zend/CodeGenerator/Php/Docblock/Tag.php b/library/Zend/CodeGenerator/Php/Docblock/Tag.php new file mode 100644 index 0000000..bea6d16 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Docblock/Tag.php @@ -0,0 +1,178 @@ +getName(); + + $codeGenDocblockTag = self::factory($tagName); + + // transport any properties via accessors and mutators from reflection to codegen object + $reflectionClass = new ReflectionClass($reflectionTag); + foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + if (substr($method->getName(), 0, 3) == 'get') { + $propertyName = substr($method->getName(), 3); + if (method_exists($codeGenDocblockTag, 'set' . $propertyName)) { + $codeGenDocblockTag->{'set' . $propertyName}($reflectionTag->{'get' . $propertyName}()); + } + } + } + + return $codeGenDocblockTag; + } + + /** + * setPluginLoader() + * + * @param Zend_Loader_PluginLoader $pluginLoader + */ + public static function setPluginLoader(Zend_Loader_PluginLoader $pluginLoader) + { + self::$_pluginLoader = $pluginLoader; + return; + } + + /** + * getPluginLoader() + * + * @return Zend_Loader_PluginLoader + */ + public static function getPluginLoader() + { + if (self::$_pluginLoader == null) { + require_once 'Zend/Loader/PluginLoader.php'; + self::setPluginLoader(new Zend_Loader_PluginLoader(array( + 'Zend_CodeGenerator_Php_Docblock_Tag' => dirname(__FILE__) . '/Tag/')) + ); + } + + return self::$_pluginLoader; + } + + public static function factory($tagName) + { + $pluginLoader = self::getPluginLoader(); + + try { + $tagClass = $pluginLoader->load($tagName); + } catch (Zend_Loader_Exception $exception) { + $tagClass = 'Zend_CodeGenerator_Php_Docblock_Tag'; + } + + $tag = new $tagClass(array('name' => $tagName)); + return $tag; + } + + /** + * setName() + * + * @param string $name + * @return Zend_CodeGenerator_Php_Docblock_Tag + */ + public function setName($name) + { + $this->_name = ltrim($name, '@'); + return $this; + } + + /** + * getName() + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * setDescription() + * + * @param string $description + * @return Zend_CodeGenerator_Php_Docblock_Tag + */ + public function setDescription($description) + { + $this->_description = $description; + return $this; + } + + /** + * getDescription() + * + * @return string + */ + public function getDescription() + { + return $this->_description; + } + + /** + * generate() + * + * @return string + */ + public function generate() + { + $tag = '@' . $this->_name; + if ($this->_description) { + $tag .= ' ' . $this->_description; + } + return $tag; + } + +} diff --git a/library/Zend/CodeGenerator/Php/Docblock/Tag/License.php b/library/Zend/CodeGenerator/Php/Docblock/Tag/License.php new file mode 100644 index 0000000..3dfb77e --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Docblock/Tag/License.php @@ -0,0 +1,98 @@ +setName('license'); + $returnTag->setUrl($reflectionTagLicense->getUrl()); + $returnTag->setDescription($reflectionTagLicense->getDescription()); + + return $returnTag; + } + + /** + * setUrl() + * + * @param string $url + * @return Zend_CodeGenerator_Php_Docblock_Tag_License + */ + public function setUrl($url) + { + $this->_url = $url; + return $this; + } + + /** + * getUrl() + * + * @return string + */ + public function getUrl() + { + return $this->_url; + } + + + /** + * generate() + * + * @return string + */ + public function generate() + { + $output = '@license ' . $this->_url . ' ' . $this->_description . self::LINE_FEED; + return $output; + } + +} \ No newline at end of file diff --git a/library/Zend/CodeGenerator/Php/Docblock/Tag/Param.php b/library/Zend/CodeGenerator/Php/Docblock/Tag/Param.php new file mode 100644 index 0000000..d1f8e67 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Docblock/Tag/Param.php @@ -0,0 +1,128 @@ +setName('param'); + $paramTag->setDatatype($reflectionTagParam->getType()); // @todo rename + $paramTag->setParamName($reflectionTagParam->getVariableName()); + $paramTag->setDescription($reflectionTagParam->getDescription()); + + return $paramTag; + } + + /** + * setDatatype() + * + * @param string $datatype + * @return Zend_CodeGenerator_Php_Docblock_Tag_Param + */ + public function setDatatype($datatype) + { + $this->_datatype = $datatype; + return $this; + } + + /** + * getDatatype + * + * @return string + */ + public function getDatatype() + { + return $this->_datatype; + } + + /** + * setParamName() + * + * @param string $paramName + * @return Zend_CodeGenerator_Php_Docblock_Tag_Param + */ + public function setParamName($paramName) + { + $this->_paramName = $paramName; + return $this; + } + + /** + * getParamName() + * + * @return string + */ + public function getParamName() + { + return $this->_paramName; + } + + /** + * generate() + * + * @return string + */ + public function generate() + { + $output = '@param ' + . (($this->_datatype != null) ? $this->_datatype : 'unknown') + . (($this->_paramName != null) ? ' $' . $this->_paramName : '') + . (($this->_description != null) ? ' ' . $this->_description : ''); + return $output; + } + +} diff --git a/library/Zend/CodeGenerator/Php/Docblock/Tag/Return.php b/library/Zend/CodeGenerator/Php/Docblock/Tag/Return.php new file mode 100644 index 0000000..2cbf0fa --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Docblock/Tag/Return.php @@ -0,0 +1,98 @@ +setName('return'); + $returnTag->setDatatype($reflectionTagReturn->getType()); // @todo rename + $returnTag->setDescription($reflectionTagReturn->getDescription()); + + return $returnTag; + } + + /** + * setDatatype() + * + * @param string $datatype + * @return Zend_CodeGenerator_Php_Docblock_Tag_Return + */ + public function setDatatype($datatype) + { + $this->_datatype = $datatype; + return $this; + } + + /** + * getDatatype() + * + * @return string + */ + public function getDatatype() + { + return $this->_datatype; + } + + + /** + * generate() + * + * @return string + */ + public function generate() + { + $output = '@return ' . $this->_datatype . ' ' . $this->_description; + return $output; + } + +} \ No newline at end of file diff --git a/library/Zend/CodeGenerator/Php/Exception.php b/library/Zend/CodeGenerator/Php/Exception.php new file mode 100644 index 0000000..65f0db9 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Exception.php @@ -0,0 +1,37 @@ +getFilename(); + } + + if ($fileName == '') { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('FileName does not exist.'); + } + + // cannot use realpath since the file might not exist, but we do need to have the index + // in the same DIRECTORY_SEPARATOR that realpath would use: + $fileName = str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $fileName); + + self::$_fileCodeGenerators[$fileName] = $fileCodeGenerator; + + } + + /** + * fromReflectedFileName() - use this if you intend on generating code generation objects based on the same file. + * This will keep previous changes to the file in tact during the same PHP process + * + * @param string $filePath + * @param bool $usePreviousCodeGeneratorIfItExists + * @param bool $includeIfNotAlreadyIncluded + * @return Zend_CodeGenerator_Php_File + */ + public static function fromReflectedFileName($filePath, $usePreviousCodeGeneratorIfItExists = true, $includeIfNotAlreadyIncluded = true) + { + $realpath = realpath($filePath); + + if ($realpath === false) { + if ( ($realpath = Zend_Reflection_File::findRealpathInIncludePath($filePath)) === false) { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('No file for ' . $realpath . ' was found.'); + } + } + + if ($usePreviousCodeGeneratorIfItExists && isset(self::$_fileCodeGenerators[$realpath])) { + return self::$_fileCodeGenerators[$realpath]; + } + + if ($includeIfNotAlreadyIncluded && !in_array($realpath, get_included_files())) { + include $realpath; + } + + $codeGenerator = self::fromReflection(($fileReflector = new Zend_Reflection_File($realpath))); + + if (!isset(self::$_fileCodeGenerators[$fileReflector->getFileName()])) { + self::$_fileCodeGenerators[$fileReflector->getFileName()] = $codeGenerator; + } + + return $codeGenerator; + } + + /** + * fromReflection() + * + * @param Zend_Reflection_File $reflectionFile + * @return Zend_CodeGenerator_Php_File + */ + public static function fromReflection(Zend_Reflection_File $reflectionFile) + { + $file = new self(); + + $file->setSourceContent($reflectionFile->getContents()); + $file->setSourceDirty(false); + + $body = $reflectionFile->getContents(); + + // @todo this whole area needs to be reworked with respect to how body lines are processed + foreach ($reflectionFile->getClasses() as $class) { + $file->setClass(Zend_CodeGenerator_Php_Class::fromReflection($class)); + $classStartLine = $class->getStartLine(true); + $classEndLine = $class->getEndLine(); + + $bodyLines = explode("\n", $body); + $bodyReturn = array(); + for ($lineNum = 1; $lineNum <= count($bodyLines); $lineNum++) { + if ($lineNum == $classStartLine) { + $bodyReturn[] = str_replace('?', $class->getName(), self::$_markerClass); //'/* Zend_CodeGenerator_Php_File-ClassMarker: {' . $class->getName() . '} */'; + $lineNum = $classEndLine; + } else { + $bodyReturn[] = $bodyLines[$lineNum - 1]; // adjust for index -> line conversion + } + } + $body = implode("\n", $bodyReturn); + unset($bodyLines, $bodyReturn, $classStartLine, $classEndLine); + } + + if (($reflectionFile->getDocComment() != '')) { + $docblock = $reflectionFile->getDocblock(); + $file->setDocblock(Zend_CodeGenerator_Php_Docblock::fromReflection($docblock)); + + $bodyLines = explode("\n", $body); + $bodyReturn = array(); + for ($lineNum = 1; $lineNum <= count($bodyLines); $lineNum++) { + if ($lineNum == $docblock->getStartLine()) { + $bodyReturn[] = str_replace('?', $class->getName(), self::$_markerDocblock); //'/* Zend_CodeGenerator_Php_File-ClassMarker: {' . $class->getName() . '} */'; + $lineNum = $docblock->getEndLine(); + } else { + $bodyReturn[] = $bodyLines[$lineNum - 1]; // adjust for index -> line conversion + } + } + $body = implode("\n", $bodyReturn); + unset($bodyLines, $bodyReturn, $classStartLine, $classEndLine); + } + + $file->setBody($body); + + return $file; + } + + /** + * setDocblock() Set the docblock + * + * @param Zend_CodeGenerator_Php_Docblock|array|string $docblock + * @return Zend_CodeGenerator_Php_File + */ + public function setDocblock($docblock) + { + if (is_string($docblock)) { + $docblock = array('shortDescription' => $docblock); + } + + if (is_array($docblock)) { + $docblock = new Zend_CodeGenerator_Php_Docblock($docblock); + } elseif (!$docblock instanceof Zend_CodeGenerator_Php_Docblock) { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('setDocblock() is expecting either a string, array or an instance of Zend_CodeGenerator_Php_Docblock'); + } + + $this->_docblock = $docblock; + return $this; + } + + /** + * Get docblock + * + * @return Zend_CodeGenerator_Php_Docblock + */ + public function getDocblock() + { + return $this->_docblock; + } + + /** + * setRequiredFiles + * + * @param array $requiredFiles + * @return Zend_CodeGenerator_Php_File + */ + public function setRequiredFiles($requiredFiles) + { + $this->_requiredFiles = $requiredFiles; + return $this; + } + + /** + * getRequiredFiles() + * + * @return array + */ + public function getRequiredFiles() + { + return $this->_requiredFiles; + } + + /** + * setClasses() + * + * @param array $classes + * @return Zend_CodeGenerator_Php_File + */ + public function setClasses(Array $classes) + { + foreach ($classes as $class) { + $this->setClass($class); + } + return $this; + } + + /** + * getClass() + * + * @param string $name + * @return Zend_CodeGenerator_Php_Class + */ + public function getClass($name = null) + { + if ($name == null) { + reset($this->_classes); + return current($this->_classes); + } + + return $this->_classes[$name]; + } + + /** + * setClass() + * + * @param Zend_CodeGenerator_Php_Class|array $class + * @return Zend_CodeGenerator_Php_File + */ + public function setClass($class) + { + if (is_array($class)) { + $class = new Zend_CodeGenerator_Php_Class($class); + $className = $class->getName(); + } elseif ($class instanceof Zend_CodeGenerator_Php_Class) { + $className = $class->getName(); + } else { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('Expecting either an array or an instance of Zend_CodeGenerator_Php_Class'); + } + + // @todo check for dup here + + $this->_classes[$className] = $class; + return $this; + } + + /** + * setFilename() + * + * @param string $filename + * @return Zend_CodeGenerator_Php_File + */ + public function setFilename($filename) + { + $this->_filename = $filename; + return $this; + } + + /** + * getFilename() + * + * @return string + */ + public function getFilename() + { + return $this->_filename; + } + + /** + * getClasses() + * + * @return array Array of Zend_CodeGenerator_Php_Class + */ + public function getClasses() + { + return $this->_classes; + } + + /** + * setBody() + * + * @param string $body + * @return Zend_CodeGenerator_Php_File + */ + public function setBody($body) + { + $this->_body = $body; + return $this; + } + + /** + * getBody() + * + * @return string + */ + public function getBody() + { + return $this->_body; + } + + /** + * isSourceDirty() + * + * @return bool + */ + public function isSourceDirty() + { + if (($docblock = $this->getDocblock()) && $docblock->isSourceDirty()) { + return true; + } + + foreach ($this->_classes as $class) { + if ($class->isSourceDirty()) { + return true; + } + } + + return parent::isSourceDirty(); + } + + /** + * generate() + * + * @return string + */ + public function generate() + { + if ($this->isSourceDirty() === false) { + return $this->_sourceContent; + } + + $output = ''; + + // start with the body (if there), or open tag + if (preg_match('#(?:\s*)<\?php#', $this->getBody()) == false) { + $output = 'getBody(); + if (preg_match('#/\* Zend_CodeGenerator_Php_File-(.*?)Marker#', $body)) { + $output .= $body; + $body = ''; + } + + // Add file docblock, if any + if (null !== ($docblock = $this->getDocblock())) { + $docblock->setIndentation(''); + $regex = preg_quote(self::$_markerDocblock, '#'); + if (preg_match('#'.$regex.'#', $output)) { + $output = preg_replace('#'.$regex.'#', $docblock->generate(), $output, 1); + } else { + $output .= $docblock->generate() . self::LINE_FEED; + } + } + + // newline + $output .= self::LINE_FEED; + + // process required files + // @todo marker replacement for required files + $requiredFiles = $this->getRequiredFiles(); + if (!empty($requiredFiles)) { + foreach ($requiredFiles as $requiredFile) { + $output .= 'require_once \'' . $requiredFile . '\';' . self::LINE_FEED; + } + + $output .= self::LINE_FEED; + } + + // process classes + $classes = $this->getClasses(); + if (!empty($classes)) { + foreach ($classes as $class) { + if($this->getDocblock() == $class->getDocblock()) { + $class->setDocblock(null); + } + $regex = str_replace('?', $class->getName(), self::$_markerClass); + $regex = preg_quote($regex, '#'); + if (preg_match('#'.$regex.'#', $output)) { + $output = preg_replace('#'.$regex.'#', $class->generate(), $output, 1); + } else { + $output .= $class->generate() . self::LINE_FEED; + } + } + + } + + if (!empty($body)) { + + // add an extra space betwee clsses and + if (!empty($classes)) { + $output .= self::LINE_FEED; + } + + $output .= $body; + } + + return $output; + } + + public function write() + { + if ($this->_filename == '' || !is_writable(dirname($this->_filename))) { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('This code generator object is not writable.'); + } + file_put_contents($this->_filename, $this->generate()); + return $this; + } + +} diff --git a/library/Zend/CodeGenerator/Php/Member/Abstract.php b/library/Zend/CodeGenerator/Php/Member/Abstract.php new file mode 100644 index 0000000..52b2f83 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Member/Abstract.php @@ -0,0 +1,222 @@ + $docblock); + } + + if (is_array($docblock)) { + $docblock = new Zend_CodeGenerator_Php_Docblock($docblock); + } elseif (!$docblock instanceof Zend_CodeGenerator_Php_Docblock) { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('setDocblock() is expecting either a string, array or an instance of Zend_CodeGenerator_Php_Docblock'); + } + + $this->_docblock = $docblock; + return $this; + } + + /** + * getDocblock() + * + * @return Zend_CodeGenerator_Php_Docblock + */ + public function getDocblock() + { + return $this->_docblock; + } + + /** + * setAbstract() + * + * @param bool $isAbstract + * @return Zend_CodeGenerator_Php_Member_Abstract + */ + public function setAbstract($isAbstract) + { + $this->_isAbstract = ($isAbstract) ? true : false; + return $this; + } + + /** + * isAbstract() + * + * @return bool + */ + public function isAbstract() + { + return $this->_isAbstract; + } + + /** + * setFinal() + * + * @param bool $isFinal + * @return Zend_CodeGenerator_Php_Member_Abstract + */ + public function setFinal($isFinal) + { + $this->_isFinal = ($isFinal) ? true : false; + return $this; + } + + /** + * isFinal() + * + * @return bool + */ + public function isFinal() + { + return $this->_isFinal; + } + + /** + * setStatic() + * + * @param bool $isStatic + * @return Zend_CodeGenerator_Php_Member_Abstract + */ + public function setStatic($isStatic) + { + $this->_isStatic = ($isStatic) ? true : false; + return $this; + } + + /** + * isStatic() + * + * @return bool + */ + public function isStatic() + { + return $this->_isStatic; + } + + /** + * setVisitibility() + * + * @param const $visibility + * @return Zend_CodeGenerator_Php_Member_Abstract + */ + public function setVisibility($visibility) + { + $this->_visibility = $visibility; + return $this; + } + + /** + * getVisibility() + * + * @return const + */ + public function getVisibility() + { + return $this->_visibility; + } + + /** + * setName() + * + * @param string $name + * @return Zend_CodeGenerator_Php_Member_Abstract + */ + public function setName($name) + { + $this->_name = $name; + return $this; + } + + /** + * getName() + * + * @return string + */ + public function getName() + { + return $this->_name; + } +} diff --git a/library/Zend/CodeGenerator/Php/Member/Container.php b/library/Zend/CodeGenerator/Php/Member/Container.php new file mode 100644 index 0000000..944f6e1 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Member/Container.php @@ -0,0 +1,55 @@ +_type = $type; + parent::__construct(array(), self::ARRAY_AS_PROPS); + } + +} \ No newline at end of file diff --git a/library/Zend/CodeGenerator/Php/Method.php b/library/Zend/CodeGenerator/Php/Method.php new file mode 100644 index 0000000..2ff4468 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Method.php @@ -0,0 +1,236 @@ +setSourceContent($reflectionMethod->getContents(false)); + $method->setSourceDirty(false); + + if ($reflectionMethod->getDocComment() != '') { + $method->setDocblock(Zend_CodeGenerator_Php_Docblock::fromReflection($reflectionMethod->getDocblock())); + } + + $method->setFinal($reflectionMethod->isFinal()); + + if ($reflectionMethod->isPrivate()) { + $method->setVisibility(self::VISIBILITY_PRIVATE); + } elseif ($reflectionMethod->isProtected()) { + $method->setVisibility(self::VISIBILITY_PROTECTED); + } else { + $method->setVisibility(self::VISIBILITY_PUBLIC); + } + + $method->setStatic($reflectionMethod->isStatic()); + + $method->setName($reflectionMethod->getName()); + + foreach ($reflectionMethod->getParameters() as $reflectionParameter) { + $method->setParameter(Zend_CodeGenerator_Php_Parameter::fromReflection($reflectionParameter)); + } + + $method->setBody($reflectionMethod->getBody()); + + return $method; + } + + /** + * setFinal() + * + * @param bool $isFinal + */ + public function setFinal($isFinal) + { + $this->_isFinal = ($isFinal) ? true : false; + } + + /** + * setParameters() + * + * @param array $parameters + * @return Zend_CodeGenerator_Php_Method + */ + public function setParameters(Array $parameters) + { + foreach ($parameters as $parameter) { + $this->setParameter($parameter); + } + return $this; + } + + /** + * setParameter() + * + * @param Zend_CodeGenerator_Php_Parameter|array $parameter + * @return Zend_CodeGenerator_Php_Method + */ + public function setParameter($parameter) + { + if (is_array($parameter)) { + $parameter = new Zend_CodeGenerator_Php_Parameter($parameter); + $parameterName = $parameter->getName(); + } elseif ($parameter instanceof Zend_CodeGenerator_Php_Parameter) { + $parameterName = $parameter->getName(); + } else { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('setParameter() expects either an array of method options or an instance of Zend_CodeGenerator_Php_Parameter'); + } + + $this->_parameters[$parameterName] = $parameter; + return $this; + } + + /** + * getParameters() + * + * @return array Array of Zend_CodeGenerator_Php_Parameter + */ + public function getParameters() + { + return $this->_parameters; + } + + /** + * setBody() + * + * @param string $body + * @return Zend_CodeGenerator_Php_Method + */ + public function setBody($body) + { + $this->_body = $body; + return $this; + } + + /** + * getBody() + * + * @return string + */ + public function getBody() + { + return $this->_body; + } + + /** + * generate() + * + * @return string + */ + public function generate() + { + $output = ''; + + $indent = $this->getIndentation(); + + if (($docblock = $this->getDocblock()) !== null) { + $docblock->setIndentation($indent); + $output .= $docblock->generate(); + } + + $output .= $indent; + + if ($this->isAbstract()) { + $output .= 'abstract '; + } else { + $output .= (($this->isFinal()) ? 'final ' : ''); + } + + $output .= $this->getVisibility() + . (($this->isStatic()) ? ' static' : '') + . ' function ' . $this->getName() . '('; + + $parameters = $this->getParameters(); + if (!empty($parameters)) { + foreach ($parameters as $parameter) { + $parameterOuput[] = $parameter->generate(); + } + + $output .= implode(', ', $parameterOuput); + } + + $output .= ')' . self::LINE_FEED . $indent . '{' . self::LINE_FEED; + + if ($this->_body && $this->isSourceDirty()) { + $output .= ' ' + . str_replace(self::LINE_FEED, self::LINE_FEED . $indent . $indent, trim($this->_body)) + . self::LINE_FEED; + } elseif ($this->_body) { + $output .= $this->_body . self::LINE_FEED; + } + + $output .= $indent . '}' . self::LINE_FEED; + + return $output; + } + +} diff --git a/library/Zend/CodeGenerator/Php/Parameter.php b/library/Zend/CodeGenerator/Php/Parameter.php new file mode 100644 index 0000000..1dc80f3 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Parameter.php @@ -0,0 +1,250 @@ +setName($reflectionParameter->getName()); + + if($reflectionParameter->isArray()) { + $param->setType('array'); + } else { + $typeClass = $reflectionParameter->getClass(); + if($typeClass !== null) { + $param->setType($typeClass->getName()); + } + } + + $param->setPosition($reflectionParameter->getPosition()); + + if($reflectionParameter->isOptional()) { + $param->setDefaultValue($reflectionParameter->getDefaultValue()); + } + $param->setPassedByReference($reflectionParameter->isPassedByReference()); + + return $param; + } + + /** + * setType() + * + * @param string $type + * @return Zend_CodeGenerator_Php_Parameter + */ + public function setType($type) + { + $this->_type = $type; + return $this; + } + + /** + * getType() + * + * @return string + */ + public function getType() + { + return $this->_type; + } + + /** + * setName() + * + * @param string $name + * @return Zend_CodeGenerator_Php_Parameter + */ + public function setName($name) + { + $this->_name = $name; + return $this; + } + + /** + * getName() + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * Set the default value of the parameter. + * + * Certain variables are difficult to expres + * + * @param null|bool|string|int|float|Zend_CodeGenerator_Php_Parameter_DefaultValue $defaultValue + * @return Zend_CodeGenerator_Php_Parameter + */ + public function setDefaultValue($defaultValue) + { + if($defaultValue === null) { + $this->_defaultValue = new Zend_CodeGenerator_Php_Parameter_DefaultValue("null"); + } else if(is_array($defaultValue)) { + $defaultValue = str_replace(array("\r", "\n"), "", var_export($defaultValue, true)); + $this->_defaultValue = new Zend_CodeGenerator_Php_Parameter_DefaultValue($defaultValue); + } else if(is_bool($defaultValue)) { + if($defaultValue == true) { + $this->_defaultValue = new Zend_CodeGenerator_Php_Parameter_DefaultValue("true"); + } else { + $this->_defaultValue = new Zend_CodeGenerator_Php_Parameter_DefaultValue("false"); + } + } else { + $this->_defaultValue = $defaultValue; + } + return $this; + } + + /** + * getDefaultValue() + * + * @return string + */ + public function getDefaultValue() + { + return $this->_defaultValue; + } + + /** + * setPosition() + * + * @param int $position + * @return Zend_CodeGenerator_Php_Parameter + */ + public function setPosition($position) + { + $this->_position = $position; + return $this; + } + + /** + * getPosition() + * + * @return int + */ + public function getPosition() + { + return $this->_position; + } + + /** + * @return bool + */ + public function getPassedByReference() + { + return $this->_passedByReference; + } + + /** + * @param bool $passedByReference + * @return Zend_CodeGenerator_Php_Parameter + */ + public function setPassedByReference($passedByReference) + { + $this->_passedByReference = $passedByReference; + return $this; + } + + /** + * generate() + * + * @return string + */ + public function generate() + { + $output = ''; + + if ($this->_type) { + $output .= $this->_type . ' '; + } + + if($this->_passedByReference === true) { + $output .= '&'; + } + + $output .= '$' . $this->_name; + + if ($this->_defaultValue !== null) { + $output .= ' = '; + if (is_string($this->_defaultValue)) { + $output .= '\'' . $this->_defaultValue . '\''; + } else if($this->_defaultValue instanceof Zend_CodeGenerator_Php_Parameter_DefaultValue) { + $output .= (string)$this->_defaultValue; + } else { + $output .= $this->_defaultValue; + } + } + + return $output; + } + +} \ No newline at end of file diff --git a/library/Zend/CodeGenerator/Php/Parameter/DefaultValue.php b/library/Zend/CodeGenerator/Php/Parameter/DefaultValue.php new file mode 100644 index 0000000..a8708cb --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Parameter/DefaultValue.php @@ -0,0 +1,60 @@ +_defaultValue = $defaultValue; + } + + public function __toString() + { + return $this->_defaultValue; + } +} \ No newline at end of file diff --git a/library/Zend/CodeGenerator/Php/Property.php b/library/Zend/CodeGenerator/Php/Property.php new file mode 100644 index 0000000..3dc7f3b --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Property.php @@ -0,0 +1,179 @@ +setName($reflectionProperty->getName()); + + $allDefaultProperties = $reflectionProperty->getDeclaringClass()->getDefaultProperties(); + + $property->setDefaultValue($allDefaultProperties[$reflectionProperty->getName()]); + + if ($reflectionProperty->getDocComment() != '') { + $property->setDocblock(Zend_CodeGenerator_Php_Docblock::fromReflection($reflectionProperty->getDocComment())); + } + + if ($reflectionProperty->isStatic()) { + $property->setStatic(true); + } + + if ($reflectionProperty->isPrivate()) { + $property->setVisibility(self::VISIBILITY_PRIVATE); + } elseif ($reflectionProperty->isProtected()) { + $property->setVisibility(self::VISIBILITY_PROTECTED); + } else { + $property->setVisibility(self::VISIBILITY_PUBLIC); + } + + $property->setSourceDirty(false); + + return $property; + } + + /** + * setConst() + * + * @param bool $const + * @return Zend_CodeGenerator_Php_Property + */ + public function setConst($const) + { + $this->_isConst = $const; + return $this; + } + + /** + * isConst() + * + * @return bool + */ + public function isConst() + { + return ($this->_isConst) ? true : false; + } + + /** + * setDefaultValue() + * + * @param Zend_CodeGenerator_Php_Property_DefaultValue|string|array $defaultValue + * @return Zend_CodeGenerator_Php_Property + */ + public function setDefaultValue($defaultValue) + { + // if it looks like + if (is_array($defaultValue) + && array_key_exists('value', $defaultValue) + && array_key_exists('type', $defaultValue)) { + $defaultValue = new Zend_CodeGenerator_Php_Property_DefaultValue($defaultValue); + } + + if (!($defaultValue instanceof Zend_CodeGenerator_Php_Property_DefaultValue)) { + $defaultValue = new Zend_CodeGenerator_Php_Property_DefaultValue(array('value' => $defaultValue)); + } + + $this->_defaultValue = $defaultValue; + return $this; + } + + /** + * getDefaultValue() + * + * @return Zend_CodeGenerator_Php_Property_DefaultValue + */ + public function getDefaultValue() + { + return $this->_defaultValue; + } + + /** + * generate() + * + * @return string + */ + public function generate() + { + $name = $this->getName(); + $defaultValue = $this->getDefaultValue(); + + $output = ''; + + if (($docblock = $this->getDocblock()) !== null) { + $docblock->setIndentation(' '); + $output .= $docblock->generate(); + } + + if ($this->isConst()) { + if ($defaultValue != null && !$defaultValue->isValidConstantType()) { + require_once 'Zend/CodeGenerator/Php/Exception.php'; + throw new Zend_CodeGenerator_Php_Exception('The property ' . $this->_name . ' is said to be ' + . 'constant but does not have a valid constant value.'); + } + $output .= $this->_indentation . 'const ' . $name . ' = ' + . (($defaultValue !== null) ? $defaultValue->generate() : 'null;'); + } else { + $output .= $this->_indentation + . $this->getVisibility() + . (($this->isStatic()) ? ' static' : '') + . ' $' . $name . ' = ' + . (($defaultValue !== null) ? $defaultValue->generate() : 'null;'); + } + return $output; + } + +} diff --git a/library/Zend/CodeGenerator/Php/Property/DefaultValue.php b/library/Zend/CodeGenerator/Php/Property/DefaultValue.php new file mode 100644 index 0000000..82d85ed --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Property/DefaultValue.php @@ -0,0 +1,325 @@ +getConstants(); + unset($reflect); + } + } + + /** + * isValidConstantType() + * + * @return bool + */ + public function isValidConstantType() + { + if ($this->_type == self::TYPE_AUTO) { + $type = $this->_getAutoDeterminedType($this->_value); + } else { + $type = $this->_type; + } + + // valid types for constants + $scalarTypes = array( + self::TYPE_BOOLEAN, + self::TYPE_BOOL, + self::TYPE_NUMBER, + self::TYPE_INTEGER, + self::TYPE_INT, + self::TYPE_FLOAT, + self::TYPE_DOUBLE, + self::TYPE_STRING, + self::TYPE_CONSTANT, + self::TYPE_NULL + ); + + return in_array($type, $scalarTypes); + } + + /** + * setValue() + * + * @param mixed $value + * @return Zend_CodeGenerator_Php_Property_DefaultValue + */ + public function setValue($value) + { + $this->_value = $value; + return $this; + } + + /** + * getValue() + * + * @return mixed + */ + public function getValue() + { + return $this->_value; + } + + /** + * setType() + * + * @param string $type + * @return Zend_CodeGenerator_Php_Property_DefaultValue + */ + public function setType($type) + { + $this->_type = $type; + return $this; + } + + /** + * getType() + * + * @return string + */ + public function getType() + { + return $this->_type; + } + + /** + * setArrayDepth() + * + * @param int $arrayDepth + * @return Zend_CodeGenerator_Php_Property_DefaultValue + */ + public function setArrayDepth($arrayDepth) + { + $this->_arrayDepth = $arrayDepth; + return $this; + } + + /** + * getArrayDepth() + * + * @return int + */ + public function getArrayDepth() + { + return $this->_arrayDepth; + } + + /** + * _getValidatedType() + * + * @param string $type + * @return string + */ + protected function _getValidatedType($type) + { + if (($constName = array_search($type, self::$_constants)) !== false) { + return $type; + } + + return self::TYPE_AUTO; + } + + /** + * _getAutoDeterminedType() + * + * @param mixed $value + * @return string + */ + public function _getAutoDeterminedType($value) + { + switch (gettype($value)) { + case 'boolean': + return self::TYPE_BOOLEAN; + case 'integer': + return self::TYPE_INT; + case 'string': + return self::TYPE_STRING; + case 'double': + case 'float': + case 'integer': + return self::TYPE_NUMBER; + case 'array': + return self::TYPE_ARRAY; + case 'NULL': + return self::TYPE_NULL; + case 'object': + case 'resource': + case 'unknown type': + default: + return self::TYPE_OTHER; + } + } + + /** + * generate() + * + * @return string + */ + public function generate() + { + $type = $this->_type; + + if ($type != self::TYPE_AUTO) { + $type = $this->_getValidatedType($type); + } + + $value = $this->_value; + + if ($type == self::TYPE_AUTO) { + $type = $this->_getAutoDeterminedType($value); + + if ($type == self::TYPE_ARRAY) { + $rii = new RecursiveIteratorIterator( + $it = new RecursiveArrayIterator($value), + RecursiveIteratorIterator::SELF_FIRST + ); + foreach ($rii as $curKey => $curValue) { + if (!$curValue instanceof Zend_CodeGenerator_Php_Property_DefaultValue) { + $curValue = new self(array('value' => $curValue)); + $rii->getSubIterator()->offsetSet($curKey, $curValue); + } + $curValue->setArrayDepth($rii->getDepth()); + } + $value = $rii->getSubIterator()->getArrayCopy(); + } + + } + + $output = ''; + + switch ($type) { + case self::TYPE_BOOLEAN: + case self::TYPE_BOOL: + $output .= ( $value ? 'true' : 'false' ); + break; + case self::TYPE_STRING: + $output .= "'" . addcslashes($value, "'") . "'"; + break; + case self::TYPE_NULL: + $output .= 'null'; + break; + case self::TYPE_NUMBER: + case self::TYPE_INTEGER: + case self::TYPE_INT: + case self::TYPE_FLOAT: + case self::TYPE_DOUBLE: + case self::TYPE_CONSTANT: + $output .= $value; + break; + case self::TYPE_ARRAY: + $output .= 'array('; + $curArrayMultiblock = false; + if (count($value) > 1) { + $curArrayMultiblock = true; + $output .= PHP_EOL . str_repeat($this->_indentation, $this->_arrayDepth+1); + } + $outputParts = array(); + $noKeyIndex = 0; + foreach ($value as $n => $v) { + $v->setArrayDepth($this->_arrayDepth + 1); + $partV = $v->generate(); + $partV = substr($partV, 0, strlen($partV)-1); + if ($n === $noKeyIndex) { + $outputParts[] = $partV; + $noKeyIndex++; + } else { + $outputParts[] = (is_int($n) ? $n : "'" . addcslashes($n, "'") . "'") . ' => ' . $partV; + } + + } + $output .= implode(',' . PHP_EOL . str_repeat($this->_indentation, $this->_arrayDepth+1), $outputParts); + if ($curArrayMultiblock == true) { + $output .= PHP_EOL . str_repeat($this->_indentation, $this->_arrayDepth+1); + } + $output .= ')'; + break; + case self::TYPE_OTHER: + default: + require_once "Zend/CodeGenerator/Php/Exception.php"; + throw new Zend_CodeGenerator_Php_Exception( + "Type '".get_class($value)."' is unknown or cannot be used as property default value." + ); + } + + $output .= ';'; + + return $output; + } +} diff --git a/library/Zend/Config.php b/library/Zend/Config.php new file mode 100644 index 0000000..a977845 --- /dev/null +++ b/library/Zend/Config.php @@ -0,0 +1,484 @@ +_allowModifications = (boolean) $allowModifications; + $this->_loadedSection = null; + $this->_index = 0; + $this->_data = array(); + foreach ($array as $key => $value) { + if (is_array($value)) { + $this->_data[$key] = new self($value, $this->_allowModifications); + } else { + $this->_data[$key] = $value; + } + } + $this->_count = count($this->_data); + } + + /** + * Retrieve a value and return $default if there is no element set. + * + * @param string $name + * @param mixed $default + * @return mixed + */ + public function get($name, $default = null) + { + $result = $default; + if (array_key_exists($name, $this->_data)) { + $result = $this->_data[$name]; + } + return $result; + } + + /** + * Magic function so that $obj->value will work. + * + * @param string $name + * @return mixed + */ + public function __get($name) + { + return $this->get($name); + } + + /** + * Only allow setting of a property if $allowModifications + * was set to true on construction. Otherwise, throw an exception. + * + * @param string $name + * @param mixed $value + * @throws Zend_Config_Exception + * @return void + */ + public function __set($name, $value) + { + if ($this->_allowModifications) { + if (is_array($value)) { + $this->_data[$name] = new self($value, true); + } else { + $this->_data[$name] = $value; + } + $this->_count = count($this->_data); + } else { + /** @see Zend_Config_Exception */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('Zend_Config is read only'); + } + } + + /** + * Deep clone of this instance to ensure that nested Zend_Configs + * are also cloned. + * + * @return void + */ + public function __clone() + { + $array = array(); + foreach ($this->_data as $key => $value) { + if ($value instanceof Zend_Config) { + $array[$key] = clone $value; + } else { + $array[$key] = $value; + } + } + $this->_data = $array; + } + + /** + * Return an associative array of the stored data. + * + * @return array + */ + public function toArray() + { + $array = array(); + $data = $this->_data; + foreach ($data as $key => $value) { + if ($value instanceof Zend_Config) { + $array[$key] = $value->toArray(); + } else { + $array[$key] = $value; + } + } + return $array; + } + + /** + * Support isset() overloading on PHP 5.1 + * + * @param string $name + * @return boolean + */ + public function __isset($name) + { + return isset($this->_data[$name]); + } + + /** + * Support unset() overloading on PHP 5.1 + * + * @param string $name + * @throws Zend_Config_Exception + * @return void + */ + public function __unset($name) + { + if ($this->_allowModifications) { + unset($this->_data[$name]); + $this->_count = count($this->_data); + $this->_skipNextIteration = true; + } else { + /** @see Zend_Config_Exception */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('Zend_Config is read only'); + } + + } + + /** + * Defined by Countable interface + * + * @return int + */ + public function count() + { + return $this->_count; + } + + /** + * Defined by Iterator interface + * + * @return mixed + */ + public function current() + { + $this->_skipNextIteration = false; + return current($this->_data); + } + + /** + * Defined by Iterator interface + * + * @return mixed + */ + public function key() + { + return key($this->_data); + } + + /** + * Defined by Iterator interface + * + */ + public function next() + { + if ($this->_skipNextIteration) { + $this->_skipNextIteration = false; + return; + } + next($this->_data); + $this->_index++; + } + + /** + * Defined by Iterator interface + * + */ + public function rewind() + { + $this->_skipNextIteration = false; + reset($this->_data); + $this->_index = 0; + } + + /** + * Defined by Iterator interface + * + * @return boolean + */ + public function valid() + { + return $this->_index < $this->_count; + } + + /** + * Returns the section name(s) loaded. + * + * @return mixed + */ + public function getSectionName() + { + if(is_array($this->_loadedSection) && count($this->_loadedSection) == 1) { + $this->_loadedSection = $this->_loadedSection[0]; + } + return $this->_loadedSection; + } + + /** + * Returns true if all sections were loaded + * + * @return boolean + */ + public function areAllSectionsLoaded() + { + return $this->_loadedSection === null; + } + + + /** + * Merge another Zend_Config with this one. The items + * in $merge will override the same named items in + * the current config. + * + * @param Zend_Config $merge + * @return Zend_Config + */ + public function merge(Zend_Config $merge) + { + foreach($merge as $key => $item) { + if(array_key_exists($key, $this->_data)) { + if($item instanceof Zend_Config && $this->$key instanceof Zend_Config) { + $this->$key = $this->$key->merge(new Zend_Config($item->toArray(), !$this->readOnly())); + } else { + $this->$key = $item; + } + } else { + if($item instanceof Zend_Config) { + $this->$key = new Zend_Config($item->toArray(), !$this->readOnly()); + } else { + $this->$key = $item; + } + } + } + + return $this; + } + + /** + * Prevent any more modifications being made to this instance. Useful + * after merge() has been used to merge multiple Zend_Config objects + * into one object which should then not be modified again. + * + */ + public function setReadOnly() + { + $this->_allowModifications = false; + foreach ($this->_data as $key => $value) { + if ($value instanceof Zend_Config) { + $value->setReadOnly(); + } + } + } + + /** + * Returns if this Zend_Config object is read only or not. + * + * @return boolean + */ + public function readOnly() + { + return !$this->_allowModifications; + } + + /** + * Get the current extends + * + * @return array + */ + public function getExtends() + { + return $this->_extends; + } + + /** + * Set an extend for Zend_Config_Writer + * + * @param string $extendingSection + * @param string $extendedSection + * @return void + */ + public function setExtend($extendingSection, $extendedSection = null) + { + if ($extendedSection === null && isset($this->_extends[$extendingSection])) { + unset($this->_extends[$extendingSection]); + } else if ($extendedSection !== null) { + $this->_extends[$extendingSection] = $extendedSection; + } + } + + /** + * Throws an exception if $extendingSection may not extend $extendedSection, + * and tracks the section extension if it is valid. + * + * @param string $extendingSection + * @param string $extendedSection + * @throws Zend_Config_Exception + * @return void + */ + protected function _assertValidExtend($extendingSection, $extendedSection) + { + // detect circular section inheritance + $extendedSectionCurrent = $extendedSection; + while (array_key_exists($extendedSectionCurrent, $this->_extends)) { + if ($this->_extends[$extendedSectionCurrent] == $extendingSection) { + /** @see Zend_Config_Exception */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('Illegal circular inheritance detected'); + } + $extendedSectionCurrent = $this->_extends[$extendedSectionCurrent]; + } + // remember that this section extends another section + $this->_extends[$extendingSection] = $extendedSection; + } + + /** + * Handle any errors from simplexml_load_file or parse_ini_file + * + * @param integer $errno + * @param string $errstr + * @param string $errfile + * @param integer $errline + */ + protected function _loadFileErrorHandler($errno, $errstr, $errfile, $errline) + { + if ($this->_loadFileErrorStr === null) { + $this->_loadFileErrorStr = $errstr; + } else { + $this->_loadFileErrorStr .= (PHP_EOL . $errstr); + } + } + + /** + * Merge two arrays recursively, overwriting keys of the same name + * in $firstArray with the value in $secondArray. + * + * @param mixed $firstArray First array + * @param mixed $secondArray Second array to merge into first array + * @return array + */ + protected function _arrayMergeRecursive($firstArray, $secondArray) + { + if (is_array($firstArray) && is_array($secondArray)) { + foreach ($secondArray as $key => $value) { + if (isset($firstArray[$key])) { + $firstArray[$key] = $this->_arrayMergeRecursive($firstArray[$key], $value); + } else { + if($key === 0) { + $firstArray= array(0=>$this->_arrayMergeRecursive($firstArray, $value)); + } else { + $firstArray[$key] = $value; + } + } + } + } else { + $firstArray = $secondArray; + } + + return $firstArray; + } +} \ No newline at end of file diff --git a/library/Zend/Config/Exception.php b/library/Zend/Config/Exception.php new file mode 100644 index 0000000..c3a3b34 --- /dev/null +++ b/library/Zend/Config/Exception.php @@ -0,0 +1,33 @@ +hostname === "staging" + * $data->db->connection === "database" + * + * The $options parameter may be provided as either a boolean or an array. + * If provided as a boolean, this sets the $allowModifications option of + * Zend_Config. If provided as an array, there are three configuration + * directives that may be set. For example: + * + * $options = array( + * 'allowModifications' => false, + * 'nestSeparator' => ':', + * 'skipExtends' => false, + * ); + * + * @param string $filename + * @param mixed $section + * @param boolean|array $options + * @throws Zend_Config_Exception + * @return void + */ + public function __construct($filename, $section = null, $options = false) + { + if (empty($filename)) { + /** + * @see Zend_Config_Exception + */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('Filename is not set'); + } + + $allowModifications = false; + if (is_bool($options)) { + $allowModifications = $options; + } elseif (is_array($options)) { + if (isset($options['allowModifications'])) { + $allowModifications = (bool) $options['allowModifications']; + } + if (isset($options['nestSeparator'])) { + $this->_nestSeparator = (string) $options['nestSeparator']; + } + if (isset($options['skipExtends'])) { + $this->_skipExtends = (bool) $options['skipExtends']; + } + } + + $iniArray = $this->_loadIniFile($filename); + + if (null === $section) { + // Load entire file + $dataArray = array(); + foreach ($iniArray as $sectionName => $sectionData) { + if(!is_array($sectionData)) { + $dataArray = $this->_arrayMergeRecursive($dataArray, $this->_processKey(array(), $sectionName, $sectionData)); + } else { + $dataArray[$sectionName] = $this->_processSection($iniArray, $sectionName); + } + } + parent::__construct($dataArray, $allowModifications); + } else { + // Load one or more sections + if (!is_array($section)) { + $section = array($section); + } + $dataArray = array(); + foreach ($section as $sectionName) { + if (!isset($iniArray[$sectionName])) { + /** + * @see Zend_Config_Exception + */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Section '$sectionName' cannot be found in $filename"); + } + $dataArray = $this->_arrayMergeRecursive($this->_processSection($iniArray, $sectionName), $dataArray); + + } + parent::__construct($dataArray, $allowModifications); + } + + $this->_loadedSection = $section; + } + + /** + * Load the INI file from disk using parse_ini_file(). Use a private error + * handler to convert any loading errors into a Zend_Config_Exception + * + * @param string $filename + * @throws Zend_Config_Exception + * @return array + */ + protected function _parseIniFile($filename) + { + set_error_handler(array($this, '_loadFileErrorHandler')); + $iniArray = parse_ini_file($filename, true); // Warnings and errors are suppressed + restore_error_handler(); + + // Check if there was a error while loading file + if ($this->_loadFileErrorStr !== null) { + /** + * @see Zend_Config_Exception + */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception($this->_loadFileErrorStr); + } + + return $iniArray; + } + + /** + * Load the ini file and preprocess the section separator (':' in the + * section name (that is used for section extension) so that the resultant + * array has the correct section names and the extension information is + * stored in a sub-key called ';extends'. We use ';extends' as this can + * never be a valid key name in an INI file that has been loaded using + * parse_ini_file(). + * + * @param string $filename + * @throws Zend_Config_Exception + * @return array + */ + protected function _loadIniFile($filename) + { + $loaded = $this->_parseIniFile($filename); + $iniArray = array(); + foreach ($loaded as $key => $data) + { + $pieces = explode($this->_sectionSeparator, $key); + $thisSection = trim($pieces[0]); + switch (count($pieces)) { + case 1: + $iniArray[$thisSection] = $data; + break; + + case 2: + $extendedSection = trim($pieces[1]); + $iniArray[$thisSection] = array_merge(array(';extends'=>$extendedSection), $data); + break; + + default: + /** + * @see Zend_Config_Exception + */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Section '$thisSection' may not extend multiple sections in $filename"); + } + } + + return $iniArray; + } + + /** + * Process each element in the section and handle the ";extends" inheritance + * key. Passes control to _processKey() to handle the nest separator + * sub-property syntax that may be used within the key name. + * + * @param array $iniArray + * @param string $section + * @param array $config + * @throws Zend_Config_Exception + * @return array + */ + protected function _processSection($iniArray, $section, $config = array()) + { + $thisSection = $iniArray[$section]; + + foreach ($thisSection as $key => $value) { + if (strtolower($key) == ';extends') { + if (isset($iniArray[$value])) { + $this->_assertValidExtend($section, $value); + + if (!$this->_skipExtends) { + $config = $this->_processSection($iniArray, $value, $config); + } + } else { + /** + * @see Zend_Config_Exception + */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Parent section '$section' cannot be found"); + } + } else { + $config = $this->_processKey($config, $key, $value); + } + } + return $config; + } + + /** + * Assign the key's value to the property list. Handles the + * nest separator for sub-properties. + * + * @param array $config + * @param string $key + * @param string $value + * @throws Zend_Config_Exception + * @return array + */ + protected function _processKey($config, $key, $value) + { + if (strpos($key, $this->_nestSeparator) !== false) { + $pieces = explode($this->_nestSeparator, $key, 2); + if (strlen($pieces[0]) && strlen($pieces[1])) { + if (!isset($config[$pieces[0]])) { + if ($pieces[0] === '0' && !empty($config)) { + // convert the current values in $config into an array + $config = array($pieces[0] => $config); + } else { + $config[$pieces[0]] = array(); + } + } elseif (!is_array($config[$pieces[0]])) { + /** + * @see Zend_Config_Exception + */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Cannot create sub-key for '{$pieces[0]}' as key already exists"); + } + $config[$pieces[0]] = $this->_processKey($config[$pieces[0]], $pieces[1], $value); + } else { + /** + * @see Zend_Config_Exception + */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Invalid key '$key'"); + } + } else { + $config[$key] = $value; + } + return $config; + } +} diff --git a/library/Zend/Config/Json.php b/library/Zend/Config/Json.php new file mode 100644 index 0000000..ee9924f --- /dev/null +++ b/library/Zend/Config/Json.php @@ -0,0 +1,242 @@ + $value) { + switch (strtolower($key)) { + case 'allow_modifications': + case 'allowmodifications': + $allowModifications = (bool) $value; + break; + case 'skip_extends': + case 'skipextends': + $this->_skipExtends = (bool) $value; + break; + case 'ignore_constants': + case 'ignoreconstants': + $this->_ignoreConstants = (bool) $value; + break; + default: + break; + } + } + } + + set_error_handler(array($this, '_loadFileErrorHandler')); // Warnings and errors are suppressed + if ($json[0] != '{') { + $json = file_get_contents($json); + } + restore_error_handler(); + + // Check if there was a error while loading file + if ($this->_loadFileErrorStr !== null) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception($this->_loadFileErrorStr); + } + + // Replace constants + if (!$this->_ignoreConstants) { + $json = $this->_replaceConstants($json); + } + + // Parse/decode + $config = Zend_Json::decode($json); + + if (null === $config) { + // decode failed + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Error parsing JSON data"); + } + + if ($section === null) { + $dataArray = array(); + foreach ($config as $sectionName => $sectionData) { + $dataArray[$sectionName] = $this->_processExtends($config, $sectionName); + } + + parent::__construct($dataArray, $allowModifications); + } elseif (is_array($section)) { + $dataArray = array(); + foreach ($section as $sectionName) { + if (!isset($config[$sectionName])) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception(sprintf('Section "%s" cannot be found', $sectionName)); + } + + $dataArray = array_merge($this->_processExtends($config, $sectionName), $dataArray); + } + + parent::__construct($dataArray, $allowModifications); + } else { + if (!isset($config[$section])) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception(sprintf('Section "%s" cannot be found', $section)); + } + + $dataArray = $this->_processExtends($config, $section); + if (!is_array($dataArray)) { + // Section in the JSON data contains just one top level string + $dataArray = array($section => $dataArray); + } + + parent::__construct($dataArray, $allowModifications); + } + + $this->_loadedSection = $section; + } + + /** + * Helper function to process each element in the section and handle + * the "_extends" inheritance attribute. + * + * @param array $data Data array to process + * @param string $section Section to process + * @param array $config Configuration which was parsed yet + * @throws Zend_Config_Exception When $section cannot be found + * @return array + */ + protected function _processExtends(array $data, $section, array $config = array()) + { + if (!isset($data[$section])) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception(sprintf('Section "%s" cannot be found', $section)); + } + + $thisSection = $data[$section]; + + if (is_array($thisSection) && isset($thisSection[self::EXTENDS_NAME])) { + if (is_array($thisSection[self::EXTENDS_NAME])) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('Invalid extends clause: must be a string; array received'); + } + $this->_assertValidExtend($section, $thisSection[self::EXTENDS_NAME]); + + if (!$this->_skipExtends) { + $config = $this->_processExtends($data, $thisSection[self::EXTENDS_NAME], $config); + } + unset($thisSection[self::EXTENDS_NAME]); + } + + $config = $this->_arrayMergeRecursive($config, $thisSection); + + return $config; + } + + /** + * Replace any constants referenced in a string with their values + * + * @param string $value + * @return string + */ + protected function _replaceConstants($value) + { + foreach ($this->_getConstants() as $constant) { + if (strstr($value, $constant)) { + // handle backslashes that may represent windows path names for instance + $replacement = str_replace('\\', '\\\\', constant($constant)); + $value = str_replace($constant, $replacement, $value); + } + } + return $value; + } + + /** + * Get (reverse) sorted list of defined constant names + * + * @return array + */ + protected function _getConstants() + { + $constants = array_keys(get_defined_constants()); + rsort($constants, SORT_STRING); + return $constants; + } +} diff --git a/library/Zend/Config/Writer.php b/library/Zend/Config/Writer.php new file mode 100644 index 0000000..20bd078 --- /dev/null +++ b/library/Zend/Config/Writer.php @@ -0,0 +1,101 @@ +setOptions($options); + } + } + + /** + * Set options via a Zend_Config instance + * + * @param Zend_Config $config + * @return Zend_Config_Writer + */ + public function setConfig(Zend_Config $config) + { + $this->_config = $config; + + return $this; + } + + /** + * Set options via an array + * + * @param array $options + * @return Zend_Config_Writer + */ + public function setOptions(array $options) + { + foreach ($options as $key => $value) { + if (in_array(strtolower($key), $this->_skipOptions)) { + continue; + } + + $method = 'set' . ucfirst($key); + if (method_exists($this, $method)) { + $this->$method($value); + } + } + + return $this; + } + + /** + * Write a Zend_Config object to it's target + * + * @return void + */ + abstract public function write(); +} diff --git a/library/Zend/Config/Writer/Array.php b/library/Zend/Config/Writer/Array.php new file mode 100644 index 0000000..c98c004 --- /dev/null +++ b/library/Zend/Config/Writer/Array.php @@ -0,0 +1,55 @@ +_config->toArray(); + $sectionName = $this->_config->getSectionName(); + + if (is_string($sectionName)) { + $data = array($sectionName => $data); + } + + $arrayString = "_filename = $filename; + + return $this; + } + + /** + * Set wether to exclusively lock the file or not + * + * @param boolean $exclusiveLock + * @return Zend_Config_Writer_Array + */ + public function setExclusiveLock($exclusiveLock) + { + $this->_exclusiveLock = $exclusiveLock; + + return $this; + } + + /** + * Write configuration to file. + * + * @param string $filename + * @param Zend_Config $config + * @param bool $exclusiveLock + * @return void + */ + public function write($filename = null, Zend_Config $config = null, $exclusiveLock = null) + { + if ($filename !== null) { + $this->setFilename($filename); + } + + if ($config !== null) { + $this->setConfig($config); + } + + if ($exclusiveLock !== null) { + $this->setExclusiveLock($exclusiveLock); + } + + if ($this->_filename === null) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('No filename was set'); + } + + if ($this->_config === null) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('No config was set'); + } + + $configString = $this->render(); + + $flags = 0; + + if ($this->_exclusiveLock) { + $flags |= LOCK_EX; + } + + $result = @file_put_contents($this->_filename, $configString, $flags); + + if ($result === false) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('Could not write to file "' . $this->_filename . '"'); + } + } + + /** + * Render a Zend_Config into a config file string. + * + * @since 1.10 + * @todo For 2.0 this should be redone into an abstract method. + * @return string + */ + public function render() + { + return ""; + } +} \ No newline at end of file diff --git a/library/Zend/Config/Writer/Ini.php b/library/Zend/Config/Writer/Ini.php new file mode 100644 index 0000000..f2e5a78 --- /dev/null +++ b/library/Zend/Config/Writer/Ini.php @@ -0,0 +1,193 @@ +_nestSeparator = $separator; + + return $this; + } + + /** + * Set if rendering should occour without sections or not. + * + * If set to true, the INI file is rendered without sections completely + * into the global namespace of the INI file. + * + * @param bool $withoutSections + * @return Zend_Config_Writer_Ini + */ + public function setRenderWithoutSections($withoutSections=true) + { + $this->_renderWithoutSections = (bool)$withoutSections; + return $this; + } + + /** + * Render a Zend_Config into a INI config string. + * + * @since 1.10 + * @return string + */ + public function render() + { + $iniString = ''; + $extends = $this->_config->getExtends(); + $sectionName = $this->_config->getSectionName(); + + if($this->_renderWithoutSections == true) { + $iniString .= $this->_addBranch($this->_config); + } else if (is_string($sectionName)) { + $iniString .= '[' . $sectionName . ']' . "\n" + . $this->_addBranch($this->_config) + . "\n"; + } else { + $config = $this->_sortRootElements($this->_config); + foreach ($config as $sectionName => $data) { + if (!($data instanceof Zend_Config)) { + $iniString .= $sectionName + . ' = ' + . $this->_prepareValue($data) + . "\n"; + } else { + if (isset($extends[$sectionName])) { + $sectionName .= ' : ' . $extends[$sectionName]; + } + + $iniString .= '[' . $sectionName . ']' . "\n" + . $this->_addBranch($data) + . "\n"; + } + } + } + + return $iniString; + } + + /** + * Add a branch to an INI string recursively + * + * @param Zend_Config $config + * @return void + */ + protected function _addBranch(Zend_Config $config, $parents = array()) + { + $iniString = ''; + + foreach ($config as $key => $value) { + $group = array_merge($parents, array($key)); + + if ($value instanceof Zend_Config) { + $iniString .= $this->_addBranch($value, $group); + } else { + $iniString .= implode($this->_nestSeparator, $group) + . ' = ' + . $this->_prepareValue($value) + . "\n"; + } + } + + return $iniString; + } + + /** + * Prepare a value for INI + * + * @param mixed $value + * @return string + */ + protected function _prepareValue($value) + { + if (is_integer($value) || is_float($value)) { + return $value; + } elseif (is_bool($value)) { + return ($value ? 'true' : 'false'); + } elseif (strpos($value, '"') === false) { + return '"' . $value . '"'; + } else { + /** @see Zend_Config_Exception */ + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('Value can not contain double quotes "'); + } + } + + /** + * Root elements that are not assigned to any section needs to be + * on the top of config. + * + * @see http://framework.zend.com/issues/browse/ZF-6289 + * @param Zend_Config + * @return Zend_Config + */ + protected function _sortRootElements(Zend_Config $config) + { + $configArray = $config->toArray(); + $sections = array(); + + // remove sections from config array + foreach ($configArray as $key => $value) { + if (is_array($value)) { + $sections[$key] = $value; + unset($configArray[$key]); + } + } + + // readd sections to the end + foreach ($sections as $key => $value) { + $configArray[$key] = $value; + } + + return new Zend_Config($configArray); + } +} diff --git a/library/Zend/Config/Writer/Json.php b/library/Zend/Config/Writer/Json.php new file mode 100644 index 0000000..d05557f --- /dev/null +++ b/library/Zend/Config/Writer/Json.php @@ -0,0 +1,106 @@ +_prettyPrint; + } + + /** + * Set prettyPrint flag + * + * @param bool $prettyPrint PrettyPrint flag + * @return Zend_Config_Writer_Json + */ + public function setPrettyPrint($flag) + { + $this->_prettyPrint = (bool) $flag; + return $this; + } + + /** + * Render a Zend_Config into a JSON config string. + * + * @since 1.10 + * @return string + */ + public function render() + { + $data = $this->_config->toArray(); + $sectionName = $this->_config->getSectionName(); + $extends = $this->_config->getExtends(); + + if (is_string($sectionName)) { + $data = array($sectionName => $data); + } + + foreach ($extends as $section => $parentSection) { + $data[$section][Zend_Config_Json::EXTENDS_NAME] = $parentSection; + } + + // Ensure that each "extends" section actually exists + foreach ($data as $section => $sectionData) { + if (is_array($sectionData) && isset($sectionData[Zend_Config_Json::EXTENDS_NAME])) { + $sectionExtends = $sectionData[Zend_Config_Json::EXTENDS_NAME]; + if (!isset($data[$sectionExtends])) { + // Remove "extends" declaration if section does not exist + unset($data[$section][Zend_Config_Json::EXTENDS_NAME]); + } + } + } + + $out = Zend_Json::encode($data); + if ($this->prettyPrint()) { + $out = Zend_Json::prettyPrint($out); + } + return $out; + } +} diff --git a/library/Zend/Config/Writer/Xml.php b/library/Zend/Config/Writer/Xml.php new file mode 100644 index 0000000..82463c1 --- /dev/null +++ b/library/Zend/Config/Writer/Xml.php @@ -0,0 +1,127 @@ +'); + $extends = $this->_config->getExtends(); + $sectionName = $this->_config->getSectionName(); + + if (is_string($sectionName)) { + $child = $xml->addChild($sectionName); + + $this->_addBranch($this->_config, $child, $xml); + } else { + foreach ($this->_config as $sectionName => $data) { + if (!($data instanceof Zend_Config)) { + $xml->addChild($sectionName, (string) $data); + } else { + $child = $xml->addChild($sectionName); + + if (isset($extends[$sectionName])) { + $child->addAttribute('zf:extends', $extends[$sectionName], Zend_Config_Xml::XML_NAMESPACE); + } + + $this->_addBranch($data, $child, $xml); + } + } + } + + $dom = dom_import_simplexml($xml)->ownerDocument; + $dom->formatOutput = true; + + $xmlString = $dom->saveXML(); + + return $xmlString; + } + + /** + * Add a branch to an XML object recursively + * + * @param Zend_Config $config + * @param SimpleXMLElement $xml + * @param SimpleXMLElement $parent + * @return void + */ + protected function _addBranch(Zend_Config $config, SimpleXMLElement $xml, SimpleXMLElement $parent) + { + $branchType = null; + + foreach ($config as $key => $value) { + if ($branchType === null) { + if (is_numeric($key)) { + $branchType = 'numeric'; + $branchName = $xml->getName(); + $xml = $parent; + + unset($parent->{$branchName}); + } else { + $branchType = 'string'; + } + } else if ($branchType !== (is_numeric($key) ? 'numeric' : 'string')) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('Mixing of string and numeric keys is not allowed'); + } + + if ($branchType === 'numeric') { + if ($value instanceof Zend_Config) { + $child = $parent->addChild($branchName); + + $this->_addBranch($value, $child, $parent); + } else { + $parent->addChild($branchName, (string) $value); + } + } else { + if ($value instanceof Zend_Config) { + $child = $xml->addChild($key); + + $this->_addBranch($value, $child, $xml); + } else { + $xml->addChild($key, (string) $value); + } + } + } + } +} diff --git a/library/Zend/Config/Writer/Yaml.php b/library/Zend/Config/Writer/Yaml.php new file mode 100644 index 0000000..9d7305c --- /dev/null +++ b/library/Zend/Config/Writer/Yaml.php @@ -0,0 +1,144 @@ +_yamlEncoder; + } + + /** + * Set callback for decoding YAML + * + * @param callable $yamlEncoder the decoder to set + * @return Zend_Config_Yaml + */ + public function setYamlEncoder($yamlEncoder) + { + if (!is_callable($yamlEncoder)) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('Invalid parameter to setYamlEncoder - must be callable'); + } + + $this->_yamlEncoder = $yamlEncoder; + return $this; + } + + /** + * Render a Zend_Config into a YAML config string. + * + * @since 1.10 + * @return string + */ + public function render() + { + $data = $this->_config->toArray(); + $sectionName = $this->_config->getSectionName(); + $extends = $this->_config->getExtends(); + + if (is_string($sectionName)) { + $data = array($sectionName => $data); + } + + foreach ($extends as $section => $parentSection) { + $data[$section][Zend_Config_Yaml::EXTENDS_NAME] = $parentSection; + } + + // Ensure that each "extends" section actually exists + foreach ($data as $section => $sectionData) { + if (is_array($sectionData) && isset($sectionData[Zend_Config_Yaml::EXTENDS_NAME])) { + $sectionExtends = $sectionData[Zend_Config_Yaml::EXTENDS_NAME]; + if (!isset($data[$sectionExtends])) { + // Remove "extends" declaration if section does not exist + unset($data[$section][Zend_Config_Yaml::EXTENDS_NAME]); + } + } + } + + return call_user_func($this->getYamlEncoder(), $data); + } + + /** + * Very dumb YAML encoder + * + * Until we have Zend_Yaml... + * + * @param array $data YAML data + * @return string + */ + public static function encode($data) + { + return self::_encodeYaml(0, $data); + } + + /** + * Service function for encoding YAML + * + * @param int $indent Current indent level + * @param array $data Data to encode + * @return string + */ + protected static function _encodeYaml($indent, $data) + { + reset($data); + $result = ""; + $numeric = is_numeric(key($data)); + + foreach($data as $key => $value) { + if(is_array($value)) { + $encoded = "\n".self::_encodeYaml($indent+1, $value); + } else { + $encoded = (string)$value."\n"; + } + $result .= str_repeat(" ", $indent).($numeric?"- ":"$key: ").$encoded; + } + return $result; + } +} diff --git a/library/Zend/Config/Xml.php b/library/Zend/Config/Xml.php new file mode 100644 index 0000000..2a2f066 --- /dev/null +++ b/library/Zend/Config/Xml.php @@ -0,0 +1,296 @@ + false, + * 'skipExtends' => false + * ); + * + * @param string $xml XML file or string to process + * @param mixed $section Section to process + * @param array|boolean $options + * @throws Zend_Config_Exception When xml is not set or cannot be loaded + * @throws Zend_Config_Exception When section $sectionName cannot be found in $xml + */ + public function __construct($xml, $section = null, $options = false) + { + if (empty($xml)) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('Filename is not set'); + } + + $allowModifications = false; + if (is_bool($options)) { + $allowModifications = $options; + } elseif (is_array($options)) { + if (isset($options['allowModifications'])) { + $allowModifications = (bool) $options['allowModifications']; + } + if (isset($options['skipExtends'])) { + $this->_skipExtends = (bool) $options['skipExtends']; + } + } + + set_error_handler(array($this, '_loadFileErrorHandler')); // Warnings and errors are suppressed + if (strstr($xml, '_loadFileErrorStr !== null) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception($this->_loadFileErrorStr); + } + + if ($section === null) { + $dataArray = array(); + foreach ($config as $sectionName => $sectionData) { + $dataArray[$sectionName] = $this->_processExtends($config, $sectionName); + } + + parent::__construct($dataArray, $allowModifications); + } else if (is_array($section)) { + $dataArray = array(); + foreach ($section as $sectionName) { + if (!isset($config->$sectionName)) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Section '$sectionName' cannot be found in $xml"); + } + + $dataArray = array_merge($this->_processExtends($config, $sectionName), $dataArray); + } + + parent::__construct($dataArray, $allowModifications); + } else { + if (!isset($config->$section)) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Section '$section' cannot be found in $xml"); + } + + $dataArray = $this->_processExtends($config, $section); + if (!is_array($dataArray)) { + // Section in the XML file contains just one top level string + $dataArray = array($section => $dataArray); + } + + parent::__construct($dataArray, $allowModifications); + } + + $this->_loadedSection = $section; + } + + /** + * Helper function to process each element in the section and handle + * the "extends" inheritance attribute. + * + * @param SimpleXMLElement $element XML Element to process + * @param string $section Section to process + * @param array $config Configuration which was parsed yet + * @throws Zend_Config_Exception When $section cannot be found + * @return array + */ + protected function _processExtends(SimpleXMLElement $element, $section, array $config = array()) + { + if (!isset($element->$section)) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Section '$section' cannot be found"); + } + + $thisSection = $element->$section; + $nsAttributes = $thisSection->attributes(self::XML_NAMESPACE); + + if (isset($thisSection['extends']) || isset($nsAttributes['extends'])) { + $extendedSection = (string) (isset($nsAttributes['extends']) ? $nsAttributes['extends'] : $thisSection['extends']); + $this->_assertValidExtend($section, $extendedSection); + + if (!$this->_skipExtends) { + $config = $this->_processExtends($element, $extendedSection, $config); + } + } + + $config = $this->_arrayMergeRecursive($config, $this->_toArray($thisSection)); + + return $config; + } + + /** + * Returns a string or an associative and possibly multidimensional array from + * a SimpleXMLElement. + * + * @param SimpleXMLElement $xmlObject Convert a SimpleXMLElement into an array + * @return array|string + */ + protected function _toArray(SimpleXMLElement $xmlObject) + { + $config = array(); + $nsAttributes = $xmlObject->attributes(self::XML_NAMESPACE); + + // Search for parent node values + if (count($xmlObject->attributes()) > 0) { + foreach ($xmlObject->attributes() as $key => $value) { + if ($key === 'extends') { + continue; + } + + $value = (string) $value; + + if (array_key_exists($key, $config)) { + if (!is_array($config[$key])) { + $config[$key] = array($config[$key]); + } + + $config[$key][] = $value; + } else { + $config[$key] = $value; + } + } + } + + // Search for local 'const' nodes and replace them + if (count($xmlObject->children(self::XML_NAMESPACE)) > 0) { + if (count($xmlObject->children()) > 0) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("A node with a 'const' childnode may not have any other children"); + } + + $dom = dom_import_simplexml($xmlObject); + $namespaceChildNodes = array(); + + // We have to store them in an array, as replacing nodes will + // confuse the DOMNodeList later + foreach ($dom->childNodes as $node) { + if ($node instanceof DOMElement && $node->namespaceURI === self::XML_NAMESPACE) { + $namespaceChildNodes[] = $node; + } + } + + foreach ($namespaceChildNodes as $node) { + switch ($node->localName) { + case 'const': + if (!$node->hasAttributeNS(self::XML_NAMESPACE, 'name')) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Misssing 'name' attribute in 'const' node"); + } + + $constantName = $node->getAttributeNS(self::XML_NAMESPACE, 'name'); + + if (!defined($constantName)) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Constant with name '$constantName' was not defined"); + } + + $constantValue = constant($constantName); + + $dom->replaceChild($dom->ownerDocument->createTextNode($constantValue), $node); + break; + + default: + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Unknown node with name '$node->localName' found"); + } + } + + return (string) simplexml_import_dom($dom); + } + + // Search for children + if (count($xmlObject->children()) > 0) { + foreach ($xmlObject->children() as $key => $value) { + if (count($value->children()) > 0 || count($value->children(self::XML_NAMESPACE)) > 0) { + $value = $this->_toArray($value); + } else if (count($value->attributes()) > 0) { + $attributes = $value->attributes(); + if (isset($attributes['value'])) { + $value = (string) $attributes['value']; + } else { + $value = $this->_toArray($value); + } + } else { + $value = (string) $value; + } + + if (array_key_exists($key, $config)) { + if (!is_array($config[$key]) || !array_key_exists(0, $config[$key])) { + $config[$key] = array($config[$key]); + } + + $config[$key][] = $value; + } else { + $config[$key] = $value; + } + } + } else if (!isset($xmlObject['extends']) && !isset($nsAttributes['extends']) && (count($config) === 0)) { + // Object has no children nor attributes and doesn't use the extends + // attribute: it's a string + $config = (string) $xmlObject; + } + + return $config; + } +} diff --git a/library/Zend/Config/Yaml.php b/library/Zend/Config/Yaml.php new file mode 100644 index 0000000..dea099d --- /dev/null +++ b/library/Zend/Config/Yaml.php @@ -0,0 +1,415 @@ +_yamlDecoder; + } + + /** + * Set callback for decoding YAML + * + * @param callable $yamlDecoder the decoder to set + * @return Zend_Config_Yaml + */ + public function setYamlDecoder($yamlDecoder) + { + if (!is_callable($yamlDecoder)) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('Invalid parameter to setYamlDecoder() - must be callable'); + } + + $this->_yamlDecoder = $yamlDecoder; + return $this; + } + + /** + * Loads the section $section from the config file encoded as YAML + * + * Sections are defined as properties of the main object + * + * In order to extend another section, a section defines the "_extends" + * property having a value of the section name from which the extending + * section inherits values. + * + * Note that the keys in $section will override any keys of the same + * name in the sections that have been included via "_extends". + * + * Options may include: + * - allow_modifications: whether or not the config object is mutable + * - skip_extends: whether or not to skip processing of parent configuration + * - yaml_decoder: a callback to use to decode the Yaml source + * + * @param string $yaml YAML file to process + * @param mixed $section Section to process + * @param array|boolean $options + */ + public function __construct($yaml, $section = null, $options = false) + { + if (empty($yaml)) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception('Filename is not set'); + } + + $ignoreConstants = $staticIgnoreConstants = self::ignoreConstants(); + $allowModifications = false; + if (is_bool($options)) { + $allowModifications = $options; + } elseif (is_array($options)) { + foreach ($options as $key => $value) { + switch (strtolower($key)) { + case 'allow_modifications': + case 'allowmodifications': + $allowModifications = (bool) $value; + break; + case 'skip_extends': + case 'skipextends': + $this->_skipExtends = (bool) $value; + break; + case 'ignore_constants': + case 'ignoreconstants': + $ignoreConstants = (bool) $value; + break; + case 'yaml_decoder': + case 'yamldecoder': + $this->setYamlDecoder($value); + break; + default: + break; + } + } + } + + // Suppress warnings and errors while loading file + set_error_handler(array($this, '_loadFileErrorHandler')); + $yaml = file_get_contents($yaml); + restore_error_handler(); + + // Check if there was a error while loading file + if ($this->_loadFileErrorStr !== null) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception($this->_loadFileErrorStr); + } + + // Override static value for ignore_constants if provided in $options + self::setIgnoreConstants($ignoreConstants); + + // Parse YAML + $config = call_user_func($this->getYamlDecoder(), $yaml); + + // Reset original static state of ignore_constants + self::setIgnoreConstants($staticIgnoreConstants); + + if (null === $config) { + // decode failed + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception("Error parsing YAML data"); + } + + if (null === $section) { + $dataArray = array(); + foreach ($config as $sectionName => $sectionData) { + $dataArray[$sectionName] = $this->_processExtends($config, $sectionName); + } + parent::__construct($dataArray, $allowModifications); + } elseif (is_array($section)) { + $dataArray = array(); + foreach ($section as $sectionName) { + if (!isset($config[$sectionName])) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception(sprintf( + 'Section "%s" cannot be found', + implode(' ', (array)$section) + )); + } + + $dataArray = array_merge($this->_processExtends($config, $sectionName), $dataArray); + } + parent::__construct($dataArray, $allowModifications); + } else { + if (!isset($config[$section])) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception(sprintf( + 'Section "%s" cannot be found', + implode(' ', (array)$section) + )); + } + + $dataArray = $this->_processExtends($config, $section); + if (!is_array($dataArray)) { + // Section in the yaml data contains just one top level string + $dataArray = array($section => $dataArray); + } + parent::__construct($dataArray, $allowModifications); + } + + $this->_loadedSection = $section; + } + + /** + * Helper function to process each element in the section and handle + * the "_extends" inheritance attribute. + * + * @param array $data Data array to process + * @param string $section Section to process + * @param array $config Configuration which was parsed yet + * @return array + * @throws Zend_Config_Exception When $section cannot be found + */ + protected function _processExtends(array $data, $section, array $config = array()) + { + if (!isset($data[$section])) { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception(sprintf('Section "%s" cannot be found', $section)); + } + + $thisSection = $data[$section]; + + if (is_array($thisSection) && isset($thisSection[self::EXTENDS_NAME])) { + $this->_assertValidExtend($section, $thisSection[self::EXTENDS_NAME]); + + if (!$this->_skipExtends) { + $config = $this->_processExtends($data, $thisSection[self::EXTENDS_NAME], $config); + } + unset($thisSection[self::EXTENDS_NAME]); + } + + $config = $this->_arrayMergeRecursive($config, $thisSection); + + return $config; + } + + /** + * Very dumb YAML parser + * + * Until we have Zend_Yaml... + * + * @param string $yaml YAML source + * @return array Decoded data + */ + public static function decode($yaml) + { + $lines = explode("\n", $yaml); + reset($lines); + return self::_decodeYaml(0, $lines); + } + + /** + * Service function to decode YAML + * + * @param int $currentIndent Current indent level + * @param array $lines YAML lines + * @return array|string + */ + protected static function _decodeYaml($currentIndent, &$lines) + { + $config = array(); + $inIndent = false; + while (list($n, $line) = each($lines)) { + $lineno = $n + 1; + + $line = rtrim(preg_replace("/#.*$/", "", $line)); + if (strlen($line) == 0) { + continue; + } + + $indent = strspn($line, " "); + + // line without the spaces + $line = trim($line); + if (strlen($line) == 0) { + continue; + } + + if ($indent < $currentIndent) { + // this level is done + prev($lines); + return $config; + } + + if (!$inIndent) { + $currentIndent = $indent; + $inIndent = true; + } + + if (preg_match("/(?!-)([\w\-]+):\s*(.*)/", $line, $m)) { + // key: value + if (strlen($m[2])) { + // simple key: value + $value = preg_replace("/#.*$/", "", $m[2]); + $value = self::_parseValue($value); + } else { + // key: and then values on new lines + $value = self::_decodeYaml($currentIndent + 1, $lines); + if (is_array($value) && !count($value)) { + $value = ""; + } + } + $config[$m[1]] = $value; + } elseif ($line[0] == "-") { + // item in the list: + // - FOO + if (strlen($line) > 2) { + $value = substr($line, 2); + + $config[] = self::_parseValue($value); + } else { + $config[] = self::_decodeYaml($currentIndent + 1, $lines); + } + } else { + require_once 'Zend/Config/Exception.php'; + throw new Zend_Config_Exception(sprintf( + 'Error parsing YAML at line %d - unsupported syntax: "%s"', + $lineno, $line + )); + } + } + return $config; + } + + /** + * Parse values + * + * @param string $value + * @return string + */ + protected static function _parseValue($value) + { + $value = trim($value); + + // remove quotes from string. + if ('"' == $value['0']) { + if ('"' == $value[count($value) -1]) { + $value = substr($value, 1, -1); + } + } elseif ('\'' == $value['0'] && '\'' == $value[count($value) -1]) { + $value = strtr($value, array("''" => "'", "'" => '')); + } + + // Check for booleans and constants + if (preg_match('/^(t(rue)?|on|y(es)?)$/i', $value)) { + $value = true; + } elseif (preg_match('/^(f(alse)?|off|n(o)?)$/i', $value)) { + $value = false; + } elseif (strcasecmp($value, 'null') === 0) { + $value = null; + } elseif (!self::$_ignoreConstants) { + // test for constants + $value = self::_replaceConstants($value); + } + + return $value; + } + + /** + * Replace any constants referenced in a string with their values + * + * @param string $value + * @return string + */ + protected static function _replaceConstants($value) + { + foreach (self::_getConstants() as $constant) { + if (strstr($value, $constant)) { + $value = str_replace($constant, constant($constant), $value); + } + } + return $value; + } + + /** + * Get (reverse) sorted list of defined constant names + * + * @return array + */ + protected static function _getConstants() + { + $constants = array_keys(get_defined_constants()); + rsort($constants, SORT_STRING); + return $constants; + } +} diff --git a/library/Zend/Console/Getopt.php b/library/Zend/Console/Getopt.php new file mode 100644 index 0000000..cd63dff --- /dev/null +++ b/library/Zend/Console/Getopt.php @@ -0,0 +1,970 @@ + self::MODE_ZEND, + self::CONFIG_DASHDASH => true, + self::CONFIG_IGNORECASE => false, + self::CONFIG_PARSEALL => true, + ); + + /** + * Stores the command-line arguments for the calling applicaion. + * + * @var array + */ + protected $_argv = array(); + + /** + * Stores the name of the calling applicaion. + * + * @var string + */ + protected $_progname = ''; + + /** + * Stores the list of legal options for this application. + * + * @var array + */ + protected $_rules = array(); + + /** + * Stores alternate spellings of legal options. + * + * @var array + */ + protected $_ruleMap = array(); + + /** + * Stores options given by the user in the current invocation + * of the application, as well as parameters given in options. + * + * @var array + */ + protected $_options = array(); + + /** + * Stores the command-line arguments other than options. + * + * @var array + */ + protected $_remainingArgs = array(); + + /** + * State of the options: parsed or not yet parsed? + * + * @var boolean + */ + protected $_parsed = false; + + /** + * The constructor takes one to three parameters. + * + * The first parameter is $rules, which may be a string for + * gnu-style format, or a structured array for Zend-style format. + * + * The second parameter is $argv, and it is optional. If not + * specified, $argv is inferred from the global argv. + * + * The third parameter is an array of configuration parameters + * to control the behavior of this instance of Getopt; it is optional. + * + * @param array $rules + * @param array $argv + * @param array $getoptConfig + * @return void + */ + public function __construct($rules, $argv = null, $getoptConfig = array()) + { + if (!isset($_SERVER['argv'])) { + require_once 'Zend/Console/Getopt/Exception.php'; + if (ini_get('register_argc_argv') == false) { + throw new Zend_Console_Getopt_Exception( + "argv is not available, because ini option 'register_argc_argv' is set Off" + ); + } else { + throw new Zend_Console_Getopt_Exception( + '$_SERVER["argv"] is not set, but Zend_Console_Getopt cannot work without this information.' + ); + } + } + + $this->_progname = $_SERVER['argv'][0]; + $this->setOptions($getoptConfig); + $this->addRules($rules); + if (!is_array($argv)) { + $argv = array_slice($_SERVER['argv'], 1); + } + if (isset($argv)) { + $this->addArguments((array)$argv); + } + } + + /** + * Return the state of the option seen on the command line of the + * current application invocation. This function returns true, or the + * parameter to the option, if any. If the option was not given, + * this function returns null. + * + * The magic __get method works in the context of naming the option + * as a virtual member of this class. + * + * @param string $key + * @return string + */ + public function __get($key) + { + return $this->getOption($key); + } + + /** + * Test whether a given option has been seen. + * + * @param string $key + * @return boolean + */ + public function __isset($key) + { + $this->parse(); + if (isset($this->_ruleMap[$key])) { + $key = $this->_ruleMap[$key]; + return isset($this->_options[$key]); + } + return false; + } + + /** + * Set the value for a given option. + * + * @param string $key + * @param string $value + * @return void + */ + public function __set($key, $value) + { + $this->parse(); + if (isset($this->_ruleMap[$key])) { + $key = $this->_ruleMap[$key]; + $this->_options[$key] = $value; + } + } + + /** + * Return the current set of options and parameters seen as a string. + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Unset an option. + * + * @param string $key + * @return void + */ + public function __unset($key) + { + $this->parse(); + if (isset($this->_ruleMap[$key])) { + $key = $this->_ruleMap[$key]; + unset($this->_options[$key]); + } + } + + /** + * Define additional command-line arguments. + * These are appended to those defined when the constructor was called. + * + * @param array $argv + * @throws Zend_Console_Getopt_Exception When not given an array as parameter + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function addArguments($argv) + { + if(!is_array($argv)) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Parameter #1 to addArguments should be an array"); + } + $this->_argv = array_merge($this->_argv, $argv); + $this->_parsed = false; + return $this; + } + + /** + * Define full set of command-line arguments. + * These replace any currently defined. + * + * @param array $argv + * @throws Zend_Console_Getopt_Exception When not given an array as parameter + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function setArguments($argv) + { + if(!is_array($argv)) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Parameter #1 to setArguments should be an array"); + } + $this->_argv = $argv; + $this->_parsed = false; + return $this; + } + + /** + * Define multiple configuration options from an associative array. + * These are not program options, but properties to configure + * the behavior of Zend_Console_Getopt. + * + * @param array $getoptConfig + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function setOptions($getoptConfig) + { + if (isset($getoptConfig)) { + foreach ($getoptConfig as $key => $value) { + $this->setOption($key, $value); + } + } + return $this; + } + + /** + * Define one configuration option as a key/value pair. + * These are not program options, but properties to configure + * the behavior of Zend_Console_Getopt. + * + * @param string $configKey + * @param string $configValue + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function setOption($configKey, $configValue) + { + if ($configKey !== null) { + $this->_getoptConfig[$configKey] = $configValue; + } + return $this; + } + + /** + * Define additional option rules. + * These are appended to the rules defined when the constructor was called. + * + * @param array $rules + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function addRules($rules) + { + $ruleMode = $this->_getoptConfig['ruleMode']; + switch ($this->_getoptConfig['ruleMode']) { + case self::MODE_ZEND: + if (is_array($rules)) { + $this->_addRulesModeZend($rules); + break; + } + // intentional fallthrough + case self::MODE_GNU: + $this->_addRulesModeGnu($rules); + break; + default: + /** + * Call addRulesModeFoo() for ruleMode 'foo'. + * The developer should subclass Getopt and + * provide this method. + */ + $method = '_addRulesMode' . ucfirst($ruleMode); + $this->$method($rules); + } + $this->_parsed = false; + return $this; + } + + /** + * Return the current set of options and parameters seen as a string. + * + * @return string + */ + public function toString() + { + $this->parse(); + $s = array(); + foreach ($this->_options as $flag => $value) { + $s[] = $flag . '=' . ($value === true ? 'true' : $value); + } + return implode(' ', $s); + } + + /** + * Return the current set of options and parameters seen + * as an array of canonical options and parameters. + * + * Clusters have been expanded, and option aliases + * have been mapped to their primary option names. + * + * @return array + */ + public function toArray() + { + $this->parse(); + $s = array(); + foreach ($this->_options as $flag => $value) { + $s[] = $flag; + if ($value !== true) { + $s[] = $value; + } + } + return $s; + } + + /** + * Return the current set of options and parameters seen in Json format. + * + * @return string + */ + public function toJson() + { + $this->parse(); + $j = array(); + foreach ($this->_options as $flag => $value) { + $j['options'][] = array( + 'option' => array( + 'flag' => $flag, + 'parameter' => $value + ) + ); + } + + /** + * @see Zend_Json + */ + require_once 'Zend/Json.php'; + $json = Zend_Json::encode($j); + + return $json; + } + + /** + * Return the current set of options and parameters seen in XML format. + * + * @return string + */ + public function toXml() + { + $this->parse(); + $doc = new DomDocument('1.0', 'utf-8'); + $optionsNode = $doc->createElement('options'); + $doc->appendChild($optionsNode); + foreach ($this->_options as $flag => $value) { + $optionNode = $doc->createElement('option'); + $optionNode->setAttribute('flag', utf8_encode($flag)); + if ($value !== true) { + $optionNode->setAttribute('parameter', utf8_encode($value)); + } + $optionsNode->appendChild($optionNode); + } + $xml = $doc->saveXML(); + return $xml; + } + + /** + * Return a list of options that have been seen in the current argv. + * + * @return array + */ + public function getOptions() + { + $this->parse(); + return array_keys($this->_options); + } + + /** + * Return the state of the option seen on the command line of the + * current application invocation. + * + * This function returns true, or the parameter value to the option, if any. + * If the option was not given, this function returns false. + * + * @param string $flag + * @return mixed + */ + public function getOption($flag) + { + $this->parse(); + if ($this->_getoptConfig[self::CONFIG_IGNORECASE]) { + $flag = strtolower($flag); + } + if (isset($this->_ruleMap[$flag])) { + $flag = $this->_ruleMap[$flag]; + if (isset($this->_options[$flag])) { + return $this->_options[$flag]; + } + } + return null; + } + + /** + * Return the arguments from the command-line following all options found. + * + * @return array + */ + public function getRemainingArgs() + { + $this->parse(); + return $this->_remainingArgs; + } + + /** + * Return a useful option reference, formatted for display in an + * error message. + * + * Note that this usage information is provided in most Exceptions + * generated by this class. + * + * @return string + */ + public function getUsageMessage() + { + $usage = "Usage: {$this->_progname} [ options ]\n"; + $maxLen = 20; + $lines = array(); + foreach ($this->_rules as $rule) { + $flags = array(); + if (is_array($rule['alias'])) { + foreach ($rule['alias'] as $flag) { + $flags[] = (strlen($flag) == 1 ? '-' : '--') . $flag; + } + } + $linepart['name'] = implode('|', $flags); + if (isset($rule['param']) && $rule['param'] != 'none') { + $linepart['name'] .= ' '; + switch ($rule['param']) { + case 'optional': + $linepart['name'] .= "[ <{$rule['paramType']}> ]"; + break; + case 'required': + $linepart['name'] .= "<{$rule['paramType']}>"; + break; + } + } + if (strlen($linepart['name']) > $maxLen) { + $maxLen = strlen($linepart['name']); + } + $linepart['help'] = ''; + if (isset($rule['help'])) { + $linepart['help'] .= $rule['help']; + } + $lines[] = $linepart; + } + foreach ($lines as $linepart) { + $usage .= sprintf("%s %s\n", + str_pad($linepart['name'], $maxLen), + $linepart['help']); + } + return $usage; + } + + /** + * Define aliases for options. + * + * The parameter $aliasMap is an associative array + * mapping option name (short or long) to an alias. + * + * @param array $aliasMap + * @throws Zend_Console_Getopt_Exception + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function setAliases($aliasMap) + { + foreach ($aliasMap as $flag => $alias) + { + if ($this->_getoptConfig[self::CONFIG_IGNORECASE]) { + $flag = strtolower($flag); + $alias = strtolower($alias); + } + if (!isset($this->_ruleMap[$flag])) { + continue; + } + $flag = $this->_ruleMap[$flag]; + if (isset($this->_rules[$alias]) || isset($this->_ruleMap[$alias])) { + $o = (strlen($alias) == 1 ? '-' : '--') . $alias; + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"$o\" is being defined more than once."); + } + $this->_rules[$flag]['alias'][] = $alias; + $this->_ruleMap[$alias] = $flag; + } + return $this; + } + + /** + * Define help messages for options. + * + * The parameter $help_map is an associative array + * mapping option name (short or long) to the help string. + * + * @param array $helpMap + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function setHelp($helpMap) + { + foreach ($helpMap as $flag => $help) + { + if (!isset($this->_ruleMap[$flag])) { + continue; + } + $flag = $this->_ruleMap[$flag]; + $this->_rules[$flag]['help'] = $help; + } + return $this; + } + + /** + * Parse command-line arguments and find both long and short + * options. + * + * Also find option parameters, and remaining arguments after + * all options have been parsed. + * + * @return Zend_Console_Getopt|null Provides a fluent interface + */ + public function parse() + { + if ($this->_parsed === true) { + return; + } + $argv = $this->_argv; + $this->_options = array(); + $this->_remainingArgs = array(); + while (count($argv) > 0) { + if ($argv[0] == '--') { + array_shift($argv); + if ($this->_getoptConfig[self::CONFIG_DASHDASH]) { + $this->_remainingArgs = array_merge($this->_remainingArgs, $argv); + break; + } + } + if (substr($argv[0], 0, 2) == '--') { + $this->_parseLongOption($argv); + } else if (substr($argv[0], 0, 1) == '-' && ('-' != $argv[0] || count($argv) >1)) { + $this->_parseShortOptionCluster($argv); + } else if($this->_getoptConfig[self::CONFIG_PARSEALL]) { + $this->_remainingArgs[] = array_shift($argv); + } else { + /* + * We should put all other arguments in _remainingArgs and stop parsing + * since CONFIG_PARSEALL is false. + */ + $this->_remainingArgs = array_merge($this->_remainingArgs, $argv); + break; + } + } + $this->_parsed = true; + return $this; + } + + /** + * Parse command-line arguments for a single long option. + * A long option is preceded by a double '--' character. + * Long options may not be clustered. + * + * @param mixed &$argv + * @return void + */ + protected function _parseLongOption(&$argv) + { + $optionWithParam = ltrim(array_shift($argv), '-'); + $l = explode('=', $optionWithParam, 2); + $flag = array_shift($l); + $param = array_shift($l); + if (isset($param)) { + array_unshift($argv, $param); + } + $this->_parseSingleOption($flag, $argv); + } + + /** + * Parse command-line arguments for short options. + * Short options are those preceded by a single '-' character. + * Short options may be clustered. + * + * @param mixed &$argv + * @return void + */ + protected function _parseShortOptionCluster(&$argv) + { + $flagCluster = ltrim(array_shift($argv), '-'); + foreach (str_split($flagCluster) as $flag) { + $this->_parseSingleOption($flag, $argv); + } + } + + /** + * Parse command-line arguments for a single option. + * + * @param string $flag + * @param mixed $argv + * @throws Zend_Console_Getopt_Exception + * @return void + */ + protected function _parseSingleOption($flag, &$argv) + { + if ($this->_getoptConfig[self::CONFIG_IGNORECASE]) { + $flag = strtolower($flag); + } + if (!isset($this->_ruleMap[$flag])) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"$flag\" is not recognized.", + $this->getUsageMessage()); + } + $realFlag = $this->_ruleMap[$flag]; + switch ($this->_rules[$realFlag]['param']) { + case 'required': + if (count($argv) > 0) { + $param = array_shift($argv); + $this->_checkParameterType($realFlag, $param); + } else { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"$flag\" requires a parameter.", + $this->getUsageMessage()); + } + break; + case 'optional': + if (count($argv) > 0 && substr($argv[0], 0, 1) != '-') { + $param = array_shift($argv); + $this->_checkParameterType($realFlag, $param); + } else { + $param = true; + } + break; + default: + $param = true; + } + $this->_options[$realFlag] = $param; + } + + /** + * Return true if the parameter is in a valid format for + * the option $flag. + * Throw an exception in most other cases. + * + * @param string $flag + * @param string $param + * @throws Zend_Console_Getopt_Exception + * @return bool + */ + protected function _checkParameterType($flag, $param) + { + $type = 'string'; + if (isset($this->_rules[$flag]['paramType'])) { + $type = $this->_rules[$flag]['paramType']; + } + switch ($type) { + case 'word': + if (preg_match('/\W/', $param)) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"$flag\" requires a single-word parameter, but was given \"$param\".", + $this->getUsageMessage()); + } + break; + case 'integer': + if (preg_match('/\D/', $param)) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"$flag\" requires an integer parameter, but was given \"$param\".", + $this->getUsageMessage()); + } + break; + case 'string': + default: + break; + } + return true; + } + + /** + * Define legal options using the gnu-style format. + * + * @param string $rules + * @return void + */ + protected function _addRulesModeGnu($rules) + { + $ruleArray = array(); + + /** + * Options may be single alphanumeric characters. + * Options may have a ':' which indicates a required string parameter. + * No long options or option aliases are supported in GNU style. + */ + preg_match_all('/([a-zA-Z0-9]:?)/', $rules, $ruleArray); + foreach ($ruleArray[1] as $rule) { + $r = array(); + $flag = substr($rule, 0, 1); + if ($this->_getoptConfig[self::CONFIG_IGNORECASE]) { + $flag = strtolower($flag); + } + $r['alias'][] = $flag; + if (substr($rule, 1, 1) == ':') { + $r['param'] = 'required'; + $r['paramType'] = 'string'; + } else { + $r['param'] = 'none'; + } + $this->_rules[$flag] = $r; + $this->_ruleMap[$flag] = $flag; + } + } + + /** + * Define legal options using the Zend-style format. + * + * @param array $rules + * @throws Zend_Console_Getopt_Exception + * @return void + */ + protected function _addRulesModeZend($rules) + { + foreach ($rules as $ruleCode => $helpMessage) + { + // this may have to translate the long parm type if there + // are any complaints that =string will not work (even though that use + // case is not documented) + if (in_array(substr($ruleCode, -2, 1), array('-', '='))) { + $flagList = substr($ruleCode, 0, -2); + $delimiter = substr($ruleCode, -2, 1); + $paramType = substr($ruleCode, -1); + } else { + $flagList = $ruleCode; + $delimiter = $paramType = null; + } + if ($this->_getoptConfig[self::CONFIG_IGNORECASE]) { + $flagList = strtolower($flagList); + } + $flags = explode('|', $flagList); + $rule = array(); + $mainFlag = $flags[0]; + foreach ($flags as $flag) { + if (empty($flag)) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Blank flag not allowed in rule \"$ruleCode\"."); + } + if (strlen($flag) == 1) { + if (isset($this->_ruleMap[$flag])) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"-$flag\" is being defined more than once."); + } + $this->_ruleMap[$flag] = $mainFlag; + $rule['alias'][] = $flag; + } else { + if (isset($this->_rules[$flag]) || isset($this->_ruleMap[$flag])) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"--$flag\" is being defined more than once."); + } + $this->_ruleMap[$flag] = $mainFlag; + $rule['alias'][] = $flag; + } + } + if (isset($delimiter)) { + switch ($delimiter) { + case self::PARAM_REQUIRED: + $rule['param'] = 'required'; + break; + case self::PARAM_OPTIONAL: + default: + $rule['param'] = 'optional'; + } + switch (substr($paramType, 0, 1)) { + case self::TYPE_WORD: + $rule['paramType'] = 'word'; + break; + case self::TYPE_INTEGER: + $rule['paramType'] = 'integer'; + break; + case self::TYPE_STRING: + default: + $rule['paramType'] = 'string'; + } + } else { + $rule['param'] = 'none'; + } + $rule['help'] = $helpMessage; + $this->_rules[$mainFlag] = $rule; + } + } + +} diff --git a/library/Zend/Console/Getopt/Exception.php b/library/Zend/Console/Getopt/Exception.php new file mode 100644 index 0000000..5d07de7 --- /dev/null +++ b/library/Zend/Console/Getopt/Exception.php @@ -0,0 +1,66 @@ +usage = $usage; + parent::__construct($message); + } + + /** + * Returns the usage + * + * @return string + */ + public function getUsageMessage() + { + return $this->usage; + } +} diff --git a/library/Zend/Controller/Action.php b/library/Zend/Controller/Action.php new file mode 100644 index 0000000..5da5226 --- /dev/null +++ b/library/Zend/Controller/Action.php @@ -0,0 +1,798 @@ +setRequest($request) + ->setResponse($response) + ->_setInvokeArgs($invokeArgs); + $this->_helper = new Zend_Controller_Action_HelperBroker($this); + $this->init(); + } + + /** + * Initialize object + * + * Called from {@link __construct()} as final step of object instantiation. + * + * @return void + */ + public function init() + { + } + + /** + * Initialize View object + * + * Initializes {@link $view} if not otherwise a Zend_View_Interface. + * + * If {@link $view} is not otherwise set, instantiates a new Zend_View + * object, using the 'views' subdirectory at the same level as the + * controller directory for the current module as the base directory. + * It uses this to set the following: + * - script path = views/scripts/ + * - helper path = views/helpers/ + * - filter path = views/filters/ + * + * @return Zend_View_Interface + * @throws Zend_Controller_Exception if base view directory does not exist + */ + public function initView() + { + if (!$this->getInvokeArg('noViewRenderer') && $this->_helper->hasHelper('viewRenderer')) { + return $this->view; + } + + require_once 'Zend/View/Interface.php'; + if (isset($this->view) && ($this->view instanceof Zend_View_Interface)) { + return $this->view; + } + + $request = $this->getRequest(); + $module = $request->getModuleName(); + $dirs = $this->getFrontController()->getControllerDirectory(); + if (empty($module) || !isset($dirs[$module])) { + $module = $this->getFrontController()->getDispatcher()->getDefaultModule(); + } + $baseDir = dirname($dirs[$module]) . DIRECTORY_SEPARATOR . 'views'; + if (!file_exists($baseDir) || !is_dir($baseDir)) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Missing base view directory ("' . $baseDir . '")'); + } + + require_once 'Zend/View.php'; + $this->view = new Zend_View(array('basePath' => $baseDir)); + + return $this->view; + } + + /** + * Render a view + * + * Renders a view. By default, views are found in the view script path as + *
+ * $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 {
+ while ($count > 0) {
+ if (strpos($text, '?') !== false) {
+ $text = substr_replace($text, $this->quote($value, $type), strpos($text, '?'), 1);
+ }
+ --$count;
+ }
+ return $text;
+ }
+ }
+
+ /**
+ * 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 = array();
+ 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 = false;
+ return array_keys(array_diff_key(get_object_vars($this), array('_connection'=>false)));
+ }
+
+ /**
+ * 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/library/Zend/Db/Adapter/Db2.php b/library/Zend/Db/Adapter/Db2.php
new file mode 100644
index 0000000..7d9864b
--- /dev/null
+++ b/library/Zend/Db/Adapter/Db2.php
@@ -0,0 +1,840 @@
+ (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 = array(
+ '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 = array(
+ 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 = array(
+ 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 = array();
+
+ 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 = array();
+ $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) = array(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])] = array(
+ '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 = intval($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 = intval($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) {
+ $limit_sql = $sql . " FETCH FIRST $count ROWS ONLY";
+ return $limit_sql;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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 = array();
+ 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';
+ $value = $this->fetchOne($sql);
+ return $value;
+ }
+
+ 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/library/Zend/Db/Adapter/Db2/Exception.php b/library/Zend/Db/Adapter/Db2/Exception.php
new file mode 100644
index 0000000..4962070
--- /dev/null
+++ b/library/Zend/Db/Adapter/Db2/Exception.php
@@ -0,0 +1,45 @@
+getCode();
+ }
+ parent::__construct($message, $code, $e);
+ }
+
+ public function hasChainedException()
+ {
+ return ($this->_previous !== null);
+ }
+
+ public function getChainedException()
+ {
+ return $this->getPrevious();
+ }
+
+}
diff --git a/library/Zend/Db/Adapter/Mysqli.php b/library/Zend/Db/Adapter/Mysqli.php
new file mode 100644
index 0000000..ac79cf4
--- /dev/null
+++ b/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 = array();
+ // 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 = array();
+
+ $row_defaults = array(
+ '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'])] = array(
+ '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 = intval($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 = intval($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/library/Zend/Db/Adapter/Mysqli/Exception.php b/library/Zend/Db/Adapter/Mysqli/Exception.php
new file mode 100644
index 0000000..6805e04
--- /dev/null
+++ b/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 = array(
+ '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 = array(
+ 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';
+ $value = $this->fetchOne($sql);
+ return $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 '.$this->quoteIdentifier($sequenceName, true).'.NEXTVAL FROM dual';
+ $value = $this->fetchOne($sql);
+ return $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.
+ *
+ * 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();
+ $data = $this->fetchCol('SELECT table_name FROM all_tables');
+ return $data;
+ }
+
+ /**
+ * 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 = array();
+ foreach ($result as $key => $row) {
+ list ($primary, $primaryPosition, $identity) = array(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])] = array(
+ '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 = intval($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 = intval($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.
+ */
+ $limit_sql = "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);
+ return $limit_sql;
+ }
+
+ /**
+ * @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/library/Zend/Db/Adapter/Oracle/Exception.php b/library/Zend/Db/Adapter/Oracle/Exception.php
new file mode 100644
index 0000000..c04aa58
--- /dev/null
+++ b/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/library/Zend/Db/Adapter/Pdo/Abstract.php b/library/Zend/Db/Adapter/Pdo/Abstract.php
new file mode 100644
index 0000000..fe6f5f9
--- /dev/null
+++ b/library/Zend/Db/Adapter/Pdo/Abstract.php
@@ -0,0 +1,401 @@
+_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 Zend_Db_Statement_Pdo
+ * @throws Zend_Db_Adapter_Exception To re-throw PDOException.
+ */
+ public function query($sql, $bind = array())
+ {
+ 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($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/library/Zend/Db/Adapter/Pdo/Ibm.php b/library/Zend/Db/Adapter/Pdo/Ibm.php
new file mode 100644
index 0000000..b4898b8
--- /dev/null
+++ b/library/Zend/Db/Adapter/Pdo/Ibm.php
@@ -0,0 +1,360 @@
+ 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);
+ } else {
+ throw new Zend_Db_Adapter_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+ }
+
+ /**
+ * Creates a PDO DSN for the adapter from $this->_config settings.
+ *
+ * @return string
+ */
+ 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
+ */
+ 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
+ */
+ 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
+ *
+ * @todo Discover integer unsigned property.
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ 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.
+ */
+ public function insert($table, array $bind)
+ {
+ $this->_connect();
+ $newbind = array();
+ 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
+ */
+ 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
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ $this->_connect();
+
+ if ($tableName !== null) {
+ $sequenceName = $tableName;
+ if ($primaryKey) {
+ $sequenceName .= "_$primaryKey";
+ }
+ $sequenceName .= '_seq';
+ return $this->lastSequenceId($sequenceName);
+ }
+
+ $id = $this->getConnection()->lastInsertId();
+
+ return $id;
+ }
+
+ /**
+ * Return the most recent value from the specified sequence in the database.
+ *
+ * @param string $sequenceName
+ * @return integer
+ */
+ 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
+ */
+ 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
+ */
+ 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/library/Zend/Db/Adapter/Pdo/Ibm/Db2.php b/library/Zend/Db/Adapter/Pdo/Ibm/Db2.php
new file mode 100644
index 0000000..07b4d29
--- /dev/null
+++ b/library/Zend/Db/Adapter/Pdo/Ibm/Db2.php
@@ -0,0 +1,228 @@
+_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 = array();
+ $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) {
+ list ($primary, $primaryPosition, $identity) = array(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])] = array(
+ '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 = intval($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");
+ } else {
+ $offset = intval($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) {
+ $limit_sql = $sql . " FETCH FIRST $count ROWS ONLY";
+ return $limit_sql;
+ }
+ /**
+ * 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 integer
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $sql = 'SELECT PREVVAL FOR '.$this->_adapter->quoteIdentifier($sequenceName).' AS VAL FROM SYSIBM.SYSDUMMY1';
+ $value = $this->_adapter->fetchOne($sql);
+ return $value;
+ }
+
+ /**
+ * DB2-specific sequence id value
+ *
+ * @param string $sequenceName
+ * @return integer
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $sql = 'SELECT NEXTVAL FOR '.$this->_adapter->quoteIdentifier($sequenceName).' AS VAL FROM SYSIBM.SYSDUMMY1';
+ $value = $this->_adapter->fetchOne($sql);
+ return $value;
+ }
+}
diff --git a/library/Zend/Db/Adapter/Pdo/Ibm/Ids.php b/library/Zend/Db/Adapter/Pdo/Ibm/Ids.php
new file mode 100644
index 0000000..ef357ce
--- /dev/null
+++ b/library/Zend/Db/Adapter/Pdo/Ibm/Ids.php
@@ -0,0 +1,301 @@
+_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 = array();
+ $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])] = array (
+ '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 = array(
+ 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 = array();
+
+ // 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;
+ } else {
+ $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 = intval($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");
+ } else if ($count == 0) {
+ $limit_sql = str_ireplace("SELECT", "SELECT * FROM (SELECT", $sql);
+ $limit_sql .= ") WHERE 0 = 1";
+ } else {
+ $offset = intval($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 integer
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $sql = 'SELECT '.$this->_adapter->quoteIdentifier($sequenceName).'.CURRVAL FROM '
+ .'systables WHERE tabid = 1';
+ $value = $this->_adapter->fetchOne($sql);
+ return $value;
+ }
+
+ /**
+ * IDS-specific sequence id value
+ *
+ * @param string $sequenceName
+ * @return integer
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $sql = 'SELECT '.$this->_adapter->quoteIdentifier($sequenceName).'.NEXTVAL FROM '
+ .'systables WHERE tabid = 1';
+ $value = $this->_adapter->fetchOne($sql);
+ return $value;
+ }
+}
diff --git a/library/Zend/Db/Adapter/Pdo/Mssql.php b/library/Zend/Db/Adapter/Pdo/Mssql.php
new file mode 100644
index 0000000..d654d81
--- /dev/null
+++ b/library/Zend/Db/Adapter/Pdo/Mssql.php
@@ -0,0 +1,423 @@
+ 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 = array();
+ $pkey_column_name = 3;
+ $pkey_key_seq = 4;
+ foreach ($primaryKeysResult as $pkeysRow) {
+ $primaryKeyColumn[$pkeysRow[$pkey_column_name]] = $pkeysRow[$pkey_key_seq];
+ }
+
+ $desc = array();
+ $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])] = array(
+ '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 = intval($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 = intval($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 = array();
+ 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 string
+ * @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 SERVERPROPERTY('productversion')");
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+ if (count($result)) {
+ return $result[0][0];
+ }
+ return null;
+ } catch (PDOException $e) {
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/library/Zend/Db/Adapter/Pdo/Mysql.php b/library/Zend/Db/Adapter/Pdo/Mysql.php
new file mode 100644
index 0000000..45636e5
--- /dev/null
+++ b/library/Zend/Db/Adapter/Pdo/Mysql.php
@@ -0,0 +1,270 @@
+ 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()
+ */
+ 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'])) {
+ $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 = array();
+ $i = 1;
+ $p = 1;
+ foreach ($result as $row) {
+ list($length, $scale, $precision, $unsigned, $primary, $primaryPosition, $identity)
+ = array(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])] = array(
+ '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 = intval($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 = intval($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/library/Zend/Db/Adapter/Pdo/Oci.php b/library/Zend/Db/Adapter/Pdo/Oci.php
new file mode 100644
index 0000000..8411dd9
--- /dev/null
+++ b/library/Zend/Db/Adapter/Pdo/Oci.php
@@ -0,0 +1,378 @@
+ 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()
+ {
+ $data = $this->fetchCol('SELECT table_name FROM all_tables');
+ return $data;
+ }
+
+ /**
+ * 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 = array();
+ foreach ($result as $key => $row) {
+ list ($primary, $primaryPosition, $identity) = array(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])] = array(
+ '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 integer
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $value = $this->fetchOne('SELECT '.$this->quoteIdentifier($sequenceName, true).'.CURRVAL FROM dual');
+ return $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 integer
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $value = $this->fetchOne('SELECT '.$this->quoteIdentifier($sequenceName, true).'.NEXTVAL FROM dual');
+ return $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.
+ *
+ * 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
+ * @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 = intval($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 = intval($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.
+ */
+ $limit_sql = "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);
+ return $limit_sql;
+ }
+
+}
diff --git a/library/Zend/Db/Adapter/Pdo/Pgsql.php b/library/Zend/Db/Adapter/Pdo/Pgsql.php
new file mode 100644
index 0000000..6551ed1
--- /dev/null
+++ b/library/Zend/Db/Adapter/Pdo/Pgsql.php
@@ -0,0 +1,336 @@
+ 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,
+ d.adsrc 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 = array();
+ 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) = array(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])] = array(
+ '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 = intval($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 = intval($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);
+ $value = $this->fetchOne("SELECT CURRVAL("
+ . $this->quote($this->quoteIdentifier($sequenceName, true))
+ . ")");
+ return $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();
+ $sequenceName = str_replace($this->getQuoteIdentifierSymbol(), '', (string) $sequenceName);
+ $value = $this->fetchOne("SELECT NEXTVAL("
+ . $this->quote($this->quoteIdentifier($sequenceName, true))
+ . ")");
+ return $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 !== null) {
+ $sequenceName = $tableName;
+ if ($primaryKey) {
+ $sequenceName .= "_$primaryKey";
+ }
+ $sequenceName .= '_seq';
+ return $this->lastSequenceId($sequenceName);
+ }
+ return $this->_connection->lastInsertId($tableName);
+ }
+
+}
diff --git a/library/Zend/Db/Adapter/Pdo/Sqlite.php b/library/Zend/Db/Adapter/Pdo/Sqlite.php
new file mode 100644
index 0000000..a51aae9
--- /dev/null
+++ b/library/Zend/Db/Adapter/Pdo/Sqlite.php
@@ -0,0 +1,297 @@
+ 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 = array())
+ {
+ 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 = array();
+
+ $p = 1;
+ foreach ($result as $key => $row) {
+ list($length, $scale, $precision, $primary, $primaryPosition, $identity) =
+ array(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])] = array(
+ '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 = intval($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 = intval($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/library/Zend/Db/Adapter/Sqlsrv.php b/library/Zend/Db/Adapter/Sqlsrv.php
new file mode 100644
index 0000000..e7d5a8e
--- /dev/null
+++ b/library/Zend/Db/Adapter/Sqlsrv.php
@@ -0,0 +1,678 @@
+ (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 = array(
+ '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 = array(
+ 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 = array(
+ 'Database' => $this->_config['dbname'],
+ );
+
+ if (isset($this->_config['username']) && isset($this->_config['password']))
+ {
+ $connectionInfo += array(
+ '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);
+ }
+
+ 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 = array();
+ $vals = array();
+ 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 array();
+ }
+
+ $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 = array();
+
+ // 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 = array();
+ $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])] = array(
+ '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 = intval($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 = intval($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/library/Zend/Db/Adapter/Sqlsrv/Exception.php b/library/Zend/Db/Adapter/Sqlsrv/Exception.php
new file mode 100644
index 0000000..dc358db
--- /dev/null
+++ b/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/library/Zend/Db/Profiler.php b/library/Zend/Db/Profiler.php
new file mode 100644
index 0000000..31993aa
--- /dev/null
+++ b/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 = array();
+
+ 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 = array();
+ 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/library/Zend/Db/Profiler/Exception.php b/library/Zend/Db/Profiler/Exception.php
new file mode 100644
index 0000000..14186eb
--- /dev/null
+++ b/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(array('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(array((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(array('%label%',
+ '%totalCount%',
+ '%totalDuration%'),
+ array($this->_label,
+ $this->getTotalNumQueries(),
+ (string)round($this->_totalElapsedTime,5)),
+ $this->_label_template));
+ }
+}
diff --git a/library/Zend/Db/Profiler/Query.php b/library/Zend/Db/Profiler/Query.php
new file mode 100644
index 0000000..3420e8b
--- /dev/null
+++ b/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 = array();
+ $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/library/Zend/Db/Select.php b/library/Zend/Db/Select.php
new file mode 100644
index 0000000..717e45d
--- /dev/null
+++ b/library/Zend/Db/Select.php
@@ -0,0 +1,1356 @@
+ false,
+ self::COLUMNS => array(),
+ self::UNION => array(),
+ self::FROM => array(),
+ self::WHERE => array(),
+ self::GROUP => array(),
+ self::HAVING => array(),
+ self::ORDER => array(),
+ self::LIMIT_COUNT => null,
+ self::LIMIT_OFFSET => null,
+ self::FOR_UPDATE => false
+ );
+
+ /**
+ * Specify legal join types.
+ *
+ * @var array
+ */
+ protected static $_joinTypes = array(
+ 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 = array(
+ 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 = array();
+
+ /**
+ * Tracks which columns are being select from each table and join.
+ *
+ * @var array
+ */
+ protected $_tableCols = array();
+
+ /**
+ * 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 Zend_Db_Select
+ */
+ 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 Zend_Db_Select 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 Zend_Db_Select 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 Zend_Db_Select 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 Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function union($select = array(), $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][] = array($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 Zend_Db_Select 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 Zend_Db_Select 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 Zend_Db_Select 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 Zend_Db_Select 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 Zend_Db_Select 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 Zend_Db_Select 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 Zend_Db_Select 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 Zend_Db_Select 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 Zend_Db_Select 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 Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function group($spec)
+ {
+ if (!is_array($spec)) {
+ $spec = array($spec);
+ }
+
+ foreach ($spec as $val) {
+ if (preg_match('/\(.*\)/', (string) $val)) {
+ $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 Zend_Db_Select 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 Zend_Db_Select 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 Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function order($spec)
+ {
+ if (!is_array($spec)) {
+ $spec = array($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];
+ }
+ if (preg_match('/\(.*\)/', $val)) {
+ $val = new Zend_Db_Expr($val);
+ }
+ $this->_parts[self::ORDER][] = array($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 Zend_Db_Select 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 Zend_Db_Select 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.
+ *
+ * @param bool $flag Whether or not the SELECT is FOR UPDATE (default true).
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function forUpdate($flag = true)
+ {
+ $this->_parts[self::FOR_UPDATE] = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Get part of the structured information for the currect 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 PDO_Statement|Zend_Db_Statement
+ */
+ public function query($fetchMode = null, $bind = array())
+ {
+ 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 Zend_Db_Select
+ */
+ public function reset($part = null)
+ {
+ if ($part == null) {
+ $this->_parts = self::$_partsInit;
+ } else if (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 Zend_Db_Select 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 = '';
+ } else if (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;
+ }
+ } else if ($name instanceof Zend_Db_Expr|| $name instanceof Zend_Db_Select) {
+ $tableName = $name;
+ $correlationName = $this->_uniqueCorrelation('t');
+ } else if (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] = array();
+ // 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 = array();
+ }
+ $this->_parts[self::FROM][$correlationName] = array(
+ '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 Zend_Db_Select 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 = array();
+ 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 = array($cols);
+ }
+
+ if ($correlationName == null) {
+ $correlationName = '';
+ }
+
+ $columnValues = array();
+
+ foreach (array_filter($cols) as $alias => $col) {
+ $currentCorrelationName = $correlationName;
+ if (is_string($col)) {
+ // Check for a column matching "