diff --git a/application/Bootstrap.php b/application/Bootstrap.php new file mode 100644 index 00000000..317ef5b7 --- /dev/null +++ b/application/Bootstrap.php @@ -0,0 +1,153 @@ +bootstrap('view'); + $view = $this->getResource('view'); + /* + * Si on détecte un périphérique mobile alors on charge + * les bons scripts + $theme = 'mobile'; + $path = realpath(APPLICATION_PATH.'/views/'.$theme); + $view = new Zend_View(); + $view->addBasePath($path); + */ + $theme = 'default'; + $pathStyle = '/themes/'.$theme.'/styles'; + $pathScript = '/themes/'.$theme.'/scripts'; + $view->setEncoding('UTF-8'); + $view->doctype('XHTML1_STRICT'); + $view->headMeta() + ->appendHttpEquiv('Content-Type', 'text/html; charset=UTF-8') + ->appendHttpEquiv('Content-Language', 'fr-FR'); + + $view->headLink() + ->headLink(array('rel' => 'favicon', 'type' => 'image/png', 'href' => '/favicon.png')); + + $view->headLink() + ->headLink(array('rel' => 'shortcut icon', 'type' => 'image/x-icon', 'href' => '/favicon.ico')); + + + //Get controller name + action name to define css and js to load + $view->headLink() + ->appendStylesheet($pathStyle.'/reset.css', 'all') + ->appendStylesheet($pathStyle.'/main.css', 'all') + ->appendStylesheet($pathStyle.'/menu.css', 'all') + ->appendStylesheet($pathStyle.'/jquery-ui.css', 'all') + ->appendStylesheet($pathStyle.'/jquery.qtip.css', 'all'); + + + if (APPLICATION_ENV != 'development'){ + $view->headScript() + ->appendFile($pathScript.'/min/jquery.min.js', 'text/javascript') + ->appendFile($pathScript.'/jquery.bgiframe.js', 'text/javascript') + ->appendFile($pathScript.'/min/jquery-ui.min.js', 'text/javascript') + ->appendFile($pathScript.'/min/jquery.qtip.min.js', 'text/javascript') + ->appendFile($pathScript.'/script.js', 'text/javascript'); + } else { + $view->headScript() + ->appendFile($pathScript.'/jquery.js', 'text/javascript') + ->appendFile($pathScript.'/jquery.bgiframe.js', 'text/javascript') + ->appendFile($pathScript.'/jquery-ui.js', 'text/javascript') + ->appendFile($pathScript.'/jquery.qtip.js', 'text/javascript') + ->appendFile($pathScript.'/script.js', 'text/javascript'); + } + + $view->headTitle()->setSeparator(' - '); + $view->headTitle('Extranet Scores & Décisions'); + } + + protected function _initRouter() + { + $this->bootstrap('frontController'); + $front = $this->getResource('frontController'); + $router = $front->getRouter(); + $localauthRoute = new Zend_Controller_Router_Route('localauth/', array( + 'controller' => 'user', + 'action' => 'login' + )); + $fichierRoute = new Zend_Controller_Router_Route('fichier/:action/:fichier', array( + 'controller' => 'fichier', + 'fichier' => '', + )); + $printRoute = new Zend_Controller_Router_Route('editer/:action/:fichier', array( + 'controller' => 'print', + 'fichier' => '', + )); + + $router->addRoute('localauth', $localauthRoute); + $router->addRoute('fichier', $fichierRoute); + $router->addRoute('print', $printRoute); + return $router; + } + + protected function _initLogging() + { + //Logger de développement + $writer = new Zend_Log_Writer_Firebug(); + if(APPLICATION_ENV=='production') $writer->setEnabled(false); + $logger = new Zend_Log($writer); + Zend_Registry::set('firebug', $logger); + + //Application Logger en Production + $AppLogger = new Zend_Log(); + if (APPLICATION_ENV == 'production') + { + $Mail = new Zend_Mail(); + $Mail->setFrom('production@scores-decisions.com') + ->addTo('supportdev@scores-decisions.com'); + $AppMailWriter = new Zend_Log_Writer_Mail($WsMail); + $AppMailWriter->setSubjectPrependText('ERREUR'); + $AppMailWriter->addFilter(Zend_Log::ERR); + $AppLogger->addWriter($MailWriter); + } + $config = new Zend_Config_Ini(APPLICATION_PATH . '/configs/configuration.ini', 'path'); + $path = $config->data.'/'.$config->log; + $AppFileWriter = new Zend_Log_Writer_Stream($path.'/application.log'); + $AppFileWriter->addFilter(Zend_Log::ERR); + $AppLogger->addWriter($AppFileWriter); + Zend_Registry::set('log', $AppLogger); + } + + protected function _initZFDebug() + { + if (APPLICATION_ENV == 'development') + { + $autoloader = Zend_Loader_Autoloader::getInstance(); + $autoloader->registerNamespace('ZFDebug'); + + $options = array( + 'plugins' => array( + 'Variables', + 'File' => array('base_path' => APPLICATION_PATH . '../' ), + 'Memory', + 'Time', + 'Exception') + ); + + # Instantiate the database adapter and setup the plugin. + # Alternatively just add the plugin like above and rely on the autodiscovery feature. + if ($this->hasPluginResource('db')) { + $this->bootstrap('db'); + $db = $this->getPluginResource('db')->getDbAdapter(); + $options['plugins']['Database']['adapter'] = $db; + } + + # Setup the cache plugin + if ($this->hasPluginResource('cache')) { + $this->bootstrap('cache'); + $cache = $this->getPluginResource('cache')->getDbAdapter(); + $options['plugins']['Cache']['backend'] = $cache->getBackend(); + } + + $debug = new ZFDebug_Controller_Plugin_Debug($options); + + $this->bootstrap('frontController'); + $frontController = $this->getResource('frontController'); + $frontController->registerPlugin($debug); + } + } + +} \ No newline at end of file diff --git a/application/configs/application.ini b/application/configs/application.ini new file mode 100644 index 00000000..20d8621f --- /dev/null +++ b/application/configs/application.ini @@ -0,0 +1,33 @@ +[production] +phpSettings.date.timezone = "Europe/Paris" +phpSettings.display_startup_errors = 0 +phpSettings.display_errors = 0 +includePaths.library = APPLICATION_PATH "/../library" +bootstrap.path = APPLICATION_PATH "/Bootstrap.php" +bootstrap.class = "Bootstrap" +appnamespace = "Application" +resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers" +resources.frontController.params.displayExceptions = 0 +autoloaderNamespaces[] = "Application_" +resources.frontController.plugins.Auth = "Application_Controller_Plugin_Auth" +resources.frontController.plugins.Menu = "Application_Controller_Plugin_Menu" +resources.frontController.plugins.Pdf = "Application_Controller_Plugin_Pdf" +resources.frontController.plugins.Xml = "Application_Controller_Plugin_Xml" +resources.frontController.plugins.Cgu = "Application_Controller_Plugin_Cgu" +resources.layout.layout = "main" +resources.layout.layoutPath = APPLICATION_PATH "/views/default" +resources.view.basePath = APPLICATION_PATH "/views/default" + +[staging : production] +resources.frontController.params.displayExceptions = 0 +phpSettings.soap.wsdl_cache_enabled = 0 + +[development : production] +phpSettings.display_startup_errors = 1 +phpSettings.display_errors = 1 +phpSettings.soap.wsdl_cache_enabled = 0 +resources.frontController.params.displayExceptions = 1 + +[testing : production] +phpSettings.display_startup_errors = 1 +phpSettings.display_errors = 1 \ No newline at end of file diff --git a/application/controllers/IndexController.php b/application/controllers/IndexController.php new file mode 100644 index 00000000..b3d9bbc7 --- /dev/null +++ b/application/controllers/IndexController.php @@ -0,0 +1 @@ + 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 $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 00000000..71a8b15d --- /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 00000000..33a64920 --- /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 00000000..d6d16ca9 --- /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 00000000..f5b710ba --- /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: + + + + + + + + +*/ + foreach($xml->role as $role) { + $this->_acl->addRole(new Zend_Acl_Role((string)$role["id"])); + foreach($role->user as $user) { + $this->_users[(string)$user["name"]] = array("password" => (string)$user["password"], + "role" => (string)$role["id"]); + } + } + } + + /** + * Get ACL with roles from XML file + * + * @return Zend_Acl + */ + public function getAcl() + { + return $this->_acl; + } + + /** + * Perform authentication + * + * @throws Zend_Auth_Adapter_Exception + * @return Zend_Auth_Result + * @see Zend_Auth_Adapter_Interface#authenticate() + */ + public function authenticate() + { + if (empty($this->_username) || + empty($this->_password)) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('Username/password should be set'); + } + + if(!isset($this->_users[$this->_username])) { + return new Zend_Auth_Result(Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND, + null, + array('Username not found') + ); + } + + $user = $this->_users[$this->_username]; + if($user["password"] != $this->_password) { + return new Zend_Auth_Result(Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID, + null, + array('Authentication failed') + ); + } + + $id = new stdClass(); + $id->role = $user["role"]; + $id->name = $this->_username; + return new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $id); + } +} diff --git a/library/Zend/Amf/Adobe/DbInspector.php b/library/Zend/Amf/Adobe/DbInspector.php new file mode 100644 index 00000000..2047a5bc --- /dev/null +++ b/library/Zend/Amf/Adobe/DbInspector.php @@ -0,0 +1,103 @@ +describeTable('Pdo_Mysql', + * array( + * 'host' => '127.0.0.1', + * 'username' => 'webuser', + * 'password' => 'xxxxxxxx', + * 'dbname' => 'test' + * ), + * 'mytable' + * ); + * + * @param string $dbType Database adapter type for Zend_Db + * @param array|object $dbDescription Adapter-specific connection settings + * @param string $tableName Table name + * @return array Table description + * @see Zend_Db::describeTable() + * @see Zend_Db::factory() + */ + public function describeTable($dbType, $dbDescription, $tableName) + { + $db = $this->_connect($dbType, $dbDescription); + return $db->describeTable($tableName); + } + + /** + * Test database connection + * + * @param string $dbType Database adapter type for Zend_Db + * @param array|object $dbDescription Adapter-specific connection settings + * @return bool + * @see Zend_Db::factory() + */ + public function connect($dbType, $dbDescription) + { + $db = $this->_connect($dbType, $dbDescription); + $db->listTables(); + return true; + } + + /** + * Get the list of database tables + * + * @param string $dbType Database adapter type for Zend_Db + * @param array|object $dbDescription Adapter-specific connection settings + * @return array List of the tables + */ + public function getTables($dbType, $dbDescription) + { + $db = $this->_connect($dbType, $dbDescription); + return $db->listTables(); + } +} diff --git a/library/Zend/Amf/Adobe/Introspector.php b/library/Zend/Amf/Adobe/Introspector.php new file mode 100644 index 00000000..d370c8f0 --- /dev/null +++ b/library/Zend/Amf/Adobe/Introspector.php @@ -0,0 +1,318 @@ +_xml = new DOMDocument('1.0', 'utf-8'); + } + + /** + * Create XML definition on an AMF service class + * + * @param string $serviceClass Service class name + * @param array $options invocation options + * @return string XML with service class introspection + */ + public function introspect($serviceClass, $options = array()) + { + $this->_options = $options; + + if (strpbrk($serviceClass, '\\/<>')) { + return $this->_returnError('Invalid service name'); + } + + // Transform com.foo.Bar into com_foo_Bar + $serviceClass = str_replace('.' , '_', $serviceClass); + + // Introspect! + if (!class_exists($serviceClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($serviceClass, $this->_getServicePath()); + } + + $serv = $this->_xml->createElement('service-description'); + $serv->setAttribute('xmlns', 'http://ns.adobe.com/flex/service-description/2008'); + + $this->_types = $this->_xml->createElement('types'); + $this->_ops = $this->_xml->createElement('operations'); + + $r = Zend_Server_Reflection::reflectClass($serviceClass); + $this->_addService($r, $this->_ops); + + $serv->appendChild($this->_types); + $serv->appendChild($this->_ops); + $this->_xml->appendChild($serv); + + return $this->_xml->saveXML(); + } + + /** + * Authentication handler + * + * @param Zend_Acl $acl + * @return unknown_type + */ + public function initAcl(Zend_Acl $acl) + { + return false; // we do not need auth for this class + } + + /** + * Generate map of public class attributes + * + * @param string $typename type name + * @param DOMElement $typexml target XML element + * @return void + */ + protected function _addClassAttributes($typename, DOMElement $typexml) + { + // Do not try to autoload here because _phpTypeToAS should + // have already attempted to load this class + if (!class_exists($typename, false)) { + return; + } + + $rc = new Zend_Reflection_Class($typename); + foreach ($rc->getProperties() as $prop) { + if (!$prop->isPublic()) { + continue; + } + + $propxml = $this->_xml->createElement('property'); + $propxml->setAttribute('name', $prop->getName()); + + $type = $this->_registerType($this->_getPropertyType($prop)); + $propxml->setAttribute('type', $type); + + $typexml->appendChild($propxml); + } + } + + /** + * Build XML service description from reflection class + * + * @param Zend_Server_Reflection_Class $refclass + * @param DOMElement $target target XML element + * @return void + */ + protected function _addService(Zend_Server_Reflection_Class $refclass, DOMElement $target) + { + foreach ($refclass->getMethods() as $method) { + if (!$method->isPublic() + || $method->isConstructor() + || ('__' == substr($method->name, 0, 2)) + ) { + continue; + } + + foreach ($method->getPrototypes() as $proto) { + $op = $this->_xml->createElement('operation'); + $op->setAttribute('name', $method->getName()); + + $rettype = $this->_registerType($proto->getReturnType()); + $op->setAttribute('returnType', $rettype); + + foreach ($proto->getParameters() as $param) { + $arg = $this->_xml->createElement('argument'); + $arg->setAttribute('name', $param->getName()); + + $type = $param->getType(); + if ($type == 'mixed' && ($pclass = $param->getClass())) { + $type = $pclass->getName(); + } + + $ptype = $this->_registerType($type); + $arg->setAttribute('type', $ptype); + + if($param->isDefaultValueAvailable()) { + $arg->setAttribute('defaultvalue', $param->getDefaultValue()); + } + + $op->appendChild($arg); + } + + $target->appendChild($op); + } + } + } + + /** + * Extract type of the property from DocBlock + * + * @param Zend_Reflection_Property $prop reflection property object + * @return string Property type + */ + protected function _getPropertyType(Zend_Reflection_Property $prop) + { + $docBlock = $prop->getDocComment(); + + if (!$docBlock) { + return 'Unknown'; + } + + if (!$docBlock->hasTag('var')) { + return 'Unknown'; + } + + $tag = $docBlock->getTag('var'); + return trim($tag->getDescription()); + } + + /** + * Get the array of service directories + * + * @return array Service class directories + */ + protected function _getServicePath() + { + if (isset($this->_options['server'])) { + return $this->_options['server']->getDirectory(); + } + + if (isset($this->_options['directories'])) { + return $this->_options['directories']; + } + + return array(); + } + + /** + * Map from PHP type name to AS type name + * + * @param string $typename PHP type name + * @return string AS type name + */ + protected function _phpTypeToAS($typename) + { + if (class_exists($typename)) { + $vars = get_class_vars($typename); + + if (isset($vars['_explicitType'])) { + return $vars['_explicitType']; + } + } + + if (false !== ($asname = Zend_Amf_Parse_TypeLoader::getMappedClassName($typename))) { + return $asname; + } + + return $typename; + } + + /** + * Register new type on the system + * + * @param string $typename type name + * @return string New type name + */ + protected function _registerType($typename) + { + // Known type - return its AS name + if (isset($this->_typesMap[$typename])) { + return $this->_typesMap[$typename]; + } + + // Standard types + if (in_array($typename, array('void', 'null', 'mixed', 'unknown_type'))) { + return 'Unknown'; + } + + // Arrays + if ('array' == $typename) { + return 'Unknown[]'; + } + + if (in_array($typename, array('int', 'integer', 'bool', 'boolean', 'float', 'string', 'object', 'Unknown', 'stdClass'))) { + return $typename; + } + + // Resolve and store AS name + $asTypeName = $this->_phpTypeToAS($typename); + $this->_typesMap[$typename] = $asTypeName; + + // Create element for the name + $typeEl = $this->_xml->createElement('type'); + $typeEl->setAttribute('name', $asTypeName); + $this->_addClassAttributes($typename, $typeEl); + $this->_types->appendChild($typeEl); + + return $asTypeName; + } + + /** + * Return error with error message + * + * @param string $msg Error message + * @return string + */ + protected function _returnError($msg) + { + return 'ERROR: $msg'; + } +} diff --git a/library/Zend/Amf/Auth/Abstract.php b/library/Zend/Amf/Auth/Abstract.php new file mode 100644 index 00000000..b816b80f --- /dev/null +++ b/library/Zend/Amf/Auth/Abstract.php @@ -0,0 +1,42 @@ +_username = $username; + $this->_password = $password; + } +} diff --git a/library/Zend/Amf/Constants.php b/library/Zend/Amf/Constants.php new file mode 100644 index 00000000..f92a3253 --- /dev/null +++ b/library/Zend/Amf/Constants.php @@ -0,0 +1,87 @@ +_stream->readByte(); + } + + switch($typeMarker) { + // number + case Zend_Amf_Constants::AMF0_NUMBER: + return $this->_stream->readDouble(); + + // boolean + case Zend_Amf_Constants::AMF0_BOOLEAN: + return (boolean) $this->_stream->readByte(); + + // string + case Zend_Amf_Constants::AMF0_STRING: + return $this->_stream->readUTF(); + + // object + case Zend_Amf_Constants::AMF0_OBJECT: + return $this->readObject(); + + // null + case Zend_Amf_Constants::AMF0_NULL: + return null; + + // undefined + case Zend_Amf_Constants::AMF0_UNDEFINED: + return null; + + // Circular references are returned here + case Zend_Amf_Constants::AMF0_REFERENCE: + return $this->readReference(); + + // mixed array with numeric and string keys + case Zend_Amf_Constants::AMF0_MIXEDARRAY: + return $this->readMixedArray(); + + // array + case Zend_Amf_Constants::AMF0_ARRAY: + return $this->readArray(); + + // date + case Zend_Amf_Constants::AMF0_DATE: + return $this->readDate(); + + // longString strlen(string) > 2^16 + case Zend_Amf_Constants::AMF0_LONGSTRING: + return $this->_stream->readLongUTF(); + + //internal AS object, not supported + case Zend_Amf_Constants::AMF0_UNSUPPORTED: + return null; + + // XML + case Zend_Amf_Constants::AMF0_XML: + return $this->readXmlString(); + + // typed object ie Custom Class + case Zend_Amf_Constants::AMF0_TYPEDOBJECT: + return $this->readTypedObject(); + + //AMF3-specific + case Zend_Amf_Constants::AMF0_AMF3: + return $this->readAmf3TypeMarker(); + + default: + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unsupported marker type: ' . $typeMarker); + } + } + + /** + * Read AMF objects and convert to PHP objects + * + * Read the name value pair objects form the php message and convert them to + * a php object class. + * + * Called when the marker type is 3. + * + * @param array|null $object + * @return object + */ + public function readObject($object = null) + { + if ($object === null) { + $object = array(); + } + + while (true) { + $key = $this->_stream->readUTF(); + $typeMarker = $this->_stream->readByte(); + if ($typeMarker != Zend_Amf_Constants::AMF0_OBJECTTERM ){ + //Recursivly call readTypeMarker to get the types of properties in the object + $object[$key] = $this->readTypeMarker($typeMarker); + } else { + //encountered AMF object terminator + break; + } + } + $this->_reference[] = $object; + return (object) $object; + } + + /** + * Read reference objects + * + * Used to gain access to the private array of reference objects. + * Called when marker type is 7. + * + * @return object + * @throws Zend_Amf_Exception for invalid reference keys + */ + public function readReference() + { + $key = $this->_stream->readInt(); + if (!array_key_exists($key, $this->_reference)) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Invalid reference key: '. $key); + } + return $this->_reference[$key]; + } + + /** + * Reads an array with numeric and string indexes. + * + * Called when marker type is 8 + * + * @todo As of Flash Player 9 there is not support for mixed typed arrays + * so we handle this as an object. With the introduction of vectors + * in Flash Player 10 this may need to be reconsidered. + * @return array + */ + public function readMixedArray() + { + $length = $this->_stream->readLong(); + return $this->readObject(); + } + + /** + * Converts numerically indexed actiosncript arrays into php arrays. + * + * Called when marker type is 10 + * + * @return array + */ + public function readArray() + { + $length = $this->_stream->readLong(); + $array = array(); + while ($length--) { + $array[] = $this->readTypeMarker(); + } + return $array; + } + + /** + * Convert AS Date to Zend_Date + * + * @return Zend_Date + */ + public function readDate() + { + // get the unix time stamp. Not sure why ActionScript does not use + // milliseconds + $timestamp = floor($this->_stream->readDouble() / 1000); + + // The timezone offset is never returned to the server; it is always 0, + // so read and ignore. + $offset = $this->_stream->readInt(); + + require_once 'Zend/Date.php'; + $date = new Zend_Date($timestamp); + return $date; + } + + /** + * Convert XML to SimpleXml + * If user wants DomDocument they can use dom_import_simplexml + * + * @return SimpleXml Object + */ + public function readXmlString() + { + $string = $this->_stream->readLongUTF(); + return simplexml_load_string($string); + } + + /** + * Read Class that is to be mapped to a server class. + * + * Commonly used for Value Objects on the server + * + * @todo implement Typed Class mapping + * @return object|array + * @throws Zend_Amf_Exception if unable to load type + */ + public function readTypedObject() + { + require_once 'Zend/Amf/Parse/TypeLoader.php'; + // get the remote class name + $className = $this->_stream->readUTF(); + $loader = Zend_Amf_Parse_TypeLoader::loadType($className); + $returnObject = new $loader(); + $properties = get_object_vars($this->readObject()); + foreach($properties as $key=>$value) { + if($key) { + $returnObject->$key = $value; + } + } + if($returnObject instanceof Zend_Amf_Value_Messaging_ArrayCollection) { + $returnObject = get_object_vars($returnObject); + } + return $returnObject; + } + + /** + * AMF3 data type encountered load AMF3 Deserializer to handle + * type markers. + * + * @return string + */ + public function readAmf3TypeMarker() + { + require_once 'Zend/Amf/Parse/Amf3/Deserializer.php'; + $deserializer = new Zend_Amf_Parse_Amf3_Deserializer($this->_stream); + $this->_objectEncoding = Zend_Amf_Constants::AMF3_OBJECT_ENCODING; + return $deserializer->readTypeMarker(); + } + + /** + * Return the object encoding to check if an AMF3 object + * is going to be return. + * + * @return int + */ + public function getObjectEncoding() + { + return $this->_objectEncoding; + } +} diff --git a/library/Zend/Amf/Parse/Amf0/Serializer.php b/library/Zend/Amf/Parse/Amf0/Serializer.php new file mode 100644 index 00000000..2c64a810 --- /dev/null +++ b/library/Zend/Amf/Parse/Amf0/Serializer.php @@ -0,0 +1,362 @@ +writeObjectReference($data, $markerType)) { + // Write the Type Marker to denote the following action script data type + $this->_stream->writeByte($markerType); + switch($markerType) { + case Zend_Amf_Constants::AMF0_NUMBER: + $this->_stream->writeDouble($data); + break; + case Zend_Amf_Constants::AMF0_BOOLEAN: + $this->_stream->writeByte($data); + break; + case Zend_Amf_Constants::AMF0_STRING: + $this->_stream->writeUTF($data); + break; + case Zend_Amf_Constants::AMF0_OBJECT: + $this->writeObject($data); + break; + case Zend_Amf_Constants::AMF0_NULL: + break; + case Zend_Amf_Constants::AMF0_REFERENCE: + $this->_stream->writeInt($data); + break; + case Zend_Amf_Constants::AMF0_MIXEDARRAY: + // Write length of numeric keys as zero. + $this->_stream->writeLong(0); + $this->writeObject($data); + break; + case Zend_Amf_Constants::AMF0_ARRAY: + $this->writeArray($data); + break; + case Zend_Amf_Constants::AMF0_DATE: + $this->writeDate($data); + break; + case Zend_Amf_Constants::AMF0_LONGSTRING: + $this->_stream->writeLongUTF($data); + break; + case Zend_Amf_Constants::AMF0_TYPEDOBJECT: + $this->writeTypedObject($data); + break; + case Zend_Amf_Constants::AMF0_AMF3: + $this->writeAmf3TypeMarker($data); + break; + default: + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception("Unknown Type Marker: " . $markerType); + } + } + } else { + if (is_resource($data)) { + $data = Zend_Amf_Parse_TypeLoader::handleResource($data); + } + switch (true) { + case (is_int($data) || is_float($data)): + $markerType = Zend_Amf_Constants::AMF0_NUMBER; + break; + case (is_bool($data)): + $markerType = Zend_Amf_Constants::AMF0_BOOLEAN; + break; + case (is_string($data) && (strlen($data) > 65536)): + $markerType = Zend_Amf_Constants::AMF0_LONGSTRING; + break; + case (is_string($data)): + $markerType = Zend_Amf_Constants::AMF0_STRING; + break; + case (is_object($data)): + if (($data instanceof DateTime) || ($data instanceof Zend_Date)) { + $markerType = Zend_Amf_Constants::AMF0_DATE; + } else { + + if($className = $this->getClassName($data)){ + //Object is a Typed object set classname + $markerType = Zend_Amf_Constants::AMF0_TYPEDOBJECT; + $this->_className = $className; + } else { + // Object is a generic classname + $markerType = Zend_Amf_Constants::AMF0_OBJECT; + } + break; + } + break; + case (null === $data): + $markerType = Zend_Amf_Constants::AMF0_NULL; + break; + case (is_array($data)): + // check if it is an associative array + $i = 0; + foreach (array_keys($data) as $key) { + // check if it contains non-integer keys + if (!is_numeric($key) || intval($key) != $key) { + $markerType = Zend_Amf_Constants::AMF0_OBJECT; + break; + // check if it is a sparse indexed array + } else if ($key != $i) { + $markerType = Zend_Amf_Constants::AMF0_MIXEDARRAY; + break; + } + $i++; + } + // Dealing with a standard numeric array + if(!$markerType){ + $markerType = Zend_Amf_Constants::AMF0_ARRAY; + break; + } + break; + default: + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unsupported data type: ' . gettype($data)); + } + + $this->writeTypeMarker($data, $markerType); + } + return $this; + } + + /** + * Check if the given object is in the reference table, write the reference if it exists, + * otherwise add the object to the reference table + * + * @param mixed $object object reference to check for reference + * @param string $markerType AMF type of the object to write + * @param mixed $objectByVal object to check for reference + * @return Boolean true, if the reference was written, false otherwise + */ + protected function writeObjectReference(&$object, $markerType, $objectByVal = false) + { + // Workaround for PHP5 with E_STRICT enabled complaining about "Only + // variables should be passed by reference" + if ((null === $object) && ($objectByVal !== false)) { + $object = &$objectByVal; + } + + if ($markerType == Zend_Amf_Constants::AMF0_OBJECT + || $markerType == Zend_Amf_Constants::AMF0_MIXEDARRAY + || $markerType == Zend_Amf_Constants::AMF0_ARRAY + || $markerType == Zend_Amf_Constants::AMF0_TYPEDOBJECT + ) { + $ref = array_search($object, $this->_referenceObjects, true); + //handle object reference + if($ref !== false){ + $this->writeTypeMarker($ref,Zend_Amf_Constants::AMF0_REFERENCE); + return true; + } + + $this->_referenceObjects[] = $object; + } + + return false; + } + + /** + * Write a PHP array with string or mixed keys. + * + * @param object $data + * @return Zend_Amf_Parse_Amf0_Serializer + */ + public function writeObject($object) + { + // Loop each element and write the name of the property. + foreach ($object as $key => &$value) { + // skip variables starting with an _ private transient + if( $key[0] == "_") continue; + $this->_stream->writeUTF($key); + $this->writeTypeMarker($value); + } + + // Write the end object flag + $this->_stream->writeInt(0); + $this->_stream->writeByte(Zend_Amf_Constants::AMF0_OBJECTTERM); + return $this; + } + + /** + * Write a standard numeric array to the output stream. If a mixed array + * is encountered call writeTypeMarker with mixed array. + * + * @param array $array + * @return Zend_Amf_Parse_Amf0_Serializer + */ + public function writeArray(&$array) + { + $length = count($array); + if (!$length < 0) { + // write the length of the array + $this->_stream->writeLong(0); + } else { + // Write the length of the numeric array + $this->_stream->writeLong($length); + for ($i=0; $i<$length; $i++) { + $value = isset($array[$i]) ? $array[$i] : null; + $this->writeTypeMarker($value); + } + } + return $this; + } + + /** + * Convert the DateTime into an AMF Date + * + * @param DateTime|Zend_Date $data + * @return Zend_Amf_Parse_Amf0_Serializer + */ + public function writeDate($data) + { + if ($data instanceof DateTime) { + $dateString = $data->format('U'); + } elseif ($data instanceof Zend_Date) { + $dateString = $data->toString('U'); + } else { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Invalid date specified; must be a DateTime or Zend_Date object'); + } + $dateString *= 1000; + + // Make the conversion and remove milliseconds. + $this->_stream->writeDouble($dateString); + + // Flash does not respect timezone but requires it. + $this->_stream->writeInt(0); + + return $this; + } + + /** + * Write a class mapped object to the output stream. + * + * @param object $data + * @return Zend_Amf_Parse_Amf0_Serializer + */ + public function writeTypedObject($data) + { + $this->_stream->writeUTF($this->_className); + $this->writeObject($data); + return $this; + } + + /** + * Encountered and AMF3 Type Marker use AMF3 serializer. Once AMF3 is + * encountered it will not return to AMf0. + * + * @param string $data + * @return Zend_Amf_Parse_Amf0_Serializer + */ + public function writeAmf3TypeMarker(&$data) + { + require_once 'Zend/Amf/Parse/Amf3/Serializer.php'; + $serializer = new Zend_Amf_Parse_Amf3_Serializer($this->_stream); + $serializer->writeTypeMarker($data); + return $this; + } + + /** + * Find if the class name is a class mapped name and return the + * respective classname if it is. + * + * @param object $object + * @return false|string $className + */ + protected function getClassName($object) + { + require_once 'Zend/Amf/Parse/TypeLoader.php'; + //Check to see if the object is a typed object and we need to change + $className = ''; + switch (true) { + // the return class mapped name back to actionscript class name. + case Zend_Amf_Parse_TypeLoader::getMappedClassName(get_class($object)): + $className = Zend_Amf_Parse_TypeLoader::getMappedClassName(get_class($object)); + break; + // Check to see if the user has defined an explicit Action Script type. + case isset($object->_explicitType): + $className = $object->_explicitType; + break; + // Check if user has defined a method for accessing the Action Script type + case method_exists($object, 'getASClassName'): + $className = $object->getASClassName(); + break; + // No return class name is set make it a generic object + case ($object instanceof stdClass): + $className = ''; + break; + // By default, use object's class name + default: + $className = get_class($object); + break; + } + if(!$className == '') { + return $className; + } else { + return false; + } + } +} diff --git a/library/Zend/Amf/Parse/Amf3/Deserializer.php b/library/Zend/Amf/Parse/Amf3/Deserializer.php new file mode 100644 index 00000000..ac06c07c --- /dev/null +++ b/library/Zend/Amf/Parse/Amf3/Deserializer.php @@ -0,0 +1,421 @@ +_stream->readByte(); + } + + switch($typeMarker) { + case Zend_Amf_Constants::AMF3_UNDEFINED: + return null; + case Zend_Amf_Constants::AMF3_NULL: + return null; + case Zend_Amf_Constants::AMF3_BOOLEAN_FALSE: + return false; + case Zend_Amf_Constants::AMF3_BOOLEAN_TRUE: + return true; + case Zend_Amf_Constants::AMF3_INTEGER: + return $this->readInteger(); + case Zend_Amf_Constants::AMF3_NUMBER: + return $this->_stream->readDouble(); + case Zend_Amf_Constants::AMF3_STRING: + return $this->readString(); + case Zend_Amf_Constants::AMF3_DATE: + return $this->readDate(); + case Zend_Amf_Constants::AMF3_ARRAY: + return $this->readArray(); + case Zend_Amf_Constants::AMF3_OBJECT: + return $this->readObject(); + case Zend_Amf_Constants::AMF3_XML: + case Zend_Amf_Constants::AMF3_XMLSTRING: + return $this->readXmlString(); + case Zend_Amf_Constants::AMF3_BYTEARRAY: + return $this->readString(); + default: + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unsupported type marker: ' . $typeMarker); + } + } + + /** + * Read and deserialize an integer + * + * AMF 3 represents smaller integers with fewer bytes using the most + * significant bit of each byte. The worst case uses 32-bits + * to represent a 29-bit number, which is what we would have + * done with no compression. + * - 0x00000000 - 0x0000007F : 0xxxxxxx + * - 0x00000080 - 0x00003FFF : 1xxxxxxx 0xxxxxxx + * - 0x00004000 - 0x001FFFFF : 1xxxxxxx 1xxxxxxx 0xxxxxxx + * - 0x00200000 - 0x3FFFFFFF : 1xxxxxxx 1xxxxxxx 1xxxxxxx xxxxxxxx + * - 0x40000000 - 0xFFFFFFFF : throw range exception + * + * 0x04 -> integer type code, followed by up to 4 bytes of data. + * + * Parsing integers on OSFlash for the AMF3 integer data format: + * @link http://osflash.org/amf3/parsing_integers + * @return int|float + */ + public function readInteger() + { + $count = 1; + $intReference = $this->_stream->readByte(); + $result = 0; + while ((($intReference & 0x80) != 0) && $count < 4) { + $result <<= 7; + $result |= ($intReference & 0x7f); + $intReference = $this->_stream->readByte(); + $count++; + } + if ($count < 4) { + $result <<= 7; + $result |= $intReference; + } else { + // Use all 8 bits from the 4th byte + $result <<= 8; + $result |= $intReference; + + // Check if the integer should be negative + if (($result & 0x10000000) != 0) { + //and extend the sign bit + $result |= ~0xFFFFFFF; + } + } + return $result; + } + + /** + * Read and deserialize a string + * + * Strings can be sent as a reference to a previously + * occurring String by using an index to the implicit string reference table. + * Strings are encoding using UTF-8 - however the header may either + * describe a string literal or a string reference. + * + * - string = 0x06 string-data + * - string-data = integer-data [ modified-utf-8 ] + * - modified-utf-8 = *OCTET + * + * @return String + */ + public function readString() + { + $stringReference = $this->readInteger(); + + //Check if this is a reference string + if (($stringReference & 0x01) == 0) { + // reference string + $stringReference = $stringReference >> 1; + if ($stringReference >= count($this->_referenceStrings)) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Undefined string reference: ' . $stringReference); + } + // reference string found + return $this->_referenceStrings[$stringReference]; + } + + $length = $stringReference >> 1; + if ($length) { + $string = $this->_stream->readBytes($length); + $this->_referenceStrings[] = $string; + } else { + $string = ""; + } + return $string; + } + + /** + * Read and deserialize a date + * + * Data is the number of milliseconds elapsed since the epoch + * of midnight, 1st Jan 1970 in the UTC time zone. + * Local time zone information is not sent to flash. + * + * - date = 0x08 integer-data [ number-data ] + * + * @return Zend_Date + */ + public function readDate() + { + $dateReference = $this->readInteger(); + if (($dateReference & 0x01) == 0) { + $dateReference = $dateReference >> 1; + if ($dateReference>=count($this->_referenceObjects)) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Undefined date reference: ' . $dateReference); + } + return $this->_referenceObjects[$dateReference]; + } + + $timestamp = floor($this->_stream->readDouble() / 1000); + + require_once 'Zend/Date.php'; + $dateTime = new Zend_Date($timestamp); + $this->_referenceObjects[] = $dateTime; + return $dateTime; + } + + /** + * Read amf array to PHP array + * + * - array = 0x09 integer-data ( [ 1OCTET *amf3-data ] | [OCTET *amf3-data 1] | [ OCTET *amf-data ] ) + * + * @return array + */ + public function readArray() + { + $arrayReference = $this->readInteger(); + if (($arrayReference & 0x01)==0){ + $arrayReference = $arrayReference >> 1; + if ($arrayReference>=count($this->_referenceObjects)) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unknow array reference: ' . $arrayReference); + } + return $this->_referenceObjects[$arrayReference]; + } + + // Create a holder for the array in the reference list + $data = array(); + $this->_referenceObjects[] =& $data; + $key = $this->readString(); + + // Iterating for string based keys. + while ($key != '') { + $data[$key] = $this->readTypeMarker(); + $key = $this->readString(); + } + + $arrayReference = $arrayReference >>1; + + //We have a dense array + for ($i=0; $i < $arrayReference; $i++) { + $data[] = $this->readTypeMarker(); + } + + return $data; + } + + /** + * Read an object from the AMF stream and convert it into a PHP object + * + * @todo Rather than using an array of traitsInfo create Zend_Amf_Value_TraitsInfo + * @return object|array + */ + public function readObject() + { + $traitsInfo = $this->readInteger(); + $storedObject = ($traitsInfo & 0x01)==0; + $traitsInfo = $traitsInfo >> 1; + + // Check if the Object is in the stored Objects reference table + if ($storedObject) { + $ref = $traitsInfo; + if (!isset($this->_referenceObjects[$ref])) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unknown Object reference: ' . $ref); + } + $returnObject = $this->_referenceObjects[$ref]; + } else { + // Check if the Object is in the stored Definitions reference table + $storedClass = ($traitsInfo & 0x01) == 0; + $traitsInfo = $traitsInfo >> 1; + if ($storedClass) { + $ref = $traitsInfo; + if (!isset($this->_referenceDefinitions[$ref])) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unknows Definition reference: '. $ref); + } + // Populate the reference attributes + $className = $this->_referenceDefinitions[$ref]['className']; + $encoding = $this->_referenceDefinitions[$ref]['encoding']; + $propertyNames = $this->_referenceDefinitions[$ref]['propertyNames']; + } else { + // The class was not in the reference tables. Start reading rawdata to build traits. + // Create a traits table. Zend_Amf_Value_TraitsInfo would be ideal + $className = $this->readString(); + $encoding = $traitsInfo & 0x03; + $propertyNames = array(); + $traitsInfo = $traitsInfo >> 2; + } + + // We now have the object traits defined in variables. Time to go to work: + if (!$className) { + // No class name generic object + $returnObject = new stdClass(); + } else { + // Defined object + // Typed object lookup against registered classname maps + if ($loader = Zend_Amf_Parse_TypeLoader::loadType($className)) { + $returnObject = new $loader(); + } else { + //user defined typed object + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Typed object not found: '. $className . ' '); + } + } + + // Add the Object to the reference table + $this->_referenceObjects[] = $returnObject; + + $properties = array(); // clear value + // Check encoding types for additional processing. + switch ($encoding) { + case (Zend_Amf_Constants::ET_EXTERNAL): + // Externalizable object such as {ArrayCollection} and {ObjectProxy} + if (!$storedClass) { + $this->_referenceDefinitions[] = array( + 'className' => $className, + 'encoding' => $encoding, + 'propertyNames' => $propertyNames, + ); + } + $returnObject->externalizedData = $this->readTypeMarker(); + break; + case (Zend_Amf_Constants::ET_DYNAMIC): + // used for Name-value encoding + if (!$storedClass) { + $this->_referenceDefinitions[] = array( + 'className' => $className, + 'encoding' => $encoding, + 'propertyNames' => $propertyNames, + ); + } + // not a reference object read name value properties from byte stream + do { + $property = $this->readString(); + if ($property != "") { + $propertyNames[] = $property; + $properties[$property] = $this->readTypeMarker(); + } + } while ($property !=""); + break; + default: + // basic property list object. + if (!$storedClass) { + $count = $traitsInfo; // Number of properties in the list + for($i=0; $i< $count; $i++) { + $propertyNames[] = $this->readString(); + } + // Add a reference to the class. + $this->_referenceDefinitions[] = array( + 'className' => $className, + 'encoding' => $encoding, + 'propertyNames' => $propertyNames, + ); + } + foreach ($propertyNames as $property) { + $properties[$property] = $this->readTypeMarker(); + } + break; + } + + // Add properties back to the return object. + foreach($properties as $key=>$value) { + if($key) { + $returnObject->$key = $value; + } + } + + + } + + if ($returnObject instanceof Zend_Amf_Value_Messaging_ArrayCollection) { + if (isset($returnObject->externalizedData)) { + $returnObject = $returnObject->externalizedData; + } else { + $returnObject = get_object_vars($returnObject); + } + } + + return $returnObject; + } + + /** + * Convert XML to SimpleXml + * If user wants DomDocument they can use dom_import_simplexml + * + * @return SimpleXml Object + */ + public function readXmlString() + { + $xmlReference = $this->readInteger(); + $length = $xmlReference >> 1; + $string = $this->_stream->readBytes($length); + return simplexml_load_string($string); + } +} diff --git a/library/Zend/Amf/Parse/Amf3/Serializer.php b/library/Zend/Amf/Parse/Amf3/Serializer.php new file mode 100644 index 00000000..9913a834 --- /dev/null +++ b/library/Zend/Amf/Parse/Amf3/Serializer.php @@ -0,0 +1,534 @@ +_stream->writeByte($markerType); + + switch ($markerType) { + case Zend_Amf_Constants::AMF3_NULL: + break; + case Zend_Amf_Constants::AMF3_BOOLEAN_FALSE: + break; + case Zend_Amf_Constants::AMF3_BOOLEAN_TRUE: + break; + case Zend_Amf_Constants::AMF3_INTEGER: + $this->writeInteger($data); + break; + case Zend_Amf_Constants::AMF3_NUMBER: + $this->_stream->writeDouble($data); + break; + case Zend_Amf_Constants::AMF3_STRING: + $this->writeString($data); + break; + case Zend_Amf_Constants::AMF3_DATE: + $this->writeDate($data); + break; + case Zend_Amf_Constants::AMF3_ARRAY: + $this->writeArray($data); + break; + case Zend_Amf_Constants::AMF3_OBJECT: + $this->writeObject($data); + break; + case Zend_Amf_Constants::AMF3_BYTEARRAY: + $this->writeByteArray($data); + break; + case Zend_Amf_Constants::AMF3_XMLSTRING; + $this->writeXml($data); + break; + default: + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unknown Type Marker: ' . $markerType); + } + } else { + // Detect Type Marker + if (is_resource($data)) { + $data = Zend_Amf_Parse_TypeLoader::handleResource($data); + } + switch (true) { + case (null === $data): + $markerType = Zend_Amf_Constants::AMF3_NULL; + break; + case (is_bool($data)): + if ($data){ + $markerType = Zend_Amf_Constants::AMF3_BOOLEAN_TRUE; + } else { + $markerType = Zend_Amf_Constants::AMF3_BOOLEAN_FALSE; + } + break; + case (is_int($data)): + if (($data > 0xFFFFFFF) || ($data < -268435456)) { + $markerType = Zend_Amf_Constants::AMF3_NUMBER; + } else { + $markerType = Zend_Amf_Constants::AMF3_INTEGER; + } + break; + case (is_float($data)): + $markerType = Zend_Amf_Constants::AMF3_NUMBER; + break; + case (is_string($data)): + $markerType = Zend_Amf_Constants::AMF3_STRING; + break; + case (is_array($data)): + $markerType = Zend_Amf_Constants::AMF3_ARRAY; + break; + case (is_object($data)): + // Handle object types. + if (($data instanceof DateTime) || ($data instanceof Zend_Date)) { + $markerType = Zend_Amf_Constants::AMF3_DATE; + } else if ($data instanceof Zend_Amf_Value_ByteArray) { + $markerType = Zend_Amf_Constants::AMF3_BYTEARRAY; + } else if (($data instanceof DOMDocument) || ($data instanceof SimpleXMLElement)) { + $markerType = Zend_Amf_Constants::AMF3_XMLSTRING; + } else { + $markerType = Zend_Amf_Constants::AMF3_OBJECT; + } + break; + default: + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unsupported data type: ' . gettype($data)); + } + $this->writeTypeMarker($data, $markerType); + } + } + + /** + * Write an AMF3 integer + * + * @param int|float $data + * @return Zend_Amf_Parse_Amf3_Serializer + */ + public function writeInteger($int) + { + if (($int & 0xffffff80) == 0) { + $this->_stream->writeByte($int & 0x7f); + return $this; + } + + if (($int & 0xffffc000) == 0 ) { + $this->_stream->writeByte(($int >> 7 ) | 0x80); + $this->_stream->writeByte($int & 0x7f); + return $this; + } + + if (($int & 0xffe00000) == 0) { + $this->_stream->writeByte(($int >> 14 ) | 0x80); + $this->_stream->writeByte(($int >> 7 ) | 0x80); + $this->_stream->writeByte($int & 0x7f); + return $this; + } + + $this->_stream->writeByte(($int >> 22 ) | 0x80); + $this->_stream->writeByte(($int >> 15 ) | 0x80); + $this->_stream->writeByte(($int >> 8 ) | 0x80); + $this->_stream->writeByte($int & 0xff); + return $this; + } + + /** + * Send string to output stream, without trying to reference it. + * The string is prepended with strlen($string) << 1 | 0x01 + * + * @param string $string + * @return Zend_Amf_Parse_Amf3_Serializer + */ + protected function writeBinaryString(&$string){ + $ref = strlen($string) << 1 | 0x01; + $this->writeInteger($ref); + $this->_stream->writeBytes($string); + + return $this; + } + + /** + * Send string to output stream + * + * @param string $string + * @return Zend_Amf_Parse_Amf3_Serializer + */ + public function writeString(&$string) + { + $len = strlen($string); + if(!$len){ + $this->writeInteger(0x01); + return $this; + } + + $ref = array_key_exists($string, $this->_referenceStrings) + ? $this->_referenceStrings[$string] + : false; + if ($ref === false){ + $this->_referenceStrings[$string] = count($this->_referenceStrings); + $this->writeBinaryString($string); + } else { + $ref <<= 1; + $this->writeInteger($ref); + } + + return $this; + } + + /** + * Send ByteArray to output stream + * + * @param string|Zend_Amf_Value_ByteArray $data + * @return Zend_Amf_Parse_Amf3_Serializer + */ + public function writeByteArray(&$data) + { + if ($this->writeObjectReference($data)) { + return $this; + } + + if (is_string($data)) { + //nothing to do + } else if ($data instanceof Zend_Amf_Value_ByteArray) { + $data = $data->getData(); + } else { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Invalid ByteArray specified; must be a string or Zend_Amf_Value_ByteArray'); + } + + $this->writeBinaryString($data); + + return $this; + } + + /** + * Send xml to output stream + * + * @param DOMDocument|SimpleXMLElement $xml + * @return Zend_Amf_Parse_Amf3_Serializer + */ + public function writeXml($xml) + { + if ($this->writeObjectReference($xml)) { + return $this; + } + + if(is_string($xml)) { + //nothing to do + } else if ($xml instanceof DOMDocument) { + $xml = $xml->saveXml(); + } else if ($xml instanceof SimpleXMLElement) { + $xml = $xml->asXML(); + } else { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Invalid xml specified; must be a DOMDocument or SimpleXMLElement'); + } + + $this->writeBinaryString($xml); + + return $this; + } + + /** + * Convert DateTime/Zend_Date to AMF date + * + * @param DateTime|Zend_Date $date + * @return Zend_Amf_Parse_Amf3_Serializer + */ + public function writeDate($date) + { + if ($this->writeObjectReference($date)) { + return $this; + } + + if ($date instanceof DateTime) { + $dateString = $date->format('U') * 1000; + } elseif ($date instanceof Zend_Date) { + $dateString = $date->toString('U') * 1000; + } else { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Invalid date specified; must be a string DateTime or Zend_Date object'); + } + + $this->writeInteger(0x01); + // write time to stream minus milliseconds + $this->_stream->writeDouble($dateString); + return $this; + } + + /** + * Write a PHP array back to the amf output stream + * + * @param array $array + * @return Zend_Amf_Parse_Amf3_Serializer + */ + public function writeArray(&$array) + { + // arrays aren't reference here but still counted + $this->_referenceObjects[] = $array; + + // have to seperate mixed from numberic keys. + $numeric = array(); + $string = array(); + foreach ($array as $key => &$value) { + if (is_int($key)) { + $numeric[] = $value; + } else { + $string[$key] = $value; + } + } + + // write the preamble id of the array + $length = count($numeric); + $id = ($length << 1) | 0x01; + $this->writeInteger($id); + + //Write the mixed type array to the output stream + foreach($string as $key => &$value) { + $this->writeString($key) + ->writeTypeMarker($value); + } + $this->writeString($this->_strEmpty); + + // Write the numeric array to ouput stream + foreach($numeric as &$value) { + $this->writeTypeMarker($value); + } + return $this; + } + + /** + * Check if the given object is in the reference table, write the reference if it exists, + * otherwise add the object to the reference table + * + * @param mixed $object object reference to check for reference + * @param mixed $objectByVal object to check for reference + * @return Boolean true, if the reference was written, false otherwise + */ + protected function writeObjectReference(&$object, $objectByVal = false) + { + // Workaround for PHP5 with E_STRICT enabled complaining about "Only + // variables should be passed by reference" + if ((null === $object) && ($objectByVal !== false)) { + $object = &$objectByVal; + } + + $hash = spl_object_hash($object); + $ref = array_key_exists($hash, $this->_referenceObjects) + ? $this->_referenceObjects[$hash] + : false; + + // quickly handle object references + if ($ref !== false){ + $ref <<= 1; + $this->writeInteger($ref); + return true; + } + $this->_referenceObjects[$hash] = count($this->_referenceObjects); + return false; + } + + /** + * Write object to ouput stream + * + * @param mixed $data + * @return Zend_Amf_Parse_Amf3_Serializer + */ + public function writeObject($object) + { + if($this->writeObjectReference($object)){ + return $this; + } + + $className = ''; + + //Check to see if the object is a typed object and we need to change + switch (true) { + // the return class mapped name back to actionscript class name. + case ($className = Zend_Amf_Parse_TypeLoader::getMappedClassName(get_class($object))): + break; + + // Check to see if the user has defined an explicit Action Script type. + case isset($object->_explicitType): + $className = $object->_explicitType; + break; + + // Check if user has defined a method for accessing the Action Script type + case method_exists($object, 'getASClassName'): + $className = $object->getASClassName(); + break; + + // No return class name is set make it a generic object + case ($object instanceof stdClass): + $className = ''; + break; + + // By default, use object's class name + default: + $className = get_class($object); + break; + } + + $writeTraits = true; + + //check to see, if we have a corresponding definition + if(array_key_exists($className, $this->_referenceDefinitions)){ + $traitsInfo = $this->_referenceDefinitions[$className]['id']; + $encoding = $this->_referenceDefinitions[$className]['encoding']; + $propertyNames = $this->_referenceDefinitions[$className]['propertyNames']; + + $traitsInfo = ($traitsInfo << 2) | 0x01; + + $writeTraits = false; + } else { + $propertyNames = array(); + + if($className == ''){ + //if there is no className, we interpret the class as dynamic without any sealed members + $encoding = Zend_Amf_Constants::ET_DYNAMIC; + } else { + $encoding = Zend_Amf_Constants::ET_PROPLIST; + + foreach($object as $key => $value) { + if( $key[0] != "_") { + $propertyNames[] = $key; + } + } + } + + $this->_referenceDefinitions[$className] = array( + 'id' => count($this->_referenceDefinitions), + 'encoding' => $encoding, + 'propertyNames' => $propertyNames, + ); + + $traitsInfo = Zend_Amf_Constants::AMF3_OBJECT_ENCODING; + $traitsInfo |= $encoding << 2; + $traitsInfo |= (count($propertyNames) << 4); + } + + $this->writeInteger($traitsInfo); + + if($writeTraits){ + $this->writeString($className); + foreach ($propertyNames as $value) { + $this->writeString($value); + } + } + + try { + switch($encoding) { + case Zend_Amf_Constants::ET_PROPLIST: + //Write the sealed values to the output stream. + foreach ($propertyNames as $key) { + $this->writeTypeMarker($object->$key); + } + break; + case Zend_Amf_Constants::ET_DYNAMIC: + //Write the sealed values to the output stream. + foreach ($propertyNames as $key) { + $this->writeTypeMarker($object->$key); + } + + //Write remaining properties + foreach($object as $key => $value){ + if(!in_array($key,$propertyNames) && $key[0] != "_"){ + $this->writeString($key); + $this->writeTypeMarker($value); + } + } + + //Write an empty string to end the dynamic part + $this->writeString($this->_strEmpty); + break; + case Zend_Amf_Constants::ET_EXTERNAL: + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('External Object Encoding not implemented'); + break; + default: + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unknown Object Encoding type: ' . $encoding); + } + } catch (Exception $e) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unable to writeObject output: ' . $e->getMessage(), 0, $e); + } + + return $this; + } +} diff --git a/library/Zend/Amf/Parse/Deserializer.php b/library/Zend/Amf/Parse/Deserializer.php new file mode 100644 index 00000000..a9c5a765 --- /dev/null +++ b/library/Zend/Amf/Parse/Deserializer.php @@ -0,0 +1,65 @@ +_stream = $stream; + } + + /** + * Checks for AMF marker types and calls the appropriate methods + * for deserializing those marker types. Markers are the data type of + * the following value. + * + * @param int $typeMarker + * @return mixed Whatever the data type is of the marker in php + */ + public abstract function readTypeMarker($markerType = null); +} diff --git a/library/Zend/Amf/Parse/InputStream.php b/library/Zend/Amf/Parse/InputStream.php new file mode 100644 index 00000000..190758eb --- /dev/null +++ b/library/Zend/Amf/Parse/InputStream.php @@ -0,0 +1,39 @@ + Value is Mysql type (exact string) => PHP type + */ + static public $fieldTypes = array( + "int" => "int", + "timestamp" => "int", + "year" => "int", + "real" => "float", + ); + /** + * Parse resource into array + * + * @param resource $resource + * @return array + */ + public function parse($resource) { + $result = array(); + $fieldcnt = mysql_num_fields($resource); + $fields_transform = array(); + for($i=0;$i<$fieldcnt;$i++) { + $type = mysql_field_type($resource, $i); + if(isset(self::$fieldTypes[$type])) { + $fields_transform[mysql_field_name($resource, $i)] = self::$fieldTypes[$type]; + } + } + + while($row = mysql_fetch_object($resource)) { + foreach($fields_transform as $fieldname => $fieldtype) { + settype($row->$fieldname, $fieldtype); + } + $result[] = $row; + } + return $result; + } +} diff --git a/library/Zend/Amf/Parse/Resource/MysqliResult.php b/library/Zend/Amf/Parse/Resource/MysqliResult.php new file mode 100644 index 00000000..a5124b38 --- /dev/null +++ b/library/Zend/Amf/Parse/Resource/MysqliResult.php @@ -0,0 +1,128 @@ + "MYSQLI_TYPE_DECIMAL", + 1 => "MYSQLI_TYPE_TINYINT", + 2 => "MYSQLI_TYPE_SMALLINT", + 3 => "MYSQLI_TYPE_INTEGER", + 4 => "MYSQLI_TYPE_FLOAT", + 5 => "MYSQLI_TYPE_DOUBLE", + 7 => "MYSQLI_TYPE_TIMESTAMP", + 8 => "MYSQLI_TYPE_BIGINT", + 9 => "MYSQLI_TYPE_MEDIUMINT", + 10 => "MYSQLI_TYPE_DATE", + 11 => "MYSQLI_TYPE_TIME", + 12 => "MYSQLI_TYPE_DATETIME", + 13 => "MYSQLI_TYPE_YEAR", + 14 => "MYSQLI_TYPE_DATE", + 16 => "MYSQLI_TYPE_BIT", + 246 => "MYSQLI_TYPE_DECIMAL", + 247 => "MYSQLI_TYPE_ENUM", + 248 => "MYSQLI_TYPE_SET", + 249 => "MYSQLI_TYPE_TINYBLOB", + 250 => "MYSQLI_TYPE_MEDIUMBLOB", + 251 => "MYSQLI_TYPE_LONGBLOB", + 252 => "MYSQLI_TYPE_BLOB", + 253 => "MYSQLI_TYPE_VARCHAR", + 254 => "MYSQLI_TYPE_CHAR", + 255 => "MYSQLI_TYPE_GEOMETRY", + ); + + // Build an associative array for a type look up + static $mysqli_to_php = array( + "MYSQLI_TYPE_DECIMAL" => 'float', + "MYSQLI_TYPE_NEWDECIMAL" => 'float', + "MYSQLI_TYPE_BIT" => 'integer', + "MYSQLI_TYPE_TINYINT" => 'integer', + "MYSQLI_TYPE_SMALLINT" => 'integer', + "MYSQLI_TYPE_MEDIUMINT" => 'integer', + "MYSQLI_TYPE_BIGINT" => 'integer', + "MYSQLI_TYPE_INTEGER" => 'integer', + "MYSQLI_TYPE_FLOAT" => 'float', + "MYSQLI_TYPE_DOUBLE" => 'float', + "MYSQLI_TYPE_NULL" => 'null', + "MYSQLI_TYPE_TIMESTAMP" => 'string', + "MYSQLI_TYPE_INT24" => 'integer', + "MYSQLI_TYPE_DATE" => 'string', + "MYSQLI_TYPE_TIME" => 'string', + "MYSQLI_TYPE_DATETIME" => 'string', + "MYSQLI_TYPE_YEAR" => 'string', + "MYSQLI_TYPE_NEWDATE" => 'string', + "MYSQLI_TYPE_ENUM" => 'string', + "MYSQLI_TYPE_SET" => 'string', + "MYSQLI_TYPE_TINYBLOB" => 'object', + "MYSQLI_TYPE_MEDIUMBLOB" => 'object', + "MYSQLI_TYPE_LONGBLOB" => 'object', + "MYSQLI_TYPE_BLOB" => 'object', + "MYSQLI_TYPE_CHAR" => 'string', + "MYSQLI_TYPE_VARCHAR" => 'string', + "MYSQLI_TYPE_GEOMETRY" => 'object', + "MYSQLI_TYPE_BIT" => 'integer', + ); + + /** + * Parse resource into array + * + * @param resource $resource + * @return array + */ + public function parse($resource) { + + $result = array(); + $fieldcnt = mysqli_num_fields($resource); + + + $fields_transform = array(); + + for($i=0;$i<$fieldcnt;$i++) { + $finfo = mysqli_fetch_field_direct($resource, $i); + + if(isset(self::$mysqli_type[$finfo->type])) { + $fields_transform[$finfo->name] = self::$mysqli_to_php[self::$mysqli_type[$finfo->type]]; + } + } + + while($row = mysqli_fetch_assoc($resource)) { + foreach($fields_transform as $fieldname => $fieldtype) { + settype($row[$fieldname], $fieldtype); + } + $result[] = $row; + } + return $result; + } +} diff --git a/library/Zend/Amf/Parse/Resource/Stream.php b/library/Zend/Amf/Parse/Resource/Stream.php new file mode 100644 index 00000000..29daa0d7 --- /dev/null +++ b/library/Zend/Amf/Parse/Resource/Stream.php @@ -0,0 +1,42 @@ +_stream = $stream; + } + + /** + * Find the PHP object type and convert it into an AMF object type + * + * @param mixed $content + * @param int $markerType + * @param mixed $contentByVal + * @return void + */ + public abstract function writeTypeMarker(&$content, $markerType = null, $contentByVal = false); +} diff --git a/library/Zend/Amf/Parse/TypeLoader.php b/library/Zend/Amf/Parse/TypeLoader.php new file mode 100644 index 00000000..39916882 --- /dev/null +++ b/library/Zend/Amf/Parse/TypeLoader.php @@ -0,0 +1,231 @@ + 'Zend_Amf_Value_Messaging_AcknowledgeMessage', + 'flex.messaging.messages.ErrorMessage' => 'Zend_Amf_Value_Messaging_AsyncMessage', + 'flex.messaging.messages.CommandMessage' => 'Zend_Amf_Value_Messaging_CommandMessage', + 'flex.messaging.messages.ErrorMessage' => 'Zend_Amf_Value_Messaging_ErrorMessage', + 'flex.messaging.messages.RemotingMessage' => 'Zend_Amf_Value_Messaging_RemotingMessage', + 'flex.messaging.io.ArrayCollection' => 'Zend_Amf_Value_Messaging_ArrayCollection', + ); + + /** + * @var array Default class map + */ + protected static $_defaultClassMap = array( + 'flex.messaging.messages.AcknowledgeMessage' => 'Zend_Amf_Value_Messaging_AcknowledgeMessage', + 'flex.messaging.messages.ErrorMessage' => 'Zend_Amf_Value_Messaging_AsyncMessage', + 'flex.messaging.messages.CommandMessage' => 'Zend_Amf_Value_Messaging_CommandMessage', + 'flex.messaging.messages.ErrorMessage' => 'Zend_Amf_Value_Messaging_ErrorMessage', + 'flex.messaging.messages.RemotingMessage' => 'Zend_Amf_Value_Messaging_RemotingMessage', + 'flex.messaging.io.ArrayCollection' => 'Zend_Amf_Value_Messaging_ArrayCollection', + ); + + /** + * @var Zend_Loader_PluginLoader_Interface + */ + protected static $_resourceLoader = null; + + + /** + * Load the mapped class type into a callback. + * + * @param string $className + * @return object|false + */ + public static function loadType($className) + { + $class = self::getMappedClassName($className); + if(!$class) { + $class = str_replace('.', '_', $className); + } + if (!class_exists($class)) { + return "stdClass"; + } + return $class; + } + + /** + * Looks up the supplied call name to its mapped class name + * + * @param string $className + * @return string + */ + public static function getMappedClassName($className) + { + $mappedName = array_search($className, self::$classMap); + + if ($mappedName) { + return $mappedName; + } + + $mappedName = array_search($className, array_flip(self::$classMap)); + + if ($mappedName) { + return $mappedName; + } + + return false; + } + + /** + * Map PHP class names to ActionScript class names + * + * Allows users to map the class names of there action script classes + * to the equivelent php class name. Used in deserialization to load a class + * and serialiation to set the class name of the returned object. + * + * @param string $asClassName + * @param string $phpClassName + * @return void + */ + public static function setMapping($asClassName, $phpClassName) + { + self::$classMap[$asClassName] = $phpClassName; + } + + /** + * Reset type map + * + * @return void + */ + public static function resetMap() + { + self::$classMap = self::$_defaultClassMap; + } + + /** + * Set loader for resource type handlers + * + * @param Zend_Loader_PluginLoader_Interface $loader + */ + public static function setResourceLoader(Zend_Loader_PluginLoader_Interface $loader) + { + self::$_resourceLoader = $loader; + } + + /** + * Add directory to the list of places where to look for resource handlers + * + * @param string $prefix + * @param string $dir + */ + public static function addResourceDirectory($prefix, $dir) + { + if(self::$_resourceLoader) { + self::$_resourceLoader->addPrefixPath($prefix, $dir); + } + } + + /** + * Get plugin class that handles this resource + * + * @param resource $resource Resource type + * @return string Class name + */ + public static function getResourceParser($resource) + { + if(self::$_resourceLoader) { + $type = preg_replace("/[^A-Za-z0-9_]/", " ", get_resource_type($resource)); + $type = str_replace(" ","", ucwords($type)); + return self::$_resourceLoader->load($type); + } + return false; + } + + /** + * Convert resource to a serializable object + * + * @param resource $resource + * @return mixed + */ + public static function handleResource($resource) + { + if(!self::$_resourceLoader) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unable to handle resources - resource plugin loader not set'); + } + try { + while(is_resource($resource)) { + $resclass = self::getResourceParser($resource); + if(!$resclass) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Can not serialize resource type: '. get_resource_type($resource)); + } + $parser = new $resclass(); + if(is_callable(array($parser, 'parse'))) { + $resource = $parser->parse($resource); + } else { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception("Could not call parse() method on class $resclass"); + } + } + return $resource; + } catch(Zend_Amf_Exception $e) { + throw new Zend_Amf_Exception($e->getMessage(), $e->getCode(), $e); + } catch(Exception $e) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Can not serialize resource type: '. get_resource_type($resource), 0, $e); + } + } +} diff --git a/library/Zend/Amf/Request.php b/library/Zend/Amf/Request.php new file mode 100644 index 00000000..e982efda --- /dev/null +++ b/library/Zend/Amf/Request.php @@ -0,0 +1,251 @@ +_inputStream = new Zend_Amf_Parse_InputStream($request); + $this->_deserializer = new Zend_Amf_Parse_Amf0_Deserializer($this->_inputStream); + $this->readMessage($this->_inputStream); + return $this; + } + + /** + * Takes the raw AMF input stream and converts it into valid PHP objects + * + * @param Zend_Amf_Parse_InputStream + * @return Zend_Amf_Request + */ + public function readMessage(Zend_Amf_Parse_InputStream $stream) + { + $clientVersion = $stream->readUnsignedShort(); + if (($clientVersion != Zend_Amf_Constants::AMF0_OBJECT_ENCODING) + && ($clientVersion != Zend_Amf_Constants::AMF3_OBJECT_ENCODING) + && ($clientVersion != Zend_Amf_Constants::FMS_OBJECT_ENCODING) + ) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unknown Player Version ' . $clientVersion); + } + + $this->_bodies = array(); + $this->_headers = array(); + $headerCount = $stream->readInt(); + + // Iterate through the AMF envelope header + while ($headerCount--) { + $this->_headers[] = $this->readHeader(); + } + + // Iterate through the AMF envelope body + $bodyCount = $stream->readInt(); + while ($bodyCount--) { + $this->_bodies[] = $this->readBody(); + } + + return $this; + } + + /** + * Deserialize a message header from the input stream. + * + * A message header is structured as: + * - NAME String + * - MUST UNDERSTAND Boolean + * - LENGTH Int + * - DATA Object + * + * @return Zend_Amf_Value_MessageHeader + */ + public function readHeader() + { + $name = $this->_inputStream->readUTF(); + $mustRead = (bool)$this->_inputStream->readByte(); + $length = $this->_inputStream->readLong(); + + try { + $data = $this->_deserializer->readTypeMarker(); + } catch (Exception $e) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unable to parse ' . $name . ' header data: ' . $e->getMessage() . ' '. $e->getLine(), 0, $e); + } + + $header = new Zend_Amf_Value_MessageHeader($name, $mustRead, $data, $length); + return $header; + } + + /** + * Deserialize a message body from the input stream + * + * @return Zend_Amf_Value_MessageBody + */ + public function readBody() + { + $targetURI = $this->_inputStream->readUTF(); + $responseURI = $this->_inputStream->readUTF(); + $length = $this->_inputStream->readLong(); + + try { + $data = $this->_deserializer->readTypeMarker(); + } catch (Exception $e) { + require_once 'Zend/Amf/Exception.php'; + throw new Zend_Amf_Exception('Unable to parse ' . $targetURI . ' body data ' . $e->getMessage(), 0, $e); + } + + // Check for AMF3 objectEncoding + if ($this->_deserializer->getObjectEncoding() == Zend_Amf_Constants::AMF3_OBJECT_ENCODING) { + /* + * When and AMF3 message is sent to the server it is nested inside + * an AMF0 array called Content. The following code gets the object + * out of the content array and sets it as the message data. + */ + if(is_array($data) && $data[0] instanceof Zend_Amf_Value_Messaging_AbstractMessage){ + $data = $data[0]; + } + + // set the encoding so we return our message in AMF3 + $this->_objectEncoding = Zend_Amf_Constants::AMF3_OBJECT_ENCODING; + } + + $body = new Zend_Amf_Value_MessageBody($targetURI, $responseURI, $data); + return $body; + } + + /** + * Return an array of the body objects that were found in the amf request. + * + * @return array {target, response, length, content} + */ + public function getAmfBodies() + { + return $this->_bodies; + } + + /** + * Accessor to private array of message bodies. + * + * @param Zend_Amf_Value_MessageBody $message + * @return Zend_Amf_Request + */ + public function addAmfBody(Zend_Amf_Value_MessageBody $message) + { + $this->_bodies[] = $message; + return $this; + } + + /** + * Return an array of headers that were found in the amf request. + * + * @return array {operation, mustUnderstand, length, param} + */ + public function getAmfHeaders() + { + return $this->_headers; + } + + /** + * Return the either 0 or 3 for respect AMF version + * + * @return int + */ + public function getObjectEncoding() + { + return $this->_objectEncoding; + } + + /** + * Set the object response encoding + * + * @param mixed $int + * @return Zend_Amf_Request + */ + public function setObjectEncoding($int) + { + $this->_objectEncoding = $int; + return $this; + } +} diff --git a/library/Zend/Amf/Request/Http.php b/library/Zend/Amf/Request/Http.php new file mode 100644 index 00000000..ec98ccfe --- /dev/null +++ b/library/Zend/Amf/Request/Http.php @@ -0,0 +1,80 @@ +_rawRequest = $amfRequest; + $this->initialize($amfRequest); + } else { + echo '

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 00000000..ca6283b7 --- /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 00000000..ce20a2e5 --- /dev/null +++ b/library/Zend/Amf/Response/Http.php @@ -0,0 +1,51 @@ + 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. + $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); + } + + 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); + } +} diff --git a/library/Zend/Amf/Server/Exception.php b/library/Zend/Amf/Server/Exception.php new file mode 100644 index 00000000..0b6bbc37 --- /dev/null +++ b/library/Zend/Amf/Server/Exception.php @@ -0,0 +1,37 @@ +_stream = $stream; + $this->_needle = 0; + $this->_streamLength = 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 = 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. + */ + 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: ' . $length); + } + + 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(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(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 = 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 00000000..1e6dead5 --- /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 00000000..a835fd2f --- /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-2011 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 00000000..655bf234 --- /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 00000000..eeed4b2a --- /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 00000000..d39ad810 --- /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 00000000..aeb3bed9 --- /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 00000000..10f4d447 --- /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 00000000..ecc18c64 --- /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 00000000..9d69419d --- /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 00000000..14679f9e --- /dev/null +++ b/library/Zend/Application/Bootstrap/BootstrapAbstract.php @@ -0,0 +1,770 @@ +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)) { //@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() methods + * + * @param string $method + * @param array $args + * @return void + * @throws Zend_Application_Bootstrap_Exception On invalid method name + */ + public function __call($method, $args) + { + if (9 < strlen($method) && 'bootstrap' === substr($method, 0, 9)) { + $resource = substr($method, 9); + return $this->bootstrap($resource); + } + + throw new Zend_Application_Bootstrap_Exception('Invalid method "' . $method . '"'); + } + + /** + * Bootstrap implementation + * + * This method may be overridden to provide custom bootstrapping logic. + * It is the sole method called by {@link bootstrap()}. + * + * @param null|string|array $resource + * @return void + * @throws Zend_Application_Bootstrap_Exception When invalid argument was passed + */ + protected function _bootstrap($resource = null) + { + if (null === $resource) { + foreach ($this->getClassResourceNames() as $resource) { + $this->_executeResource($resource); + } + + foreach ($this->getPluginResourceNames() as $resource) { + $this->_executeResource($resource); + } + } elseif (is_string($resource)) { + $this->_executeResource($resource); + } elseif (is_array($resource)) { + foreach ($resource as $r) { + $this->_executeResource($r); + } + } else { + throw new Zend_Application_Bootstrap_Exception('Invalid argument passed to ' . __METHOD__); + } + } + + /** + * Execute a resource + * + * Checks to see if the resource has already been run. If not, it searches + * first to see if a local method matches the resource, and executes that. + * If not, it checks to see if a plugin resource matches, and executes that + * if found. + * + * Finally, if not found, it throws an exception. + * + * @param string $resource + * @return void + * @throws Zend_Application_Bootstrap_Exception When resource not found + */ + protected function _executeResource($resource) + { + $resourceName = strtolower($resource); + + if (in_array($resourceName, $this->_run)) { + return; + } + + if (isset($this->_started[$resourceName]) && $this->_started[$resourceName]) { + throw new Zend_Application_Bootstrap_Exception('Circular resource dependency detected'); + } + + $classResources = $this->getClassResources(); + if (array_key_exists($resourceName, $classResources)) { + $this->_started[$resourceName] = true; + $method = $classResources[$resourceName]; + $return = $this->$method(); + unset($this->_started[$resourceName]); + $this->_markRun($resourceName); + + if (null !== $return) { + $this->getContainer()->{$resourceName} = $return; + } + + return; + } + + if ($this->hasPluginResource($resource)) { + $this->_started[$resourceName] = true; + $plugin = $this->getPluginResource($resource); + $return = $plugin->init(); + unset($this->_started[$resourceName]); + $this->_markRun($resourceName); + + if (null !== $return) { + $this->getContainer()->{$resourceName} = $return; + } + + return; + } + + throw new Zend_Application_Bootstrap_Exception('Resource matching "' . $resource . '" not found'); + } + + /** + * Load a plugin resource + * + * @param string $resource + * @param array|object|null $options + * @return string|false + */ + protected function _loadPluginResource($resource, $options) + { + $options = (array) $options; + $options['bootstrap'] = $this; + $className = $this->getPluginLoader()->load(strtolower($resource), false); + + if (!$className) { + return false; + } + + $instance = new $className($options); + + unset($this->_pluginResources[$resource]); + + if (isset($instance->_explicitType)) { + $resource = $instance->_explicitType; + } + $resource = strtolower($resource); + $this->_pluginResources[$resource] = $instance; + + return $resource; + } + + /** + * Mark a resource as having run + * + * @param string $resource + * @return void + */ + protected function _markRun($resource) + { + if (!in_array($resource, $this->_run)) { + $this->_run[] = $resource; + } + } + + /** + * Resolve a plugin resource name + * + * Uses, in order of preference + * - $_explicitType property of resource + * - Short name of resource (if a matching prefix path is found) + * - class name (if none of the above are true) + * + * The name is then cast to lowercase. + * + * @param Zend_Application_Resource_Resource $resource + * @return string + */ + protected function _resolvePluginResourceName($resource) + { + if (isset($resource->_explicitType)) { + $pluginName = $resource->_explicitType; + } else { + $className = get_class($resource); + $pluginName = $className; + $loader = $this->getPluginLoader(); + foreach ($loader->getPaths() as $prefix => $paths) { + if (0 === strpos($className, $prefix)) { + $pluginName = substr($className, strlen($prefix)); + $pluginName = trim($pluginName, '_'); + break; + } + } + } + $pluginName = strtolower($pluginName); + return $pluginName; + } +} diff --git a/library/Zend/Application/Bootstrap/Bootstrapper.php b/library/Zend/Application/Bootstrap/Bootstrapper.php new file mode 100644 index 00000000..dbaf0a7f --- /dev/null +++ b/library/Zend/Application/Bootstrap/Bootstrapper.php @@ -0,0 +1,94 @@ +initDefaultResourceTypes(); + } + + /** + * Initialize default resource types for module resource classes + * + * @return void + */ + public function initDefaultResourceTypes() + { + $basePath = $this->getBasePath(); + $this->addResourceTypes(array( + 'dbtable' => array( + 'namespace' => 'Model_DbTable', + 'path' => 'models/DbTable', + ), + 'mappers' => array( + 'namespace' => 'Model_Mapper', + 'path' => 'models/mappers', + ), + 'form' => array( + 'namespace' => 'Form', + 'path' => 'forms', + ), + 'model' => array( + 'namespace' => 'Model', + 'path' => 'models', + ), + 'plugin' => array( + 'namespace' => 'Plugin', + 'path' => 'plugins', + ), + 'service' => array( + 'namespace' => 'Service', + 'path' => 'services', + ), + 'viewhelper' => array( + 'namespace' => 'View_Helper', + 'path' => 'views/helpers', + ), + 'viewfilter' => array( + 'namespace' => 'View_Filter', + 'path' => 'views/filters', + ), + )); + $this->setDefaultResourceType('model'); + } +} diff --git a/library/Zend/Application/Module/Bootstrap.php b/library/Zend/Application/Module/Bootstrap.php new file mode 100644 index 00000000..97bb04b5 --- /dev/null +++ b/library/Zend/Application/Module/Bootstrap.php @@ -0,0 +1,128 @@ +setApplication($application); + + // Use same plugin loader as parent bootstrap + if ($application instanceof Zend_Application_Bootstrap_ResourceBootstrapper) { + $this->setPluginLoader($application->getPluginLoader()); + } + + $key = strtolower($this->getModuleName()); + if ($application->hasOption($key)) { + // Don't run via setOptions() to prevent duplicate initialization + $this->setOptions($application->getOption($key)); + } + + if ($application->hasOption('resourceloader')) { + $this->setOptions(array( + 'resourceloader' => $application->getOption('resourceloader') + )); + } + $this->initResourceLoader(); + + // ZF-6545: ensure front controller resource is loaded + if (!$this->hasPluginResource('FrontController')) { + $this->registerPluginResource('FrontController'); + } + + // ZF-6545: prevent recursive registration of modules + if ($this->hasPluginResource('modules')) { + $this->unregisterPluginResource('modules'); + } + } + + /** + * Ensure resource loader is loaded + * + * @return void + */ + public function initResourceLoader() + { + $this->getResourceLoader(); + } + + /** + * Get default application namespace + * + * Proxies to {@link getModuleName()}, and returns the current module + * name + * + * @return string + */ + public function getAppNamespace() + { + return $this->getModuleName(); + } + + /** + * Retrieve module name + * + * @return string + */ + public function getModuleName() + { + if (empty($this->_moduleName)) { + $class = get_class($this); + if (preg_match('/^([a-z][a-z0-9]*)_/i', $class, $matches)) { + $prefix = $matches[1]; + } else { + $prefix = $class; + } + $this->_moduleName = $prefix; + } + return $this->_moduleName; + } +} diff --git a/library/Zend/Application/Resource/Cachemanager.php b/library/Zend/Application/Resource/Cachemanager.php new file mode 100644 index 00000000..161abba3 --- /dev/null +++ b/library/Zend/Application/Resource/Cachemanager.php @@ -0,0 +1,73 @@ +getCacheManager(); + } + + /** + * Retrieve Zend_Cache_Manager instance + * + * @return Zend_Cache_Manager + */ + public function getCacheManager() + { + if (null === $this->_manager) { + $this->_manager = new Zend_Cache_Manager; + + $options = $this->getOptions(); + foreach ($options as $key => $value) { + if ($this->_manager->hasCacheTemplate($key)) { + $this->_manager->setTemplateOptions($key, $value); + } else { + $this->_manager->setCacheTemplate($key, $value); + } + } + } + + return $this->_manager; + } +} diff --git a/library/Zend/Application/Resource/Db.php b/library/Zend/Application/Resource/Db.php new file mode 100644 index 00000000..e950f134 --- /dev/null +++ b/library/Zend/Application/Resource/Db.php @@ -0,0 +1,193 @@ +_adapter = $adapter; + return $this; + } + + /** + * Adapter type to use + * + * @return string + */ + public function getAdapter() + { + return $this->_adapter; + } + + /** + * Set the adapter params + * + * @param string $adapter + * @return Zend_Application_Resource_Db + */ + public function setParams(array $params) + { + $this->_params = $params; + return $this; + } + + /** + * Adapter parameters + * + * @return array + */ + public function getParams() + { + return $this->_params; + } + + /** + * Set whether to use this as default table adapter + * + * @param boolean $defaultTableAdapter + * @return Zend_Application_Resource_Db + */ + public function setIsDefaultTableAdapter($isDefaultTableAdapter) + { + $this->_isDefaultTableAdapter = $isDefaultTableAdapter; + return $this; + } + + /** + * Is this adapter the default table adapter? + * + * @return void + */ + public function isDefaultTableAdapter() + { + return $this->_isDefaultTableAdapter; + } + + /** + * Retrieve initialized DB connection + * + * @return null|Zend_Db_Adapter_Abstract + */ + public function getDbAdapter() + { + if ((null === $this->_db) + && (null !== ($adapter = $this->getAdapter())) + ) { + $this->_db = Zend_Db::factory($adapter, $this->getParams()); + } + return $this->_db; + } + + /** + * Defined by Zend_Application_Resource_Resource + * + * @return Zend_Db_Adapter_Abstract|null + */ + public function init() + { + if (null !== ($db = $this->getDbAdapter())) { + if ($this->isDefaultTableAdapter()) { + Zend_Db_Table::setDefaultAdapter($db); + } + return $db; + } + } + + /** + * Set the default metadata cache + * + * @param string|Zend_Cache_Core $cache + * @return Zend_Application_Resource_Db + */ + public function setDefaultMetadataCache($cache) + { + $metadataCache = null; + + if (is_string($cache)) { + $bootstrap = $this->getBootstrap(); + if ($bootstrap instanceof Zend_Application_Bootstrap_ResourceBootstrapper + && $bootstrap->hasPluginResource('CacheManager') + ) { + $cacheManager = $bootstrap->bootstrap('CacheManager') + ->getResource('CacheManager'); + if (null !== $cacheManager && $cacheManager->hasCache($cache)) { + $metadataCache = $cacheManager->getCache($cache); + } + } + } else if ($cache instanceof Zend_Cache_Core) { + $metadataCache = $cache; + } + + if ($metadataCache instanceof Zend_Cache_Core) { + Zend_Db_Table::setDefaultMetadataCache($metadataCache); + } + + return $this; + } +} diff --git a/library/Zend/Application/Resource/Dojo.php b/library/Zend/Application/Resource/Dojo.php new file mode 100644 index 00000000..d604abf7 --- /dev/null +++ b/library/Zend/Application/Resource/Dojo.php @@ -0,0 +1,76 @@ +getDojo(); + } + + /** + * Retrieve Dojo View Helper + * + * @return Zend_Dojo_View_Dojo_Container + */ + public function getDojo() + { + if (null === $this->_dojo) { + $this->getBootstrap()->bootstrap('view'); + $view = $this->getBootstrap()->view; + + Zend_Dojo::enableView($view); + $view->dojo()->setOptions($this->getOptions()); + + $this->_dojo = $view->dojo(); + } + + return $this->_dojo; + } +} diff --git a/library/Zend/Application/Resource/Exception.php b/library/Zend/Application/Resource/Exception.php new file mode 100644 index 00000000..fa77c48a --- /dev/null +++ b/library/Zend/Application/Resource/Exception.php @@ -0,0 +1,40 @@ +getFrontController(); + + foreach ($this->getOptions() as $key => $value) { + switch (strtolower($key)) { + case 'controllerdirectory': + if (is_string($value)) { + $front->setControllerDirectory($value); + } elseif (is_array($value)) { + foreach ($value as $module => $directory) { + $front->addControllerDirectory($directory, $module); + } + } + break; + + case 'modulecontrollerdirectoryname': + $front->setModuleControllerDirectoryName($value); + break; + + case 'moduledirectory': + if (is_string($value)) { + $front->addModuleDirectory($value); + } elseif (is_array($value)) { + foreach($value as $moduleDir) { + $front->addModuleDirectory($moduleDir); + } + } + break; + + case 'defaultcontrollername': + $front->setDefaultControllerName($value); + break; + + case 'defaultaction': + $front->setDefaultAction($value); + break; + + case 'defaultmodule': + $front->setDefaultModule($value); + break; + + case 'baseurl': + if (!empty($value)) { + $front->setBaseUrl($value); + } + break; + + case 'params': + $front->setParams($value); + break; + + case 'plugins': + foreach ((array) $value as $pluginClass) { + $stackIndex = null; + if(is_array($pluginClass)) { + $pluginClass = array_change_key_case($pluginClass, CASE_LOWER); + if(isset($pluginClass['class'])) + { + if(isset($pluginClass['stackindex'])) { + $stackIndex = $pluginClass['stackindex']; + } + + $pluginClass = $pluginClass['class']; + } + } + + $plugin = new $pluginClass(); + $front->registerPlugin($plugin, $stackIndex); + } + break; + + case 'returnresponse': + $front->returnResponse((bool) $value); + break; + + case 'throwexceptions': + $front->throwExceptions((bool) $value); + break; + + case 'actionhelperpaths': + if (is_array($value)) { + foreach ($value as $helperPrefix => $helperPath) { + Zend_Controller_Action_HelperBroker::addPath($helperPath, $helperPrefix); + } + } + break; + + default: + $front->setParam($key, $value); + break; + } + } + + if (null !== ($bootstrap = $this->getBootstrap())) { + $this->getBootstrap()->frontController = $front; + } + + return $front; + } + + /** + * Retrieve front controller instance + * + * @return Zend_Controller_Front + */ + public function getFrontController() + { + if (null === $this->_front) { + $this->_front = Zend_Controller_Front::getInstance(); + } + return $this->_front; + } +} diff --git a/library/Zend/Application/Resource/Layout.php b/library/Zend/Application/Resource/Layout.php new file mode 100644 index 00000000..d4249c28 --- /dev/null +++ b/library/Zend/Application/Resource/Layout.php @@ -0,0 +1,70 @@ +getBootstrap()->bootstrap('FrontController'); + return $this->getLayout(); + } + + /** + * Retrieve layout object + * + * @return Zend_Layout + */ + public function getLayout() + { + if (null === $this->_layout) { + $this->_layout = Zend_Layout::startMvc($this->getOptions()); + } + return $this->_layout; + } +} diff --git a/library/Zend/Application/Resource/Locale.php b/library/Zend/Application/Resource/Locale.php new file mode 100644 index 00000000..87d29e79 --- /dev/null +++ b/library/Zend/Application/Resource/Locale.php @@ -0,0 +1,117 @@ +getLocale(); + } + + /** + * Retrieve locale object + * + * @return Zend_Locale + */ + public function getLocale() + { + if (null === $this->_locale) { + $options = $this->getOptions(); + + if (!isset($options['default'])) { + $this->_locale = new Zend_Locale(); + } elseif(!isset($options['force']) || + (bool) $options['force'] == false) + { + // Don't force any locale, just go for auto detection + Zend_Locale::setDefault($options['default']); + $this->_locale = new Zend_Locale(); + } else { + $this->_locale = new Zend_Locale($options['default']); + } + + $key = (isset($options['registry_key']) && !is_numeric($options['registry_key'])) + ? $options['registry_key'] + : self::DEFAULT_REGISTRY_KEY; + Zend_Registry::set($key, $this->_locale); + } + + return $this->_locale; + } + + /** + * Set the cache + * + * @param string|Zend_Cache_Core $cache + * @return Zend_Application_Resource_Locale + */ + public function setCache($cache) + { + if (is_string($cache)) { + $bootstrap = $this->getBootstrap(); + if ($bootstrap instanceof Zend_Application_Bootstrap_ResourceBootstrapper + && $bootstrap->hasPluginResource('CacheManager') + ) { + $cacheManager = $bootstrap->bootstrap('CacheManager') + ->getResource('CacheManager'); + if (null !== $cacheManager && $cacheManager->hasCache($cache)) { + $cache = $cacheManager->getCache($cache); + } + } + } + + if ($cache instanceof Zend_Cache_Core) { + Zend_Locale::setCache($cache); + } + + return $this; + } +} diff --git a/library/Zend/Application/Resource/Log.php b/library/Zend/Application/Resource/Log.php new file mode 100644 index 00000000..8a612974 --- /dev/null +++ b/library/Zend/Application/Resource/Log.php @@ -0,0 +1,78 @@ +getLog(); + } + + /** + * Attach logger + * + * @param Zend_Log $log + * @return Zend_Application_Resource_Log + */ + public function setLog(Zend_Log $log) + { + $this->_log = $log; + return $this; + } + + public function getLog() + { + if (null === $this->_log) { + $options = $this->getOptions(); + $log = Zend_Log::factory($options); + $this->setLog($log); + } + return $this->_log; + } +} diff --git a/library/Zend/Application/Resource/Mail.php b/library/Zend/Application/Resource/Mail.php new file mode 100644 index 00000000..ff311534 --- /dev/null +++ b/library/Zend/Application/Resource/Mail.php @@ -0,0 +1,147 @@ +getMail(); + } + + /** + * + * @return Zend_Mail_Transport_Abstract|null + */ + public function getMail() + { + if (null === $this->_transport) { + $options = $this->getOptions(); + foreach($options as $key => $option) { + $options[strtolower($key)] = $option; + } + $this->setOptions($options); + + if(isset($options['transport']) && + !is_numeric($options['transport'])) + { + $this->_transport = $this->_setupTransport($options['transport']); + if(!isset($options['transport']['register']) || + $options['transport']['register'] == '1' || + (isset($options['transport']['register']) && + !is_numeric($options['transport']['register']) && + (bool) $options['transport']['register'] == true)) + { + Zend_Mail::setDefaultTransport($this->_transport); + } + } + + $this->_setDefaults('from'); + $this->_setDefaults('replyTo'); + } + + return $this->_transport; + } + + protected function _setDefaults($type) { + $key = strtolower('default' . $type); + $options = $this->getOptions(); + + if(isset($options[$key]['email']) && + !is_numeric($options[$key]['email'])) + { + $method = array('Zend_Mail', 'setDefault' . ucfirst($type)); + if(isset($options[$key]['name']) && + !is_numeric($options[$key]['name'])) + { + call_user_func($method, $options[$key]['email'], + $options[$key]['name']); + } else { + call_user_func($method, $options[$key]['email']); + } + } + } + + protected function _setupTransport($options) + { + if(!isset($options['type'])) { + $options['type'] = 'sendmail'; + } + + $transportName = $options['type']; + if(!Zend_Loader_Autoloader::autoload($transportName)) + { + $transportName = ucfirst(strtolower($transportName)); + + if(!Zend_Loader_Autoloader::autoload($transportName)) + { + $transportName = 'Zend_Mail_Transport_' . $transportName; + if(!Zend_Loader_Autoloader::autoload($transportName)) { + throw new Zend_Application_Resource_Exception( + "Specified Mail Transport '{$transportName}'" + . 'could not be found' + ); + } + } + } + + unset($options['type']); + unset($options['register']); //@see ZF-11022 + + switch($transportName) { + case 'Zend_Mail_Transport_Smtp': + if(!isset($options['host'])) { + throw new Zend_Application_Resource_Exception( + 'A host is necessary for smtp transport,' + .' but none was given'); + } + + $transport = new $transportName($options['host'], $options); + break; + case 'Zend_Mail_Transport_Sendmail': + default: + $transport = new $transportName($options); + break; + } + + return $transport; + } +} diff --git a/library/Zend/Application/Resource/Modules.php b/library/Zend/Application/Resource/Modules.php new file mode 100644 index 00000000..c01e9437 --- /dev/null +++ b/library/Zend/Application/Resource/Modules.php @@ -0,0 +1,155 @@ +_bootstraps = new ArrayObject(array(), ArrayObject::ARRAY_AS_PROPS); + parent::__construct($options); + } + + /** + * Initialize modules + * + * @return array + * @throws Zend_Application_Resource_Exception When bootstrap class was not found + */ + public function init() + { + $bootstraps = array(); + $bootstrap = $this->getBootstrap(); + $bootstrap->bootstrap('FrontController'); + $front = $bootstrap->getResource('FrontController'); + + $modules = $front->getControllerDirectory(); + $default = $front->getDefaultModule(); + $curBootstrapClass = get_class($bootstrap); + foreach ($modules as $module => $moduleDirectory) { + $bootstrapClass = $this->_formatModuleName($module) . '_Bootstrap'; + if (!class_exists($bootstrapClass, false)) { + $bootstrapPath = dirname($moduleDirectory) . '/Bootstrap.php'; + if (file_exists($bootstrapPath)) { + $eMsgTpl = 'Bootstrap file found for module "%s" but bootstrap class "%s" not found'; + include_once $bootstrapPath; + if (($default != $module) + && !class_exists($bootstrapClass, false) + ) { + throw new Zend_Application_Resource_Exception(sprintf( + $eMsgTpl, $module, $bootstrapClass + )); + } elseif ($default == $module) { + if (!class_exists($bootstrapClass, false)) { + $bootstrapClass = 'Bootstrap'; + if (!class_exists($bootstrapClass, false)) { + throw new Zend_Application_Resource_Exception(sprintf( + $eMsgTpl, $module, $bootstrapClass + )); + } + } + } + } else { + continue; + } + } + + if ($bootstrapClass == $curBootstrapClass) { + // If the found bootstrap class matches the one calling this + // resource, don't re-execute. + continue; + } + + $bootstraps[$module] = $bootstrapClass; + } + + return $this->_bootstraps = $this->bootstrapBootstraps($bootstraps); + } + + /* + * Bootstraps the bootstraps found. Allows for easy extension. + * @param array $bootstraps Array containing the bootstraps to instantiate + */ + protected function bootstrapBootstraps($bootstraps) + { + $bootstrap = $this->getBootstrap(); + $out = new ArrayObject(array(), ArrayObject::ARRAY_AS_PROPS); + + foreach($bootstraps as $module => $bootstrapClass) { + $moduleBootstrap = new $bootstrapClass($bootstrap); + $moduleBootstrap->bootstrap(); + $out[$module] = $moduleBootstrap; + } + + return $out; + } + + /** + * Get bootstraps that have been run + * + * @return ArrayObject + */ + public function getExecutedBootstraps() + { + return $this->_bootstraps; + } + + /** + * Format a module name to the module class prefix + * + * @param string $name + * @return string + */ + protected function _formatModuleName($name) + { + $name = strtolower($name); + $name = str_replace(array('-', '.'), ' ', $name); + $name = ucwords($name); + $name = str_replace(' ', '', $name); + return $name; + } +} diff --git a/library/Zend/Application/Resource/Multidb.php b/library/Zend/Application/Resource/Multidb.php new file mode 100644 index 00000000..20ba661f --- /dev/null +++ b/library/Zend/Application/Resource/Multidb.php @@ -0,0 +1,210 @@ + + * resources.multidb.defaultMetadataCache = "database" + * + * resources.multidb.db1.adapter = "pdo_mysql" + * resources.multidb.db1.host = "localhost" + * resources.multidb.db1.username = "webuser" + * resources.multidb.db1.password = "XXXX" + * resources.multidb.db1.dbname = "db1" + * resources.multidb.db1.default = true + * + * resources.multidb.db2.adapter = "pdo_pgsql" + * resources.multidb.db2.host = "example.com" + * resources.multidb.db2.username = "dba" + * resources.multidb.db2.password = "notthatpublic" + * resources.multidb.db2.dbname = "db2" + * + * + * @category Zend + * @package Zend_Application + * @subpackage Resource + * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Application_Resource_Multidb extends Zend_Application_Resource_ResourceAbstract +{ + /** + * Associative array containing all configured db's + * + * @var array + */ + protected $_dbs = array(); + + /** + * An instance of the default db, if set + * + * @var null|Zend_Db_Adapter_Abstract + */ + protected $_defaultDb; + + /** + * Initialize the Database Connections (instances of Zend_Db_Table_Abstract) + * + * @return Zend_Application_Resource_Multidb + */ + public function init() + { + $options = $this->getOptions(); + + if (isset($options['defaultMetadataCache'])) { + $this->_setDefaultMetadataCache($options['defaultMetadataCache']); + unset($options['defaultMetadataCache']); + } + + foreach ($options as $id => $params) { + $adapter = $params['adapter']; + $default = (int) ( + isset($params['isDefaultTableAdapter']) && $params['isDefaultTableAdapter'] + || isset($params['default']) && $params['default'] + ); + unset( + $params['adapter'], + $params['default'], + $params['isDefaultTableAdapter'] + ); + + $this->_dbs[$id] = Zend_Db::factory($adapter, $params); + + if ($default) { + $this->_setDefault($this->_dbs[$id]); + } + } + + return $this; + } + + /** + * Determine if the given db(identifier) is the default db. + * + * @param string|Zend_Db_Adapter_Abstract $db The db to determine whether it's set as default + * @return boolean True if the given parameter is configured as default. False otherwise + */ + public function isDefault($db) + { + if(!$db instanceof Zend_Db_Adapter_Abstract) { + $db = $this->getDb($db); + } + + return $db === $this->_defaultDb; + } + + /** + * Retrieve the specified database connection + * + * @param null|string|Zend_Db_Adapter_Abstract $db The adapter to retrieve. + * Null to retrieve the default connection + * @return Zend_Db_Adapter_Abstract + * @throws Zend_Application_Resource_Exception if the given parameter could not be found + */ + public function getDb($db = null) + { + if ($db === null) { + return $this->getDefaultDb(); + } + + if (isset($this->_dbs[$db])) { + return $this->_dbs[$db]; + } + + throw new Zend_Application_Resource_Exception( + 'A DB adapter was tried to retrieve, but was not configured' + ); + } + + /** + * Get the default db connection + * + * @param boolean $justPickOne If true, a random (the first one in the stack) + * connection is returned if no default was set. + * If false, null is returned if no default was set. + * @return null|Zend_Db_Adapter_Abstract + */ + public function getDefaultDb($justPickOne = true) + { + if ($this->_defaultDb !== null) { + return $this->_defaultDb; + } + + if ($justPickOne) { + return reset($this->_dbs); // Return first db in db pool + } + + return null; + } + + /** + * Set the default db adapter + * + * @var Zend_Db_Adapter_Abstract $adapter Adapter to set as default + */ + protected function _setDefault(Zend_Db_Adapter_Abstract $adapter) + { + Zend_Db_Table::setDefaultAdapter($adapter); + $this->_defaultDb = $adapter; + } + + /** + * Set the default metadata cache + * + * @param string|Zend_Cache_Core $cache + * @return Zend_Application_Resource_Multidb + */ + protected function _setDefaultMetadataCache($cache) + { + $metadataCache = null; + + if (is_string($cache)) { + $bootstrap = $this->getBootstrap(); + if ($bootstrap instanceof Zend_Application_Bootstrap_ResourceBootstrapper && + $bootstrap->hasPluginResource('CacheManager') + ) { + $cacheManager = $bootstrap->bootstrap('CacheManager') + ->getResource('CacheManager'); + if (null !== $cacheManager && $cacheManager->hasCache($cache)) { + $metadataCache = $cacheManager->getCache($cache); + } + } + } else if ($cache instanceof Zend_Cache_Core) { + $metadataCache = $cache; + } + + if ($metadataCache instanceof Zend_Cache_Core) { + Zend_Db_Table::setDefaultMetadataCache($metadataCache); + } + + return $this; + } +} diff --git a/library/Zend/Application/Resource/Navigation.php b/library/Zend/Application/Resource/Navigation.php new file mode 100644 index 00000000..580e432d --- /dev/null +++ b/library/Zend/Application/Resource/Navigation.php @@ -0,0 +1,128 @@ +_container) { + $options = $this->getOptions(); + + if(isset($options['defaultPageType'])) { + Zend_Navigation_Page::setDefaultPageType($options['defaultPageType']); + } + + $pages = isset($options['pages']) ? $options['pages'] : array(); + $this->_container = new Zend_Navigation($pages); + } + + $this->store(); + return $this->_container; + } + + /** + * Stores navigation container in registry or Navigation view helper + * + * @return void + */ + public function store() + { + $options = $this->getOptions(); + if (isset($options['storage']['registry']) && + $options['storage']['registry'] == true) { + $this->_storeRegistry(); + } else { + $this->_storeHelper(); + } + } + + /** + * Stores navigation container in the registry + * + * @return void + */ + protected function _storeRegistry() + { + $options = $this->getOptions(); + if(isset($options['storage']['registry']['key']) && + !is_numeric($options['storage']['registry']['key'])) // see ZF-7461 + { + $key = $options['storage']['registry']['key']; + } else { + $key = self::DEFAULT_REGISTRY_KEY; + } + + Zend_Registry::set($key,$this->getContainer()); + } + + /** + * Stores navigation container in the Navigation helper + * + * @return void + */ + protected function _storeHelper() + { + $this->getBootstrap()->bootstrap('view'); + $view = $this->getBootstrap()->view; + $view->getHelper('navigation')->navigation($this->getContainer()); + } + + /** + * Returns navigation container + * + * @return Zend_Navigation + */ + public function getContainer() + { + return $this->_container; + } +} diff --git a/library/Zend/Application/Resource/Resource.php b/library/Zend/Application/Resource/Resource.php new file mode 100644 index 00000000..670b27da --- /dev/null +++ b/library/Zend/Application/Resource/Resource.php @@ -0,0 +1,80 @@ +setOptions($options); + } else if ($options instanceof Zend_Config) { + $this->setOptions($options->toArray()); + } + } + + /** + * Set options from array + * + * @param array $options Configuration for resource + * @return Zend_Application_Resource_ResourceAbstract + */ + public function setOptions(array $options) + { + if (array_key_exists('bootstrap', $options)) { + $this->setBootstrap($options['bootstrap']); + unset($options['bootstrap']); + } + + foreach ($options as $key => $value) { + if (in_array(strtolower($key), $this->_skipOptions)) { + continue; + } + + $method = 'set' . strtolower($key); + if (method_exists($this, $method)) { + $this->$method($value); + } + } + + $this->_options = $this->mergeOptions($this->_options, $options); + + return $this; + } + + /** + * Retrieve resource options + * + * @return array + */ + public function getOptions() + { + return $this->_options; + } + + /** + * 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 the bootstrap to which the resource is attached + * + * @param Zend_Application_Bootstrap_Bootstrapper $bootstrap + * @return Zend_Application_Resource_Resource + */ + public function setBootstrap(Zend_Application_Bootstrap_Bootstrapper $bootstrap) + { + $this->_bootstrap = $bootstrap; + return $this; + } + + /** + * Retrieve the bootstrap to which the resource is attached + * + * @return null|Zend_Application_Bootstrap_Bootstrapper + */ + public function getBootstrap() + { + return $this->_bootstrap; + } +} diff --git a/library/Zend/Application/Resource/Router.php b/library/Zend/Application/Resource/Router.php new file mode 100644 index 00000000..9a570d23 --- /dev/null +++ b/library/Zend/Application/Resource/Router.php @@ -0,0 +1,87 @@ +getRouter(); + } + + /** + * Retrieve router object + * + * @return Zend_Controller_Router_Rewrite + */ + public function getRouter() + { + if (null === $this->_router) { + $bootstrap = $this->getBootstrap(); + $bootstrap->bootstrap('FrontController'); + $this->_router = $bootstrap->getContainer()->frontcontroller->getRouter(); + + $options = $this->getOptions(); + if (!isset($options['routes'])) { + $options['routes'] = array(); + } + + if (isset($options['chainNameSeparator'])) { + $this->_router->setChainNameSeparator($options['chainNameSeparator']); + } + + if (isset($options['useRequestParametersAsGlobal'])) { + $this->_router->useRequestParametersAsGlobal($options['useRequestParametersAsGlobal']); + } + + $this->_router->addConfig(new Zend_Config($options['routes'])); + } + + return $this->_router; + } +} diff --git a/library/Zend/Application/Resource/Session.php b/library/Zend/Application/Resource/Session.php new file mode 100644 index 00000000..212e61d4 --- /dev/null +++ b/library/Zend/Application/Resource/Session.php @@ -0,0 +1,118 @@ +_saveHandler = $saveHandler; + return $this; + } + + /** + * Get session save handler + * + * @return Zend_Session_SaveHandler_Interface + */ + public function getSaveHandler() + { + if (!$this->_saveHandler instanceof Zend_Session_SaveHandler_Interface) { + if (is_array($this->_saveHandler)) { + if (!array_key_exists('class', $this->_saveHandler)) { + throw new Zend_Application_Resource_Exception('Session save handler class not provided in options'); + } + $options = array(); + if (array_key_exists('options', $this->_saveHandler)) { + $options = $this->_saveHandler['options']; + } + $this->_saveHandler = $this->_saveHandler['class']; + $this->_saveHandler = new $this->_saveHandler($options); + } elseif (is_string($this->_saveHandler)) { + $this->_saveHandler = new $this->_saveHandler(); + } + + if (!$this->_saveHandler instanceof Zend_Session_SaveHandler_Interface) { + throw new Zend_Application_Resource_Exception('Invalid session save handler'); + } + } + return $this->_saveHandler; + } + + /** + * @return bool + */ + protected function _hasSaveHandler() + { + return ($this->_saveHandler !== null); + } + + /** + * Defined by Zend_Application_Resource_Resource + * + * @return void + */ + public function init() + { + $options = array_change_key_case($this->getOptions(), CASE_LOWER); + if (isset($options['savehandler'])) { + unset($options['savehandler']); + } + + if (count($options) > 0) { + Zend_Session::setOptions($options); + } + + if ($this->_hasSaveHandler()) { + Zend_Session::setSaveHandler($this->getSaveHandler()); + } + } +} diff --git a/library/Zend/Application/Resource/Translate.php b/library/Zend/Application/Resource/Translate.php new file mode 100644 index 00000000..42f8386d --- /dev/null +++ b/library/Zend/Application/Resource/Translate.php @@ -0,0 +1,134 @@ +getTranslate(); + } + + /** + * Retrieve translate object + * + * @return Zend_Translate + * @throws Zend_Application_Resource_Exception if registry key was used + * already but is no instance of Zend_Translate + */ + public function getTranslate() + { + if (null === $this->_translate) { + $options = $this->getOptions(); + + if (!isset($options['content']) && !isset($options['data'])) { + require_once 'Zend/Application/Resource/Exception.php'; + throw new Zend_Application_Resource_Exception('No translation source data provided.'); + } else if (array_key_exists('content', $options) && array_key_exists('data', $options)) { + require_once 'Zend/Application/Resource/Exception.php'; + throw new Zend_Application_Resource_Exception( + 'Conflict on translation source data: choose only one key between content and data.' + ); + } + + if (empty($options['adapter'])) { + $options['adapter'] = Zend_Translate::AN_ARRAY; + } + + if (!empty($options['data'])) { + $options['content'] = $options['data']; + unset($options['data']); + } + + if (isset($options['options'])) { + foreach($options['options'] as $key => $value) { + $options[$key] = $value; + } + } + + if (!empty($options['cache']) && is_string($options['cache'])) { + $bootstrap = $this->getBootstrap(); + if ($bootstrap instanceof Zend_Application_Bootstrap_ResourceBootstrapper && + $bootstrap->hasPluginResource('CacheManager') + ) { + $cacheManager = $bootstrap->bootstrap('CacheManager') + ->getResource('CacheManager'); + if (null !== $cacheManager && + $cacheManager->hasCache($options['cache']) + ) { + $options['cache'] = $cacheManager->getCache($options['cache']); + } + } + } + + $key = (isset($options['registry_key']) && !is_numeric($options['registry_key'])) + ? $options['registry_key'] + : self::DEFAULT_REGISTRY_KEY; + unset($options['registry_key']); + + if(Zend_Registry::isRegistered($key)) { + $translate = Zend_Registry::get($key); + if(!$translate instanceof Zend_Translate) { + require_once 'Zend/Application/Resource/Exception.php'; + throw new Zend_Application_Resource_Exception($key + . ' already registered in registry but is ' + . 'no instance of Zend_Translate'); + } + + $translate->addTranslation($options); + $this->_translate = $translate; + } else { + $this->_translate = new Zend_Translate($options); + Zend_Registry::set($key, $this->_translate); + } + } + + return $this->_translate; + } +} diff --git a/library/Zend/Application/Resource/Useragent.php b/library/Zend/Application/Resource/Useragent.php new file mode 100644 index 00000000..d7411a4d --- /dev/null +++ b/library/Zend/Application/Resource/Useragent.php @@ -0,0 +1,72 @@ +getUserAgent(); + + // Optionally seed the UserAgent view helper + $bootstrap = $this->getBootstrap(); + if ($bootstrap->hasResource('view') || $bootstrap->hasPluginResource('view')) { + $bootstrap->bootstrap('view'); + $view = $bootstrap->getResource('view'); + if (null !== $view) { + $view->userAgent($userAgent); + } + } + + return $userAgent; + } + + /** + * Get UserAgent instance + * + * @return Zend_Http_UserAgent + */ + public function getUserAgent() + { + if (null === $this->_userAgent) { + $options = $this->getOptions(); + $this->_userAgent = new Zend_Http_UserAgent($options); + } + + return $this->_userAgent; + } +} diff --git a/library/Zend/Application/Resource/View.php b/library/Zend/Application/Resource/View.php new file mode 100644 index 00000000..a5dac471 --- /dev/null +++ b/library/Zend/Application/Resource/View.php @@ -0,0 +1,86 @@ +getView(); + + $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer'); + $viewRenderer->setView($view); + return $view; + } + + /** + * Retrieve view object + * + * @return Zend_View + */ + public function getView() + { + if (null === $this->_view) { + $options = $this->getOptions(); + $this->_view = new Zend_View($options); + + if (isset($options['doctype'])) { + $this->_view->doctype()->setDoctype(strtoupper($options['doctype'])); + if (isset($options['charset']) && $this->_view->doctype()->isHtml5()) { + $this->_view->headMeta()->setCharset($options['charset']); + } + } + if (isset($options['contentType'])) { + $this->_view->headMeta()->appendHttpEquiv('Content-Type', $options['contentType']); + } + if (isset($options['assign']) && is_array($options['assign'])) { + $this->_view->assign($options['assign']); + } + } + return $this->_view; + } +} diff --git a/library/Zend/Auth.php b/library/Zend/Auth.php new file mode 100644 index 00000000..a09e53a9 --- /dev/null +++ b/library/Zend/Auth.php @@ -0,0 +1,169 @@ +_storage) { + /** + * @see Zend_Auth_Storage_Session + */ + require_once 'Zend/Auth/Storage/Session.php'; + $this->setStorage(new Zend_Auth_Storage_Session()); + } + + return $this->_storage; + } + + /** + * Sets the persistent storage handler + * + * @param Zend_Auth_Storage_Interface $storage + * @return Zend_Auth Provides a fluent interface + */ + public function setStorage(Zend_Auth_Storage_Interface $storage) + { + $this->_storage = $storage; + return $this; + } + + /** + * Authenticates against the supplied adapter + * + * @param Zend_Auth_Adapter_Interface $adapter + * @return Zend_Auth_Result + */ + public function authenticate(Zend_Auth_Adapter_Interface $adapter) + { + $result = $adapter->authenticate(); + + /** + * ZF-7546 - prevent multiple succesive calls from storing inconsistent results + * Ensure storage has clean state + */ + if ($this->hasIdentity()) { + $this->clearIdentity(); + } + + if ($result->isValid()) { + $this->getStorage()->write($result->getIdentity()); + } + + return $result; + } + + /** + * Returns true if and only if an identity is available from storage + * + * @return boolean + */ + public function hasIdentity() + { + return !$this->getStorage()->isEmpty(); + } + + /** + * Returns the identity from storage or null if no identity is available + * + * @return mixed|null + */ + public function getIdentity() + { + $storage = $this->getStorage(); + + if ($storage->isEmpty()) { + return null; + } + + return $storage->read(); + } + + /** + * Clears the identity from persistent storage + * + * @return void + */ + public function clearIdentity() + { + $this->getStorage()->clear(); + } +} diff --git a/library/Zend/Auth/Adapter/DbTable.php b/library/Zend/Auth/Adapter/DbTable.php new file mode 100644 index 00000000..0983860b --- /dev/null +++ b/library/Zend/Auth/Adapter/DbTable.php @@ -0,0 +1,561 @@ +_setDbAdapter($zendDb); + + if (null !== $tableName) { + $this->setTableName($tableName); + } + + if (null !== $identityColumn) { + $this->setIdentityColumn($identityColumn); + } + + if (null !== $credentialColumn) { + $this->setCredentialColumn($credentialColumn); + } + + if (null !== $credentialTreatment) { + $this->setCredentialTreatment($credentialTreatment); + } + } + + /** + * _setDbAdapter() - set the database adapter to be used for quering + * + * @param Zend_Db_Adapter_Abstract + * @throws Zend_Auth_Adapter_Exception + * @return Zend_Auth_Adapter_DbTable + */ + protected function _setDbAdapter(Zend_Db_Adapter_Abstract $zendDb = null) + { + $this->_zendDb = $zendDb; + + /** + * If no adapter is specified, fetch default database adapter. + */ + if(null === $this->_zendDb) { + require_once 'Zend/Db/Table/Abstract.php'; + $this->_zendDb = Zend_Db_Table_Abstract::getDefaultAdapter(); + if (null === $this->_zendDb) { + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('No database adapter present'); + } + } + + return $this; + } + + /** + * setTableName() - set the table name to be used in the select query + * + * @param string $tableName + * @return Zend_Auth_Adapter_DbTable Provides a fluent interface + */ + public function setTableName($tableName) + { + $this->_tableName = $tableName; + return $this; + } + + /** + * setIdentityColumn() - set the column name to be used as the identity column + * + * @param string $identityColumn + * @return Zend_Auth_Adapter_DbTable Provides a fluent interface + */ + public function setIdentityColumn($identityColumn) + { + $this->_identityColumn = $identityColumn; + return $this; + } + + /** + * setCredentialColumn() - set the column name to be used as the credential column + * + * @param string $credentialColumn + * @return Zend_Auth_Adapter_DbTable Provides a fluent interface + */ + public function setCredentialColumn($credentialColumn) + { + $this->_credentialColumn = $credentialColumn; + return $this; + } + + /** + * setCredentialTreatment() - allows the developer to pass a parameterized string that is + * used to transform or treat the input credential data. + * + * In many cases, passwords and other sensitive data are encrypted, hashed, encoded, + * obscured, or otherwise treated through some function or algorithm. By specifying a + * parameterized treatment string with this method, a developer may apply arbitrary SQL + * upon input credential data. + * + * Examples: + * + * 'PASSWORD(?)' + * 'MD5(?)' + * + * @param string $treatment + * @return Zend_Auth_Adapter_DbTable Provides a fluent interface + */ + public function setCredentialTreatment($treatment) + { + $this->_credentialTreatment = $treatment; + return $this; + } + + /** + * setIdentity() - set the value to be used as the identity + * + * @param string $value + * @return Zend_Auth_Adapter_DbTable Provides a fluent interface + */ + public function setIdentity($value) + { + $this->_identity = $value; + return $this; + } + + /** + * setCredential() - set the credential value to be used, optionally can specify a treatment + * to be used, should be supplied in parameterized form, such as 'MD5(?)' or 'PASSWORD(?)' + * + * @param string $credential + * @return Zend_Auth_Adapter_DbTable Provides a fluent interface + */ + public function setCredential($credential) + { + $this->_credential = $credential; + return $this; + } + + /** + * setAmbiguityIdentity() - sets a flag for usage of identical identities + * with unique credentials. It accepts integers (0, 1) or boolean (true, + * false) parameters. Default is false. + * + * @param int|bool $flag + * @return Zend_Auth_Adapter_DbTable + */ + public function setAmbiguityIdentity($flag) + { + if (is_integer($flag)) { + $this->_ambiguityIdentity = (1 === $flag ? true : false); + } elseif (is_bool($flag)) { + $this->_ambiguityIdentity = $flag; + } + return $this; + } + /** + * getAmbiguityIdentity() - returns TRUE for usage of multiple identical + * identies with different credentials, FALSE if not used. + * + * @return bool + */ + public function getAmbiguityIdentity() + { + return $this->_ambiguityIdentity; + } + + /** + * getDbSelect() - Return the preauthentication Db Select object for userland select query modification + * + * @return Zend_Db_Select + */ + public function getDbSelect() + { + if ($this->_dbSelect == null) { + $this->_dbSelect = $this->_zendDb->select(); + } + + return $this->_dbSelect; + } + + /** + * getResultRowObject() - Returns the result row as a stdClass object + * + * @param string|array $returnColumns + * @param string|array $omitColumns + * @return stdClass|boolean + */ + public function getResultRowObject($returnColumns = null, $omitColumns = null) + { + if (!$this->_resultRow) { + return false; + } + + $returnObject = new stdClass(); + + if (null !== $returnColumns) { + + $availableColumns = array_keys($this->_resultRow); + foreach ( (array) $returnColumns as $returnColumn) { + if (in_array($returnColumn, $availableColumns)) { + $returnObject->{$returnColumn} = $this->_resultRow[$returnColumn]; + } + } + return $returnObject; + + } elseif (null !== $omitColumns) { + + $omitColumns = (array) $omitColumns; + foreach ($this->_resultRow as $resultColumn => $resultValue) { + if (!in_array($resultColumn, $omitColumns)) { + $returnObject->{$resultColumn} = $resultValue; + } + } + return $returnObject; + + } else { + + foreach ($this->_resultRow as $resultColumn => $resultValue) { + $returnObject->{$resultColumn} = $resultValue; + } + return $returnObject; + + } + } + + /** + * authenticate() - defined by Zend_Auth_Adapter_Interface. This method is called to + * attempt an authentication. Previous to this call, this adapter would have already + * been configured with all necessary information to successfully connect to a database + * table and attempt to find a record matching the provided identity. + * + * @throws Zend_Auth_Adapter_Exception if answering the authentication query is impossible + * @return Zend_Auth_Result + */ + public function authenticate() + { + $this->_authenticateSetup(); + $dbSelect = $this->_authenticateCreateSelect(); + $resultIdentities = $this->_authenticateQuerySelect($dbSelect); + + if ( ($authResult = $this->_authenticateValidateResultSet($resultIdentities)) instanceof Zend_Auth_Result) { + return $authResult; + } + + if (true === $this->getAmbiguityIdentity()) { + $validIdentities = array (); + $zendAuthCredentialMatchColumn = $this->_zendDb->foldCase('zend_auth_credential_match'); + foreach ($resultIdentities as $identity) { + if (1 === (int) $identity[$zendAuthCredentialMatchColumn]) { + $validIdentities[] = $identity; + } + } + $resultIdentities = $validIdentities; + } + + $authResult = $this->_authenticateValidateResult(array_shift($resultIdentities)); + return $authResult; + } + + /** + * _authenticateSetup() - This method abstracts the steps involved with + * making sure that this adapter was indeed setup properly with all + * required pieces of information. + * + * @throws Zend_Auth_Adapter_Exception - in the event that setup was not done properly + * @return true + */ + protected function _authenticateSetup() + { + $exception = null; + + if ($this->_tableName == '') { + $exception = 'A table must be supplied for the Zend_Auth_Adapter_DbTable authentication adapter.'; + } elseif ($this->_identityColumn == '') { + $exception = 'An identity column must be supplied for the Zend_Auth_Adapter_DbTable authentication adapter.'; + } elseif ($this->_credentialColumn == '') { + $exception = 'A credential column must be supplied for the Zend_Auth_Adapter_DbTable authentication adapter.'; + } elseif ($this->_identity == '') { + $exception = 'A value for the identity was not provided prior to authentication with Zend_Auth_Adapter_DbTable.'; + } elseif ($this->_credential === null) { + $exception = 'A credential value was not provided prior to authentication with Zend_Auth_Adapter_DbTable.'; + } + + if (null !== $exception) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception($exception); + } + + $this->_authenticateResultInfo = array( + 'code' => Zend_Auth_Result::FAILURE, + 'identity' => $this->_identity, + 'messages' => array() + ); + + return true; + } + + /** + * _authenticateCreateSelect() - This method creates a Zend_Db_Select object that + * is completely configured to be queried against the database. + * + * @return Zend_Db_Select + */ + protected function _authenticateCreateSelect() + { + // build credential expression + if (empty($this->_credentialTreatment) || (strpos($this->_credentialTreatment, '?') === false)) { + $this->_credentialTreatment = '?'; + } + + $credentialExpression = new Zend_Db_Expr( + '(CASE WHEN ' . + $this->_zendDb->quoteInto( + $this->_zendDb->quoteIdentifier($this->_credentialColumn, true) + . ' = ' . $this->_credentialTreatment, $this->_credential + ) + . ' THEN 1 ELSE 0 END) AS ' + . $this->_zendDb->quoteIdentifier( + $this->_zendDb->foldCase('zend_auth_credential_match') + ) + ); + + // get select + $dbSelect = clone $this->getDbSelect(); + $dbSelect->from($this->_tableName, array('*', $credentialExpression)) + ->where($this->_zendDb->quoteIdentifier($this->_identityColumn, true) . ' = ?', $this->_identity); + + return $dbSelect; + } + + /** + * _authenticateQuerySelect() - This method accepts a Zend_Db_Select object and + * performs a query against the database with that object. + * + * @param Zend_Db_Select $dbSelect + * @throws Zend_Auth_Adapter_Exception - when an invalid select + * object is encountered + * @return array + */ + protected function _authenticateQuerySelect(Zend_Db_Select $dbSelect) + { + try { + if ($this->_zendDb->getFetchMode() != Zend_DB::FETCH_ASSOC) { + $origDbFetchMode = $this->_zendDb->getFetchMode(); + $this->_zendDb->setFetchMode(Zend_DB::FETCH_ASSOC); + } + $resultIdentities = $this->_zendDb->fetchAll($dbSelect); + if (isset($origDbFetchMode)) { + $this->_zendDb->setFetchMode($origDbFetchMode); + unset($origDbFetchMode); + } + } catch (Exception $e) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('The supplied parameters to Zend_Auth_Adapter_DbTable failed to ' + . 'produce a valid sql statement, please check table and column names ' + . 'for validity.', 0, $e); + } + return $resultIdentities; + } + + /** + * _authenticateValidateResultSet() - This method attempts to make + * certain that only one record was returned in the resultset + * + * @param array $resultIdentities + * @return true|Zend_Auth_Result + */ + protected function _authenticateValidateResultSet(array $resultIdentities) + { + + if (count($resultIdentities) < 1) { + $this->_authenticateResultInfo['code'] = Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND; + $this->_authenticateResultInfo['messages'][] = 'A record with the supplied identity could not be found.'; + return $this->_authenticateCreateAuthResult(); + } elseif (count($resultIdentities) > 1 && false === $this->getAmbiguityIdentity()) { + $this->_authenticateResultInfo['code'] = Zend_Auth_Result::FAILURE_IDENTITY_AMBIGUOUS; + $this->_authenticateResultInfo['messages'][] = 'More than one record matches the supplied identity.'; + return $this->_authenticateCreateAuthResult(); + } + + return true; + } + + /** + * _authenticateValidateResult() - This method attempts to validate that + * the record in the resultset is indeed a record that matched the + * identity provided to this adapter. + * + * @param array $resultIdentity + * @return Zend_Auth_Result + */ + protected function _authenticateValidateResult($resultIdentity) + { + $zendAuthCredentialMatchColumn = $this->_zendDb->foldCase('zend_auth_credential_match'); + + if ($resultIdentity[$zendAuthCredentialMatchColumn] != '1') { + $this->_authenticateResultInfo['code'] = Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID; + $this->_authenticateResultInfo['messages'][] = 'Supplied credential is invalid.'; + return $this->_authenticateCreateAuthResult(); + } + + unset($resultIdentity[$zendAuthCredentialMatchColumn]); + $this->_resultRow = $resultIdentity; + + $this->_authenticateResultInfo['code'] = Zend_Auth_Result::SUCCESS; + $this->_authenticateResultInfo['messages'][] = 'Authentication successful.'; + return $this->_authenticateCreateAuthResult(); + } + + /** + * _authenticateCreateAuthResult() - Creates a Zend_Auth_Result object from + * the information that has been collected during the authenticate() attempt. + * + * @return Zend_Auth_Result + */ + protected function _authenticateCreateAuthResult() + { + return new Zend_Auth_Result( + $this->_authenticateResultInfo['code'], + $this->_authenticateResultInfo['identity'], + $this->_authenticateResultInfo['messages'] + ); + } + +} diff --git a/library/Zend/Auth/Adapter/Digest.php b/library/Zend/Auth/Adapter/Digest.php new file mode 100644 index 00000000..065ea9e1 --- /dev/null +++ b/library/Zend/Auth/Adapter/Digest.php @@ -0,0 +1,252 @@ +$methodName($$option); + } + } + } + + /** + * Returns the filename option value or null if it has not yet been set + * + * @return string|null + */ + public function getFilename() + { + return $this->_filename; + } + + /** + * Sets the filename option value + * + * @param mixed $filename + * @return Zend_Auth_Adapter_Digest Provides a fluent interface + */ + public function setFilename($filename) + { + $this->_filename = (string) $filename; + return $this; + } + + /** + * Returns the realm option value or null if it has not yet been set + * + * @return string|null + */ + public function getRealm() + { + return $this->_realm; + } + + /** + * Sets the realm option value + * + * @param mixed $realm + * @return Zend_Auth_Adapter_Digest Provides a fluent interface + */ + public function setRealm($realm) + { + $this->_realm = (string) $realm; + return $this; + } + + /** + * Returns the username option value or null if it has not yet been set + * + * @return string|null + */ + public function getUsername() + { + return $this->_username; + } + + /** + * Sets the username option value + * + * @param mixed $username + * @return Zend_Auth_Adapter_Digest Provides a fluent interface + */ + public function setUsername($username) + { + $this->_username = (string) $username; + return $this; + } + + /** + * Returns the password option value or null if it has not yet been set + * + * @return string|null + */ + public function getPassword() + { + return $this->_password; + } + + /** + * Sets the password option value + * + * @param mixed $password + * @return Zend_Auth_Adapter_Digest Provides a fluent interface + */ + public function setPassword($password) + { + $this->_password = (string) $password; + return $this; + } + + /** + * Defined by Zend_Auth_Adapter_Interface + * + * @throws Zend_Auth_Adapter_Exception + * @return Zend_Auth_Result + */ + public function authenticate() + { + $optionsRequired = array('filename', 'realm', 'username', 'password'); + foreach ($optionsRequired as $optionRequired) { + if (null === $this->{"_$optionRequired"}) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception("Option '$optionRequired' must be set before authentication"); + } + } + + if (false === ($fileHandle = @fopen($this->_filename, 'r'))) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception("Cannot open '$this->_filename' for reading"); + } + + $id = "$this->_username:$this->_realm"; + $idLength = strlen($id); + + $result = array( + 'code' => Zend_Auth_Result::FAILURE, + 'identity' => array( + 'realm' => $this->_realm, + 'username' => $this->_username, + ), + 'messages' => array() + ); + + while ($line = trim(fgets($fileHandle))) { + if (substr($line, 0, $idLength) === $id) { + if ($this->_secureStringCompare(substr($line, -32), md5("$this->_username:$this->_realm:$this->_password"))) { + $result['code'] = Zend_Auth_Result::SUCCESS; + } else { + $result['code'] = Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID; + $result['messages'][] = 'Password incorrect'; + } + return new Zend_Auth_Result($result['code'], $result['identity'], $result['messages']); + } + } + + $result['code'] = Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND; + $result['messages'][] = "Username '$this->_username' and realm '$this->_realm' combination not found"; + return new Zend_Auth_Result($result['code'], $result['identity'], $result['messages']); + } + + /** + * Securely compare two strings for equality while avoided C level memcmp() + * optimisations capable of leaking timing information useful to an attacker + * attempting to iteratively guess the unknown string (e.g. password) being + * compared against. + * + * @param string $a + * @param string $b + * @return bool + */ + protected function _secureStringCompare($a, $b) + { + if (strlen($a) !== strlen($b)) { + return false; + } + $result = 0; + for ($i = 0; $i < strlen($a); $i++) { + $result |= ord($a[$i]) ^ ord($b[$i]); + } + return $result == 0; + } +} diff --git a/library/Zend/Auth/Adapter/Exception.php b/library/Zend/Auth/Adapter/Exception.php new file mode 100644 index 00000000..92eed9e5 --- /dev/null +++ b/library/Zend/Auth/Adapter/Exception.php @@ -0,0 +1,38 @@ + 'basic'|'digest'|'basic digest' + * 'realm' => + * 'digest_domains' => Space-delimited list of URIs + * 'nonce_timeout' => + * 'use_opaque' => Whether to send the opaque value in the header + * 'alogrithm' => See $_supportedAlgos. Default: MD5 + * 'proxy_auth' => Whether to do authentication as a Proxy + * @throws Zend_Auth_Adapter_Exception + * @return void + */ + public function __construct(array $config) + { + if (!extension_loaded('hash')) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception(__CLASS__ . ' requires the \'hash\' extension'); + } + + $this->_request = null; + $this->_response = null; + $this->_ieNoOpaque = false; + + + if (empty($config['accept_schemes'])) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('Config key \'accept_schemes\' is required'); + } + + $schemes = explode(' ', $config['accept_schemes']); + $this->_acceptSchemes = array_intersect($schemes, $this->_supportedSchemes); + if (empty($this->_acceptSchemes)) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('No supported schemes given in \'accept_schemes\'. Valid values: ' + . implode(', ', $this->_supportedSchemes)); + } + + // Double-quotes are used to delimit the realm string in the HTTP header, + // and colons are field delimiters in the password file. + if (empty($config['realm']) || + !ctype_print($config['realm']) || + strpos($config['realm'], ':') !== false || + strpos($config['realm'], '"') !== false) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('Config key \'realm\' is required, and must contain only printable ' + . 'characters, excluding quotation marks and colons'); + } else { + $this->_realm = $config['realm']; + } + + if (in_array('digest', $this->_acceptSchemes)) { + if (empty($config['digest_domains']) || + !ctype_print($config['digest_domains']) || + strpos($config['digest_domains'], '"') !== false) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('Config key \'digest_domains\' is required, and must contain ' + . 'only printable characters, excluding quotation marks'); + } else { + $this->_domains = $config['digest_domains']; + } + + if (empty($config['nonce_timeout']) || + !is_numeric($config['nonce_timeout'])) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('Config key \'nonce_timeout\' is required, and must be an ' + . 'integer'); + } else { + $this->_nonceTimeout = (int) $config['nonce_timeout']; + } + + // We use the opaque value unless explicitly told not to + if (isset($config['use_opaque']) && false == (bool) $config['use_opaque']) { + $this->_useOpaque = false; + } else { + $this->_useOpaque = true; + } + + if (isset($config['algorithm']) && in_array($config['algorithm'], $this->_supportedAlgos)) { + $this->_algo = $config['algorithm']; + } else { + $this->_algo = 'MD5'; + } + } + + // Don't be a proxy unless explicitly told to do so + if (isset($config['proxy_auth']) && true == (bool) $config['proxy_auth']) { + $this->_imaProxy = true; // I'm a Proxy + } else { + $this->_imaProxy = false; + } + } + + /** + * Setter for the _basicResolver property + * + * @param Zend_Auth_Adapter_Http_Resolver_Interface $resolver + * @return Zend_Auth_Adapter_Http Provides a fluent interface + */ + public function setBasicResolver(Zend_Auth_Adapter_Http_Resolver_Interface $resolver) + { + $this->_basicResolver = $resolver; + + return $this; + } + + /** + * Getter for the _basicResolver property + * + * @return Zend_Auth_Adapter_Http_Resolver_Interface + */ + public function getBasicResolver() + { + return $this->_basicResolver; + } + + /** + * Setter for the _digestResolver property + * + * @param Zend_Auth_Adapter_Http_Resolver_Interface $resolver + * @return Zend_Auth_Adapter_Http Provides a fluent interface + */ + public function setDigestResolver(Zend_Auth_Adapter_Http_Resolver_Interface $resolver) + { + $this->_digestResolver = $resolver; + + return $this; + } + + /** + * Getter for the _digestResolver property + * + * @return Zend_Auth_Adapter_Http_Resolver_Interface + */ + public function getDigestResolver() + { + return $this->_digestResolver; + } + + /** + * Setter for the Request object + * + * @param Zend_Controller_Request_Http $request + * @return Zend_Auth_Adapter_Http Provides a fluent interface + */ + public function setRequest(Zend_Controller_Request_Http $request) + { + $this->_request = $request; + + return $this; + } + + /** + * Getter for the Request object + * + * @return Zend_Controller_Request_Http + */ + public function getRequest() + { + return $this->_request; + } + + /** + * Setter for the Response object + * + * @param Zend_Controller_Response_Http $response + * @return Zend_Auth_Adapter_Http Provides a fluent interface + */ + public function setResponse(Zend_Controller_Response_Http $response) + { + $this->_response = $response; + + return $this; + } + + /** + * Getter for the Response object + * + * @return Zend_Controller_Response_Http + */ + public function getResponse() + { + return $this->_response; + } + + /** + * Authenticate + * + * @throws Zend_Auth_Adapter_Exception + * @return Zend_Auth_Result + */ + public function authenticate() + { + if (empty($this->_request) || + empty($this->_response)) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('Request and Response objects must be set before calling ' + . 'authenticate()'); + } + + if ($this->_imaProxy) { + $getHeader = 'Proxy-Authorization'; + } else { + $getHeader = 'Authorization'; + } + + $authHeader = $this->_request->getHeader($getHeader); + if (!$authHeader) { + return $this->_challengeClient(); + } + + list($clientScheme) = explode(' ', $authHeader); + $clientScheme = strtolower($clientScheme); + + // The server can issue multiple challenges, but the client should + // answer with only the selected auth scheme. + if (!in_array($clientScheme, $this->_supportedSchemes)) { + $this->_response->setHttpResponseCode(400); + return new Zend_Auth_Result( + Zend_Auth_Result::FAILURE_UNCATEGORIZED, + array(), + array('Client requested an incorrect or unsupported authentication scheme') + ); + } + + // client sent a scheme that is not the one required + if (!in_array($clientScheme, $this->_acceptSchemes)) { + // challenge again the client + return $this->_challengeClient(); + } + + switch ($clientScheme) { + case 'basic': + $result = $this->_basicAuth($authHeader); + break; + case 'digest': + $result = $this->_digestAuth($authHeader); + break; + default: + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('Unsupported authentication scheme'); + } + + return $result; + } + + /** + * Challenge Client + * + * Sets a 401 or 407 Unauthorized response code, and creates the + * appropriate Authenticate header(s) to prompt for credentials. + * + * @return Zend_Auth_Result Always returns a non-identity Auth result + */ + protected function _challengeClient() + { + if ($this->_imaProxy) { + $statusCode = 407; + $headerName = 'Proxy-Authenticate'; + } else { + $statusCode = 401; + $headerName = 'WWW-Authenticate'; + } + + $this->_response->setHttpResponseCode($statusCode); + + // Send a challenge in each acceptable authentication scheme + if (in_array('basic', $this->_acceptSchemes)) { + $this->_response->setHeader($headerName, $this->_basicHeader()); + } + if (in_array('digest', $this->_acceptSchemes)) { + $this->_response->setHeader($headerName, $this->_digestHeader()); + } + return new Zend_Auth_Result( + Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID, + array(), + array('Invalid or absent credentials; challenging client') + ); + } + + /** + * Basic Header + * + * Generates a Proxy- or WWW-Authenticate header value in the Basic + * authentication scheme. + * + * @return string Authenticate header value + */ + protected function _basicHeader() + { + return 'Basic realm="' . $this->_realm . '"'; + } + + /** + * Digest Header + * + * Generates a Proxy- or WWW-Authenticate header value in the Digest + * authentication scheme. + * + * @return string Authenticate header value + */ + protected function _digestHeader() + { + $wwwauth = 'Digest realm="' . $this->_realm . '", ' + . 'domain="' . $this->_domains . '", ' + . 'nonce="' . $this->_calcNonce() . '", ' + . ($this->_useOpaque ? 'opaque="' . $this->_calcOpaque() . '", ' : '') + . 'algorithm="' . $this->_algo . '", ' + . 'qop="' . implode(',', $this->_supportedQops) . '"'; + + return $wwwauth; + } + + /** + * Basic Authentication + * + * @param string $header Client's Authorization header + * @throws Zend_Auth_Adapter_Exception + * @return Zend_Auth_Result + */ + protected function _basicAuth($header) + { + if (empty($header)) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('The value of the client Authorization header is required'); + } + if (empty($this->_basicResolver)) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('A basicResolver object must be set before doing Basic ' + . 'authentication'); + } + + // Decode the Authorization header + $auth = substr($header, strlen('Basic ')); + $auth = base64_decode($auth); + if (!$auth) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('Unable to base64_decode Authorization header value'); + } + + // See ZF-1253. Validate the credentials the same way the digest + // implementation does. If invalid credentials are detected, + // re-challenge the client. + if (!ctype_print($auth)) { + return $this->_challengeClient(); + } + // Fix for ZF-1515: Now re-challenges on empty username or password + $creds = array_filter(explode(':', $auth)); + if (count($creds) != 2) { + return $this->_challengeClient(); + } + + $password = $this->_basicResolver->resolve($creds[0], $this->_realm); + if ($password && $this->_secureStringCompare($password, $creds[1])) { + $identity = array('username'=>$creds[0], 'realm'=>$this->_realm); + return new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $identity); + } else { + return $this->_challengeClient(); + } + } + + /** + * Digest Authentication + * + * @param string $header Client's Authorization header + * @throws Zend_Auth_Adapter_Exception + * @return Zend_Auth_Result Valid auth result only on successful auth + */ + protected function _digestAuth($header) + { + if (empty($header)) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('The value of the client Authorization header is required'); + } + if (empty($this->_digestResolver)) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('A digestResolver object must be set before doing Digest authentication'); + } + + $data = $this->_parseDigestAuth($header); + if ($data === false) { + $this->_response->setHttpResponseCode(400); + return new Zend_Auth_Result( + Zend_Auth_Result::FAILURE_UNCATEGORIZED, + array(), + array('Invalid Authorization header format') + ); + } + + // See ZF-1052. This code was a bit too unforgiving of invalid + // usernames. Now, if the username is bad, we re-challenge the client. + if ('::invalid::' == $data['username']) { + return $this->_challengeClient(); + } + + // Verify that the client sent back the same nonce + if ($this->_calcNonce() != $data['nonce']) { + return $this->_challengeClient(); + } + // The opaque value is also required to match, but of course IE doesn't + // play ball. + if (!$this->_ieNoOpaque && $this->_calcOpaque() != $data['opaque']) { + return $this->_challengeClient(); + } + + // Look up the user's password hash. If not found, deny access. + // This makes no assumptions about how the password hash was + // constructed beyond that it must have been built in such a way as + // to be recreatable with the current settings of this object. + $ha1 = $this->_digestResolver->resolve($data['username'], $data['realm']); + if ($ha1 === false) { + return $this->_challengeClient(); + } + + // If MD5-sess is used, a1 value is made of the user's password + // hash with the server and client nonce appended, separated by + // colons. + if ($this->_algo == 'MD5-sess') { + $ha1 = hash('md5', $ha1 . ':' . $data['nonce'] . ':' . $data['cnonce']); + } + + // Calculate h(a2). The value of this hash depends on the qop + // option selected by the client and the supported hash functions + switch ($data['qop']) { + case 'auth': + $a2 = $this->_request->getMethod() . ':' . $data['uri']; + break; + case 'auth-int': + // Should be REQUEST_METHOD . ':' . uri . ':' . hash(entity-body), + // but this isn't supported yet, so fall through to default case + default: + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('Client requested an unsupported qop option'); + } + // Using hash() should make parameterizing the hash algorithm + // easier + $ha2 = hash('md5', $a2); + + + // Calculate the server's version of the request-digest. This must + // match $data['response']. See RFC 2617, section 3.2.2.1 + $message = $data['nonce'] . ':' . $data['nc'] . ':' . $data['cnonce'] . ':' . $data['qop'] . ':' . $ha2; + $digest = hash('md5', $ha1 . ':' . $message); + + // If our digest matches the client's let them in, otherwise return + // a 401 code and exit to prevent access to the protected resource. + if ($this->_secureStringCompare($digest, $data['response'])) { + $identity = array('username'=>$data['username'], 'realm'=>$data['realm']); + return new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $identity); + } else { + return $this->_challengeClient(); + } + } + + /** + * Calculate Nonce + * + * @return string The nonce value + */ + protected function _calcNonce() + { + // Once subtle consequence of this timeout calculation is that it + // actually divides all of time into _nonceTimeout-sized sections, such + // that the value of timeout is the point in time of the next + // approaching "boundary" of a section. This allows the server to + // consistently generate the same timeout (and hence the same nonce + // value) across requests, but only as long as one of those + // "boundaries" is not crossed between requests. If that happens, the + // nonce will change on its own, and effectively log the user out. This + // would be surprising if the user just logged in. + $timeout = ceil(time() / $this->_nonceTimeout) * $this->_nonceTimeout; + + $nonce = hash('md5', $timeout . ':' . $this->_request->getServer('HTTP_USER_AGENT') . ':' . __CLASS__); + return $nonce; + } + + /** + * Calculate Opaque + * + * The opaque string can be anything; the client must return it exactly as + * it was sent. It may be useful to store data in this string in some + * applications. Ideally, a new value for this would be generated each time + * a WWW-Authenticate header is sent (in order to reduce predictability), + * but we would have to be able to create the same exact value across at + * least two separate requests from the same client. + * + * @return string The opaque value + */ + protected function _calcOpaque() + { + return hash('md5', 'Opaque Data:' . __CLASS__); + } + + /** + * Parse Digest Authorization header + * + * @param string $header Client's Authorization: HTTP header + * @return array|false Data elements from header, or false if any part of + * the header is invalid + */ + protected function _parseDigestAuth($header) + { + $temp = null; + $data = array(); + + // See ZF-1052. Detect invalid usernames instead of just returning a + // 400 code. + $ret = preg_match('/username="([^"]+)"/', $header, $temp); + if (!$ret || empty($temp[1]) + || !ctype_print($temp[1]) + || strpos($temp[1], ':') !== false) { + $data['username'] = '::invalid::'; + } else { + $data['username'] = $temp[1]; + } + $temp = null; + + $ret = preg_match('/realm="([^"]+)"/', $header, $temp); + if (!$ret || empty($temp[1])) { + return false; + } + if (!ctype_print($temp[1]) || strpos($temp[1], ':') !== false) { + return false; + } else { + $data['realm'] = $temp[1]; + } + $temp = null; + + $ret = preg_match('/nonce="([^"]+)"/', $header, $temp); + if (!$ret || empty($temp[1])) { + return false; + } + if (!ctype_xdigit($temp[1])) { + return false; + } else { + $data['nonce'] = $temp[1]; + } + $temp = null; + + $ret = preg_match('/uri="([^"]+)"/', $header, $temp); + if (!$ret || empty($temp[1])) { + return false; + } + // Section 3.2.2.5 in RFC 2617 says the authenticating server must + // verify that the URI field in the Authorization header is for the + // same resource requested in the Request Line. + $rUri = @parse_url($this->_request->getRequestUri()); + $cUri = @parse_url($temp[1]); + if (false === $rUri || false === $cUri) { + return false; + } else { + // Make sure the path portion of both URIs is the same + if ($rUri['path'] != $cUri['path']) { + return false; + } + // Section 3.2.2.5 seems to suggest that the value of the URI + // Authorization field should be made into an absolute URI if the + // Request URI is absolute, but it's vague, and that's a bunch of + // code I don't want to write right now. + $data['uri'] = $temp[1]; + } + $temp = null; + + $ret = preg_match('/response="([^"]+)"/', $header, $temp); + if (!$ret || empty($temp[1])) { + return false; + } + if (32 != strlen($temp[1]) || !ctype_xdigit($temp[1])) { + return false; + } else { + $data['response'] = $temp[1]; + } + $temp = null; + + // The spec says this should default to MD5 if omitted. OK, so how does + // that square with the algo we send out in the WWW-Authenticate header, + // if it can easily be overridden by the client? + $ret = preg_match('/algorithm="?(' . $this->_algo . ')"?/', $header, $temp); + if ($ret && !empty($temp[1]) + && in_array($temp[1], $this->_supportedAlgos)) { + $data['algorithm'] = $temp[1]; + } else { + $data['algorithm'] = 'MD5'; // = $this->_algo; ? + } + $temp = null; + + // Not optional in this implementation + $ret = preg_match('/cnonce="([^"]+)"/', $header, $temp); + if (!$ret || empty($temp[1])) { + return false; + } + if (!ctype_print($temp[1])) { + return false; + } else { + $data['cnonce'] = $temp[1]; + } + $temp = null; + + // If the server sent an opaque value, the client must send it back + if ($this->_useOpaque) { + $ret = preg_match('/opaque="([^"]+)"/', $header, $temp); + if (!$ret || empty($temp[1])) { + + // Big surprise: IE isn't RFC 2617-compliant. + if (false !== strpos($this->_request->getHeader('User-Agent'), 'MSIE')) { + $temp[1] = ''; + $this->_ieNoOpaque = true; + } else { + return false; + } + } + // This implementation only sends MD5 hex strings in the opaque value + if (!$this->_ieNoOpaque && + (32 != strlen($temp[1]) || !ctype_xdigit($temp[1]))) { + return false; + } else { + $data['opaque'] = $temp[1]; + } + $temp = null; + } + + // Not optional in this implementation, but must be one of the supported + // qop types + $ret = preg_match('/qop="?(' . implode('|', $this->_supportedQops) . ')"?/', $header, $temp); + if (!$ret || empty($temp[1])) { + return false; + } + if (!in_array($temp[1], $this->_supportedQops)) { + return false; + } else { + $data['qop'] = $temp[1]; + } + $temp = null; + + // Not optional in this implementation. The spec says this value + // shouldn't be a quoted string, but apparently some implementations + // quote it anyway. See ZF-1544. + $ret = preg_match('/nc="?([0-9A-Fa-f]{8})"?/', $header, $temp); + if (!$ret || empty($temp[1])) { + return false; + } + if (8 != strlen($temp[1]) || !ctype_xdigit($temp[1])) { + return false; + } else { + $data['nc'] = $temp[1]; + } + $temp = null; + + return $data; + } + + /** + * Securely compare two strings for equality while avoided C level memcmp() + * optimisations capable of leaking timing information useful to an attacker + * attempting to iteratively guess the unknown string (e.g. password) being + * compared against. + * + * @param string $a + * @param string $b + * @return bool + */ + protected function _secureStringCompare($a, $b) + { + if (strlen($a) !== strlen($b)) { + return false; + } + $result = 0; + for ($i = 0; $i < strlen($a); $i++) { + $result |= ord($a[$i]) ^ ord($b[$i]); + } + return $result == 0; + } +} diff --git a/library/Zend/Auth/Adapter/Http/Resolver/Exception.php b/library/Zend/Auth/Adapter/Http/Resolver/Exception.php new file mode 100644 index 00000000..46f3fc93 --- /dev/null +++ b/library/Zend/Auth/Adapter/Http/Resolver/Exception.php @@ -0,0 +1,40 @@ +setFile($path); + } + } + + /** + * Set the path to the credentials file + * + * @param string $path + * @throws Zend_Auth_Adapter_Http_Resolver_Exception + * @return Zend_Auth_Adapter_Http_Resolver_File Provides a fluent interface + */ + public function setFile($path) + { + if (empty($path) || !is_readable($path)) { + /** + * @see Zend_Auth_Adapter_Http_Resolver_Exception + */ + require_once 'Zend/Auth/Adapter/Http/Resolver/Exception.php'; + throw new Zend_Auth_Adapter_Http_Resolver_Exception('Path not readable: ' . $path); + } + $this->_file = $path; + + return $this; + } + + /** + * Returns the path to the credentials file + * + * @return string + */ + public function getFile() + { + return $this->_file; + } + + /** + * Resolve credentials + * + * Only the first matching username/realm combination in the file is + * returned. If the file contains credentials for Digest authentication, + * the returned string is the password hash, or h(a1) from RFC 2617. The + * returned string is the plain-text password for Basic authentication. + * + * The expected format of the file is: + * username:realm:sharedSecret + * + * That is, each line consists of the user's username, the applicable + * authentication realm, and the password or hash, each delimited by + * colons. + * + * @param string $username Username + * @param string $realm Authentication Realm + * @throws Zend_Auth_Adapter_Http_Resolver_Exception + * @return string|false User's shared secret, if the user is found in the + * realm, false otherwise. + */ + public function resolve($username, $realm) + { + if (empty($username)) { + /** + * @see Zend_Auth_Adapter_Http_Resolver_Exception + */ + require_once 'Zend/Auth/Adapter/Http/Resolver/Exception.php'; + throw new Zend_Auth_Adapter_Http_Resolver_Exception('Username is required'); + } else if (!ctype_print($username) || strpos($username, ':') !== false) { + /** + * @see Zend_Auth_Adapter_Http_Resolver_Exception + */ + require_once 'Zend/Auth/Adapter/Http/Resolver/Exception.php'; + throw new Zend_Auth_Adapter_Http_Resolver_Exception('Username must consist only of printable characters, ' + . 'excluding the colon'); + } + if (empty($realm)) { + /** + * @see Zend_Auth_Adapter_Http_Resolver_Exception + */ + require_once 'Zend/Auth/Adapter/Http/Resolver/Exception.php'; + throw new Zend_Auth_Adapter_Http_Resolver_Exception('Realm is required'); + } else if (!ctype_print($realm) || strpos($realm, ':') !== false) { + /** + * @see Zend_Auth_Adapter_Http_Resolver_Exception + */ + require_once 'Zend/Auth/Adapter/Http/Resolver/Exception.php'; + throw new Zend_Auth_Adapter_Http_Resolver_Exception('Realm must consist only of printable characters, ' + . 'excluding the colon.'); + } + + // Open file, read through looking for matching credentials + $fp = @fopen($this->_file, 'r'); + if (!$fp) { + /** + * @see Zend_Auth_Adapter_Http_Resolver_Exception + */ + require_once 'Zend/Auth/Adapter/Http/Resolver/Exception.php'; + throw new Zend_Auth_Adapter_Http_Resolver_Exception('Unable to open password file: ' . $this->_file); + } + + // No real validation is done on the contents of the password file. The + // assumption is that we trust the administrators to keep it secure. + while (($line = fgetcsv($fp, 512, ':')) !== false) { + if ($line[0] == $username && $line[1] == $realm) { + $password = $line[2]; + fclose($fp); + return $password; + } + } + + fclose($fp); + return false; + } +} diff --git a/library/Zend/Auth/Adapter/Http/Resolver/Interface.php b/library/Zend/Auth/Adapter/Http/Resolver/Interface.php new file mode 100644 index 00000000..d4420f96 --- /dev/null +++ b/library/Zend/Auth/Adapter/Http/Resolver/Interface.php @@ -0,0 +1,47 @@ +_xmlToken = $strXmlDocument; + $this->_infoCard = new Zend_InfoCard(); + } + + /** + * Sets the InfoCard component Adapter to use + * + * @param Zend_InfoCard_Adapter_Interface $a + * @return Zend_Auth_Adapter_InfoCard Provides a fluent interface + */ + public function setAdapter(Zend_InfoCard_Adapter_Interface $a) + { + $this->_infoCard->setAdapter($a); + return $this; + } + + /** + * Retrieves the InfoCard component adapter being used + * + * @return Zend_InfoCard_Adapter_Interface + */ + public function getAdapter() + { + return $this->_infoCard->getAdapter(); + } + + /** + * Retrieves the InfoCard public key cipher object being used + * + * @return Zend_InfoCard_Cipher_PKI_Interface + */ + public function getPKCipherObject() + { + return $this->_infoCard->getPKCipherObject(); + } + + /** + * Sets the InfoCard public key cipher object to use + * + * @param Zend_InfoCard_Cipher_PKI_Interface $cipherObj + * @return Zend_Auth_Adapter_InfoCard Provides a fluent interface + */ + public function setPKICipherObject(Zend_InfoCard_Cipher_PKI_Interface $cipherObj) + { + $this->_infoCard->setPKICipherObject($cipherObj); + return $this; + } + + /** + * Retrieves the Symmetric cipher object being used + * + * @return Zend_InfoCard_Cipher_Symmetric_Interface + */ + public function getSymCipherObject() + { + return $this->_infoCard->getSymCipherObject(); + } + + /** + * Sets the InfoCard symmetric cipher object to use + * + * @param Zend_InfoCard_Cipher_Symmetric_Interface $cipherObj + * @return Zend_Auth_Adapter_InfoCard Provides a fluent interface + */ + public function setSymCipherObject(Zend_InfoCard_Cipher_Symmetric_Interface $cipherObj) + { + $this->_infoCard->setSymCipherObject($cipherObj); + return $this; + } + + /** + * Remove a Certificate Pair by Key ID from the search list + * + * @param string $key_id The Certificate Key ID returned from adding the certificate pair + * @throws Zend_InfoCard_Exception + * @return Zend_Auth_Adapter_InfoCard Provides a fluent interface + */ + public function removeCertificatePair($key_id) + { + $this->_infoCard->removeCertificatePair($key_id); + return $this; + } + + /** + * Add a Certificate Pair to the list of certificates searched by the component + * + * @param string $private_key_file The path to the private key file for the pair + * @param string $public_key_file The path to the certificate / public key for the pair + * @param string $type (optional) The URI for the type of key pair this is (default RSA with OAEP padding) + * @param string $password (optional) The password for the private key file if necessary + * @throws Zend_InfoCard_Exception + * @return string A key ID representing this key pair in the component + */ + public function addCertificatePair($private_key_file, $public_key_file, $type = Zend_InfoCard_Cipher::ENC_RSA_OAEP_MGF1P, $password = null) + { + return $this->_infoCard->addCertificatePair($private_key_file, $public_key_file, $type, $password); + } + + /** + * Return a Certificate Pair from a key ID + * + * @param string $key_id The Key ID of the certificate pair in the component + * @throws Zend_InfoCard_Exception + * @return array An array containing the path to the private/public key files, + * the type URI and the password if provided + */ + public function getCertificatePair($key_id) + { + return $this->_infoCard->getCertificatePair($key_id); + } + + /** + * Set the XML Token to be processed + * + * @param string $strXmlToken The XML token to process + * @return Zend_Auth_Adapter_InfoCard Provides a fluent interface + */ + public function setXmlToken($strXmlToken) + { + $this->_xmlToken = $strXmlToken; + return $this; + } + + /** + * Get the XML Token being processed + * + * @return string The XML token to be processed + */ + public function getXmlToken() + { + return $this->_xmlToken; + } + + /** + * Authenticates the XML token + * + * @return Zend_Auth_Result The result of the authentication + */ + public function authenticate() + { + try { + $claims = $this->_infoCard->process($this->getXmlToken()); + } catch(Exception $e) { + return new Zend_Auth_Result(Zend_Auth_Result::FAILURE , null, array('Exception Thrown', + $e->getMessage(), + $e->getTraceAsString(), + serialize($e))); + } + + if(!$claims->isValid()) { + switch($claims->getCode()) { + case Zend_infoCard_Claims::RESULT_PROCESSING_FAILURE: + return new Zend_Auth_Result( + Zend_Auth_Result::FAILURE, + $claims, + array( + 'Processing Failure', + $claims->getErrorMsg() + ) + ); + break; + case Zend_InfoCard_Claims::RESULT_VALIDATION_FAILURE: + return new Zend_Auth_Result( + Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID, + $claims, + array( + 'Validation Failure', + $claims->getErrorMsg() + ) + ); + break; + default: + return new Zend_Auth_Result( + Zend_Auth_Result::FAILURE, + $claims, + array( + 'Unknown Failure', + $claims->getErrorMsg() + ) + ); + break; + } + } + + return new Zend_Auth_Result( + Zend_Auth_Result::SUCCESS, + $claims + ); + } +} diff --git a/library/Zend/Auth/Adapter/Interface.php b/library/Zend/Auth/Adapter/Interface.php new file mode 100644 index 00000000..ed01a337 --- /dev/null +++ b/library/Zend/Auth/Adapter/Interface.php @@ -0,0 +1,46 @@ +setOptions($options); + if ($username !== null) { + $this->setUsername($username); + } + if ($password !== null) { + $this->setPassword($password); + } + } + + /** + * Returns the array of arrays of Zend_Ldap options of this adapter. + * + * @return array|null + */ + public function getOptions() + { + return $this->_options; + } + + /** + * Sets the array of arrays of Zend_Ldap options to be used by + * this adapter. + * + * @param array $options The array of arrays of Zend_Ldap options + * @return Zend_Auth_Adapter_Ldap Provides a fluent interface + */ + public function setOptions($options) + { + $this->_options = is_array($options) ? $options : array(); + return $this; + } + + /** + * Returns the username of the account being authenticated, or + * NULL if none is set. + * + * @return string|null + */ + public function getUsername() + { + return $this->_username; + } + + /** + * Sets the username for binding + * + * @param string $username The username for binding + * @return Zend_Auth_Adapter_Ldap Provides a fluent interface + */ + public function setUsername($username) + { + $this->_username = (string) $username; + return $this; + } + + /** + * Returns the password of the account being authenticated, or + * NULL if none is set. + * + * @return string|null + */ + public function getPassword() + { + return $this->_password; + } + + /** + * Sets the passwort for the account + * + * @param string $password The password of the account being authenticated + * @return Zend_Auth_Adapter_Ldap Provides a fluent interface + */ + public function setPassword($password) + { + $this->_password = (string) $password; + return $this; + } + + /** + * setIdentity() - set the identity (username) to be used + * + * Proxies to {@see setUsername()} + * + * Closes ZF-6813 + * + * @param string $identity + * @return Zend_Auth_Adapter_Ldap Provides a fluent interface + */ + public function setIdentity($identity) + { + return $this->setUsername($identity); + } + + /** + * setCredential() - set the credential (password) value to be used + * + * Proxies to {@see setPassword()} + * + * Closes ZF-6813 + * + * @param string $credential + * @return Zend_Auth_Adapter_Ldap Provides a fluent interface + */ + public function setCredential($credential) + { + return $this->setPassword($credential); + } + + /** + * Returns the LDAP Object + * + * @return Zend_Ldap The Zend_Ldap object used to authenticate the credentials + */ + public function getLdap() + { + if ($this->_ldap === null) { + /** + * @see Zend_Ldap + */ + require_once 'Zend/Ldap.php'; + $this->_ldap = new Zend_Ldap(); + } + + return $this->_ldap; + } + + /** + * Set an Ldap connection + * + * @param Zend_Ldap $ldap An existing Ldap object + * @return Zend_Auth_Adapter_Ldap Provides a fluent interface + */ + public function setLdap(Zend_Ldap $ldap) + { + $this->_ldap = $ldap; + + $this->setOptions(array($ldap->getOptions())); + + return $this; + } + + /** + * Returns a domain name for the current LDAP options. This is used + * for skipping redundant operations (e.g. authentications). + * + * @return string + */ + protected function _getAuthorityName() + { + $options = $this->getLdap()->getOptions(); + $name = $options['accountDomainName']; + if (!$name) + $name = $options['accountDomainNameShort']; + return $name ? $name : ''; + } + + /** + * Authenticate the user + * + * @throws Zend_Auth_Adapter_Exception + * @return Zend_Auth_Result + */ + public function authenticate() + { + /** + * @see Zend_Ldap_Exception + */ + require_once 'Zend/Ldap/Exception.php'; + + $messages = array(); + $messages[0] = ''; // reserved + $messages[1] = ''; // reserved + + $username = $this->_username; + $password = $this->_password; + + if (!$username) { + $code = Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND; + $messages[0] = 'A username is required'; + return new Zend_Auth_Result($code, '', $messages); + } + if (!$password) { + /* A password is required because some servers will + * treat an empty password as an anonymous bind. + */ + $code = Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID; + $messages[0] = 'A password is required'; + return new Zend_Auth_Result($code, '', $messages); + } + + $ldap = $this->getLdap(); + + $code = Zend_Auth_Result::FAILURE; + $messages[0] = "Authority not found: $username"; + $failedAuthorities = array(); + + /* Iterate through each server and try to authenticate the supplied + * credentials against it. + */ + foreach ($this->_options as $name => $options) { + + if (!is_array($options)) { + /** + * @see Zend_Auth_Adapter_Exception + */ + require_once 'Zend/Auth/Adapter/Exception.php'; + throw new Zend_Auth_Adapter_Exception('Adapter options array not an array'); + } + $adapterOptions = $this->_prepareOptions($ldap, $options); + $dname = ''; + + try { + if ($messages[1]) + $messages[] = $messages[1]; + $messages[1] = ''; + $messages[] = $this->_optionsToString($options); + + $dname = $this->_getAuthorityName(); + if (isset($failedAuthorities[$dname])) { + /* If multiple sets of server options for the same domain + * are supplied, we want to skip redundant authentications + * where the identity or credentials where found to be + * invalid with another server for the same domain. The + * $failedAuthorities array tracks this condition (and also + * serves to supply the original error message). + * This fixes issue ZF-4093. + */ + $messages[1] = $failedAuthorities[$dname]; + $messages[] = "Skipping previously failed authority: $dname"; + continue; + } + + $canonicalName = $ldap->getCanonicalAccountName($username); + $ldap->bind($canonicalName, $password); + /* + * Fixes problem when authenticated user is not allowed to retrieve + * group-membership information or own account. + * This requires that the user specified with "username" and optionally + * "password" in the Zend_Ldap options is able to retrieve the required + * information. + */ + $requireRebind = false; + if (isset($options['username'])) { + $ldap->bind(); + $requireRebind = true; + } + $dn = $ldap->getCanonicalAccountName($canonicalName, Zend_Ldap::ACCTNAME_FORM_DN); + + $groupResult = $this->_checkGroupMembership($ldap, $canonicalName, $dn, $adapterOptions); + if ($groupResult === true) { + $this->_authenticatedDn = $dn; + $messages[0] = ''; + $messages[1] = ''; + $messages[] = "$canonicalName authentication successful"; + if ($requireRebind === true) { + // rebinding with authenticated user + $ldap->bind($dn, $password); + } + return new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $canonicalName, $messages); + } else { + $messages[0] = 'Account is not a member of the specified group'; + $messages[1] = $groupResult; + $failedAuthorities[$dname] = $groupResult; + } + } catch (Zend_Ldap_Exception $zle) { + + /* LDAP based authentication is notoriously difficult to diagnose. Therefore + * we bend over backwards to capture and record every possible bit of + * information when something goes wrong. + */ + + $err = $zle->getCode(); + + if ($err == Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH) { + /* This error indicates that the domain supplied in the + * username did not match the domains in the server options + * and therefore we should just skip to the next set of + * server options. + */ + continue; + } else if ($err == Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT) { + $code = Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND; + $messages[0] = "Account not found: $username"; + $failedAuthorities[$dname] = $zle->getMessage(); + } else if ($err == Zend_Ldap_Exception::LDAP_INVALID_CREDENTIALS) { + $code = Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID; + $messages[0] = 'Invalid credentials'; + $failedAuthorities[$dname] = $zle->getMessage(); + } else { + $line = $zle->getLine(); + $messages[] = $zle->getFile() . "($line): " . $zle->getMessage(); + $messages[] = str_replace($password, '*****', $zle->getTraceAsString()); + $messages[0] = 'An unexpected failure occurred'; + } + $messages[1] = $zle->getMessage(); + } + } + + $msg = isset($messages[1]) ? $messages[1] : $messages[0]; + $messages[] = "$username authentication failed: $msg"; + + return new Zend_Auth_Result($code, $username, $messages); + } + + /** + * Sets the LDAP specific options on the Zend_Ldap instance + * + * @param Zend_Ldap $ldap + * @param array $options + * @return array of auth-adapter specific options + */ + protected function _prepareOptions(Zend_Ldap $ldap, array $options) + { + $adapterOptions = array( + 'group' => null, + 'groupDn' => $ldap->getBaseDn(), + 'groupScope' => Zend_Ldap::SEARCH_SCOPE_SUB, + 'groupAttr' => 'cn', + 'groupFilter' => 'objectClass=groupOfUniqueNames', + 'memberAttr' => 'uniqueMember', + 'memberIsDn' => true + ); + foreach ($adapterOptions as $key => $value) { + if (array_key_exists($key, $options)) { + $value = $options[$key]; + unset($options[$key]); + switch ($key) { + case 'groupScope': + $value = (int)$value; + if (in_array($value, array(Zend_Ldap::SEARCH_SCOPE_BASE, + Zend_Ldap::SEARCH_SCOPE_ONE, Zend_Ldap::SEARCH_SCOPE_SUB), true)) { + $adapterOptions[$key] = $value; + } + break; + case 'memberIsDn': + $adapterOptions[$key] = ($value === true || + $value === '1' || strcasecmp($value, 'true') == 0); + break; + default: + $adapterOptions[$key] = trim($value); + break; + } + } + } + $ldap->setOptions($options); + return $adapterOptions; + } + + /** + * Checks the group membership of the bound user + * + * @param Zend_Ldap $ldap + * @param string $canonicalName + * @param string $dn + * @param array $adapterOptions + * @return string|true + */ + protected function _checkGroupMembership(Zend_Ldap $ldap, $canonicalName, $dn, array $adapterOptions) + { + if ($adapterOptions['group'] === null) { + return true; + } + + if ($adapterOptions['memberIsDn'] === false) { + $user = $canonicalName; + } else { + $user = $dn; + } + + /** + * @see Zend_Ldap_Filter + */ + require_once 'Zend/Ldap/Filter.php'; + $groupName = Zend_Ldap_Filter::equals($adapterOptions['groupAttr'], $adapterOptions['group']); + $membership = Zend_Ldap_Filter::equals($adapterOptions['memberAttr'], $user); + $group = Zend_Ldap_Filter::andFilter($groupName, $membership); + $groupFilter = $adapterOptions['groupFilter']; + if (!empty($groupFilter)) { + $group = $group->addAnd($groupFilter); + } + + $result = $ldap->count($group, $adapterOptions['groupDn'], $adapterOptions['groupScope']); + + if ($result === 1) { + return true; + } else { + return 'Failed to verify group membership with ' . $group->toString(); + } + } + + /** + * getAccountObject() - Returns the result entry as a stdClass object + * + * This resembles the feature {@see Zend_Auth_Adapter_DbTable::getResultRowObject()}. + * Closes ZF-6813 + * + * @param array $returnAttribs + * @param array $omitAttribs + * @return stdClass|boolean + */ + public function getAccountObject(array $returnAttribs = array(), array $omitAttribs = array()) + { + if (!$this->_authenticatedDn) { + return false; + } + + $returnObject = new stdClass(); + + $returnAttribs = array_map('strtolower', $returnAttribs); + $omitAttribs = array_map('strtolower', $omitAttribs); + $returnAttribs = array_diff($returnAttribs, $omitAttribs); + + $entry = $this->getLdap()->getEntry($this->_authenticatedDn, $returnAttribs, true); + foreach ($entry as $attr => $value) { + if (in_array($attr, $omitAttribs)) { + // skip attributes marked to be omitted + continue; + } + if (is_array($value)) { + $returnObject->$attr = (count($value) > 1) ? $value : $value[0]; + } else { + $returnObject->$attr = $value; + } + } + return $returnObject; + } + + /** + * Converts options to string + * + * @param array $options + * @return string + */ + private function _optionsToString(array $options) + { + $str = ''; + foreach ($options as $key => $val) { + if ($key === 'password') + $val = '*****'; + if ($str) + $str .= ','; + $str .= $key . '=' . $val; + } + return $str; + } +} diff --git a/library/Zend/Auth/Adapter/OpenId.php b/library/Zend/Auth/Adapter/OpenId.php new file mode 100644 index 00000000..81150322 --- /dev/null +++ b/library/Zend/Auth/Adapter/OpenId.php @@ -0,0 +1,284 @@ +_id = $id; + $this->_storage = $storage; + $this->_returnTo = $returnTo; + $this->_root = $root; + $this->_extensions = $extensions; + $this->_response = $response; + } + + /** + * Sets the value to be used as the identity + * + * @param string $id the identity value + * @return Zend_Auth_Adapter_OpenId Provides a fluent interface + */ + public function setIdentity($id) + { + $this->_id = $id; + return $this; + } + + /** + * Sets the storage implementation which will be use by OpenId + * + * @param Zend_OpenId_Consumer_Storage $storage + * @return Zend_Auth_Adapter_OpenId Provides a fluent interface + */ + public function setStorage(Zend_OpenId_Consumer_Storage $storage) + { + $this->_storage = $storage; + return $this; + } + + /** + * Sets the HTTP URL to redirect response from server to + * + * @param string $returnTo + * @return Zend_Auth_Adapter_OpenId Provides a fluent interface + */ + public function setReturnTo($returnTo) + { + $this->_returnTo = $returnTo; + return $this; + } + + /** + * Sets HTTP URL to identify consumer on server + * + * @param string $root + * @return Zend_Auth_Adapter_OpenId Provides a fluent interface + */ + public function setRoot($root) + { + $this->_root = $root; + return $this; + } + + /** + * Sets OpenID extension(s) + * + * @param mixed $extensions + * @return Zend_Auth_Adapter_OpenId Provides a fluent interface + */ + public function setExtensions($extensions) + { + $this->_extensions = $extensions; + return $this; + } + + /** + * Sets an optional response object to perform HTTP or HTML form redirection + * + * @param string $root + * @return Zend_Auth_Adapter_OpenId Provides a fluent interface + */ + public function setResponse($response) + { + $this->_response = $response; + return $this; + } + + /** + * Enables or disables interaction with user during authentication on + * OpenID provider. + * + * @param bool $check_immediate + * @return Zend_Auth_Adapter_OpenId Provides a fluent interface + */ + public function setCheckImmediate($check_immediate) + { + $this->_check_immediate = $check_immediate; + return $this; + } + + /** + * Sets HTTP client object to make HTTP requests + * + * @param Zend_Http_Client $client HTTP client object to be used + */ + public function setHttpClient($client) { + $this->_httpClient = $client; + } + + /** + * Authenticates the given OpenId identity. + * Defined by Zend_Auth_Adapter_Interface. + * + * @throws Zend_Auth_Adapter_Exception If answering the authentication query is impossible + * @return Zend_Auth_Result + */ + public function authenticate() { + $id = $this->_id; + if (!empty($id)) { + $consumer = new Zend_OpenId_Consumer($this->_storage); + $consumer->setHttpClient($this->_httpClient); + /* login() is never returns on success */ + if (!$this->_check_immediate) { + if (!$consumer->login($id, + $this->_returnTo, + $this->_root, + $this->_extensions, + $this->_response)) { + return new Zend_Auth_Result( + Zend_Auth_Result::FAILURE, + $id, + array("Authentication failed", $consumer->getError())); + } + } else { + if (!$consumer->check($id, + $this->_returnTo, + $this->_root, + $this->_extensions, + $this->_response)) { + return new Zend_Auth_Result( + Zend_Auth_Result::FAILURE, + $id, + array("Authentication failed", $consumer->getError())); + } + } + } else { + $params = (isset($_SERVER['REQUEST_METHOD']) && + $_SERVER['REQUEST_METHOD']=='POST') ? $_POST: $_GET; + $consumer = new Zend_OpenId_Consumer($this->_storage); + $consumer->setHttpClient($this->_httpClient); + if ($consumer->verify( + $params, + $id, + $this->_extensions)) { + return new Zend_Auth_Result( + Zend_Auth_Result::SUCCESS, + $id, + array("Authentication successful")); + } else { + return new Zend_Auth_Result( + Zend_Auth_Result::FAILURE, + $id, + array("Authentication failed", $consumer->getError())); + } + } + } + +} diff --git a/library/Zend/Auth/Exception.php b/library/Zend/Auth/Exception.php new file mode 100644 index 00000000..522ef77e --- /dev/null +++ b/library/Zend/Auth/Exception.php @@ -0,0 +1,36 @@ + self::SUCCESS ) { + $code = 1; + } + + $this->_code = $code; + $this->_identity = $identity; + $this->_messages = $messages; + } + + /** + * Returns whether the result represents a successful authentication attempt + * + * @return boolean + */ + public function isValid() + { + return ($this->_code > 0) ? true : false; + } + + /** + * getCode() - Get the result code for this authentication attempt + * + * @return int + */ + public function getCode() + { + return $this->_code; + } + + /** + * Returns the identity used in the authentication attempt + * + * @return mixed + */ + public function getIdentity() + { + return $this->_identity; + } + + /** + * Returns an array of string reasons why the authentication attempt was unsuccessful + * + * If authentication was successful, this method returns an empty array. + * + * @return array + */ + public function getMessages() + { + return $this->_messages; + } +} diff --git a/library/Zend/Auth/Storage/Exception.php b/library/Zend/Auth/Storage/Exception.php new file mode 100644 index 00000000..7e7ed101 --- /dev/null +++ b/library/Zend/Auth/Storage/Exception.php @@ -0,0 +1,38 @@ +_data); + } + + /** + * Returns the contents of storage + * Behavior is undefined when storage is empty. + * + * @throws Zend_Auth_Storage_Exception If reading contents from storage is impossible + * @return mixed + */ + public function read() + { + return $this->_data; + } + + /** + * Writes $contents to storage + * + * @param mixed $contents + * @throws Zend_Auth_Storage_Exception If writing $contents to storage is impossible + * @return void + */ + public function write($contents) + { + $this->_data = $contents; + } + + /** + * Clears contents from storage + * + * @throws Zend_Auth_Storage_Exception If clearing contents from storage is impossible + * @return void + */ + public function clear() + { + $this->_data = null; + } +} diff --git a/library/Zend/Auth/Storage/Session.php b/library/Zend/Auth/Storage/Session.php new file mode 100644 index 00000000..1799632b --- /dev/null +++ b/library/Zend/Auth/Storage/Session.php @@ -0,0 +1,150 @@ +_namespace = $namespace; + $this->_member = $member; + $this->_session = new Zend_Session_Namespace($this->_namespace); + } + + /** + * Returns the session namespace + * + * @return string + */ + public function getNamespace() + { + return $this->_namespace; + } + + /** + * Returns the name of the session object member + * + * @return string + */ + public function getMember() + { + return $this->_member; + } + + /** + * Defined by Zend_Auth_Storage_Interface + * + * @return boolean + */ + public function isEmpty() + { + return !isset($this->_session->{$this->_member}); + } + + /** + * Defined by Zend_Auth_Storage_Interface + * + * @return mixed + */ + public function read() + { + return $this->_session->{$this->_member}; + } + + /** + * Defined by Zend_Auth_Storage_Interface + * + * @param mixed $contents + * @return void + */ + public function write($contents) + { + $this->_session->{$this->_member} = $contents; + } + + /** + * Defined by Zend_Auth_Storage_Interface + * + * @return void + */ + public function clear() + { + unset($this->_session->{$this->_member}); + } +} diff --git a/library/Zend/Barcode.php b/library/Zend/Barcode.php new file mode 100644 index 00000000..63e197ec --- /dev/null +++ b/library/Zend/Barcode.php @@ -0,0 +1,352 @@ +rendererParams)) { + $rendererConfig = $barcode->rendererParams->toArray(); + } + if (isset($barcode->renderer)) { + $renderer = (string) $barcode->renderer; + } + if (isset($barcode->barcodeParams)) { + $barcodeConfig = $barcode->barcodeParams->toArray(); + } + if (isset($barcode->barcode)) { + $barcode = (string) $barcode->barcode; + } else { + $barcode = null; + } + } + + try { + $barcode = self::makeBarcode($barcode, $barcodeConfig); + $renderer = self::makeRenderer($renderer, $rendererConfig); + } catch (Zend_Exception $e) { + $renderable = ($e instanceof Zend_Barcode_Exception) ? $e->isRenderable() : false; + if ($automaticRenderError && $renderable) { + $barcode = self::makeBarcode('error', array( + 'text' => $e->getMessage() + )); + $renderer = self::makeRenderer($renderer, array()); + } else { + throw $e; + } + } + + $renderer->setAutomaticRenderError($automaticRenderError); + return $renderer->setBarcode($barcode); + } + + /** + * Barcode Constructor + * + * @param mixed $barcode String name of barcode class, or Zend_Config object. + * @param mixed $barcodeConfig OPTIONAL; an array or Zend_Config object with barcode parameters. + * @return Zend_Barcode_Object + */ + public static function makeBarcode($barcode, $barcodeConfig = array()) + { + if ($barcode instanceof Zend_Barcode_Object_ObjectAbstract) { + return $barcode; + } + + /* + * Convert Zend_Config argument to plain string + * barcode name and separate config object. + */ + if ($barcode instanceof Zend_Config) { + if (isset($barcode->barcodeParams) && $barcode->barcodeParams instanceof Zend_Config) { + $barcodeConfig = $barcode->barcodeParams->toArray(); + } + if (isset($barcode->barcode)) { + $barcode = (string) $barcode->barcode; + } else { + $barcode = null; + } + } + if ($barcodeConfig instanceof Zend_Config) { + $barcodeConfig = $barcodeConfig->toArray(); + } + + /* + * Verify that barcode parameters are in an array. + */ + if (!is_array($barcodeConfig)) { + /** + * @see Zend_Barcode_Exception + */ + require_once 'Zend/Barcode/Exception.php'; + throw new Zend_Barcode_Exception( + 'Barcode parameters must be in an array or a Zend_Config object' + ); + } + + /* + * Verify that an barcode name has been specified. + */ + if (!is_string($barcode) || empty($barcode)) { + /** + * @see Zend_Barcode_Exception + */ + require_once 'Zend/Barcode/Exception.php'; + throw new Zend_Barcode_Exception( + 'Barcode name must be specified in a string' + ); + } + /* + * Form full barcode class name + */ + $barcodeNamespace = 'Zend_Barcode_Object'; + if (isset($barcodeConfig['barcodeNamespace'])) { + $barcodeNamespace = $barcodeConfig['barcodeNamespace']; + } + + $barcodeName = strtolower($barcodeNamespace . '_' . $barcode); + $barcodeName = str_replace(' ', '_', ucwords( + str_replace( '_', ' ', $barcodeName) + )); + + /* + * Load the barcode class. This throws an exception + * if the specified class cannot be loaded. + */ + if (!class_exists($barcodeName)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($barcodeName); + } + + /* + * Create an instance of the barcode class. + * Pass the config to the barcode class constructor. + */ + $bcAdapter = new $barcodeName($barcodeConfig); + + /* + * Verify that the object created is a descendent of the abstract barcode type. + */ + if (!$bcAdapter instanceof Zend_Barcode_Object_ObjectAbstract) { + /** + * @see Zend_Barcode_Exception + */ + require_once 'Zend/Barcode/Exception.php'; + throw new Zend_Barcode_Exception( + "Barcode class '$barcodeName' does not extend Zend_Barcode_Object_ObjectAbstract" + ); + } + return $bcAdapter; + } + + /** + * Renderer Constructor + * + * @param mixed $renderer String name of renderer class, or Zend_Config object. + * @param mixed $rendererConfig OPTIONAL; an array or Zend_Config object with renderer parameters. + * @return Zend_Barcode_Renderer + */ + public static function makeRenderer($renderer = 'image', $rendererConfig = array()) + { + if ($renderer instanceof Zend_Barcode_Renderer_RendererAbstract) { + return $renderer; + } + + /* + * Convert Zend_Config argument to plain string + * barcode name and separate config object. + */ + if ($renderer instanceof Zend_Config) { + if (isset($renderer->rendererParams)) { + $rendererConfig = $renderer->rendererParams->toArray(); + } + if (isset($renderer->renderer)) { + $renderer = (string) $renderer->renderer; + } + } + if ($rendererConfig instanceof Zend_Config) { + $rendererConfig = $rendererConfig->toArray(); + } + + /* + * Verify that barcode parameters are in an array. + */ + if (!is_array($rendererConfig)) { + /** + * @see Zend_Barcode_Exception + */ + require_once 'Zend/Barcode/Exception.php'; + $e = new Zend_Barcode_Exception( + 'Barcode parameters must be in an array or a Zend_Config object' + ); + $e->setIsRenderable(false); + throw $e; + } + + /* + * Verify that an barcode name has been specified. + */ + if (!is_string($renderer) || empty($renderer)) { + /** + * @see Zend_Barcode_Exception + */ + require_once 'Zend/Barcode/Exception.php'; + $e = new Zend_Barcode_Exception( + 'Renderer name must be specified in a string' + ); + $e->setIsRenderable(false); + throw $e; + } + + /* + * Form full barcode class name + */ + $rendererNamespace = 'Zend_Barcode_Renderer'; + if (isset($rendererConfig['rendererNamespace'])) { + $rendererNamespace = $rendererConfig['rendererNamespace']; + } + + $rendererName = strtolower($rendererNamespace . '_' . $renderer); + $rendererName = str_replace(' ', '_', ucwords( + str_replace( '_', ' ', $rendererName) + )); + + /* + * Load the barcode class. This throws an exception + * if the specified class cannot be loaded. + */ + if (!class_exists($rendererName)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($rendererName); + } + + /* + * Create an instance of the barcode class. + * Pass the config to the barcode class constructor. + */ + $rdrAdapter = new $rendererName($rendererConfig); + + /* + * Verify that the object created is a descendent of the abstract barcode type. + */ + if (!$rdrAdapter instanceof Zend_Barcode_Renderer_RendererAbstract) { + /** + * @see Zend_Barcode_Exception + */ + require_once 'Zend/Barcode/Exception.php'; + $e = new Zend_Barcode_Exception( + "Renderer class '$rendererName' does not extend Zend_Barcode_Renderer_RendererAbstract" + ); + $e->setIsRenderable(false); + throw $e; + } + return $rdrAdapter; + } + + /** + * Proxy to renderer render() method + * + * @param string | Zend_Barcode_Object | array | Zend_Config $barcode + * @param string | Zend_Barcode_Renderer $renderer + * @param array | Zend_Config $barcodeConfig + * @param array | Zend_Config $rendererConfig + */ + public static function render( + $barcode, + $renderer, + $barcodeConfig = array(), + $rendererConfig = array() + ) { + self::factory($barcode, $renderer, $barcodeConfig, $rendererConfig)->render(); + } + + /** + * Proxy to renderer draw() method + * + * @param string | Zend_Barcode_Object | array | Zend_Config $barcode + * @param string | Zend_Barcode_Renderer $renderer + * @param array | Zend_Config $barcodeConfig + * @param array | Zend_Config $rendererConfig + * @return mixed + */ + public static function draw( + $barcode, + $renderer, + $barcodeConfig = array(), + $rendererConfig = array() + ) { + return self::factory($barcode, $renderer, $barcodeConfig, $rendererConfig)->draw(); + } + + /** + * Proxy for setBarcodeFont of Zend_Barcode_Object + * @param string $font + * @eturn void + */ + public static function setBarcodeFont($font) + { + require_once 'Zend/Barcode/Object/ObjectAbstract.php'; + Zend_Barcode_Object_ObjectAbstract::setBarcodeFont($font); + } +} diff --git a/library/Zend/Barcode/Exception.php b/library/Zend/Barcode/Exception.php new file mode 100644 index 00000000..ecff266e --- /dev/null +++ b/library/Zend/Barcode/Exception.php @@ -0,0 +1,63 @@ +_isRenderable = (bool) $flag; + return $this; + } + + /** + * Retrieve renderable flag + * + * @return bool + */ + public function isRenderable() + { + return $this->_isRenderable; + } +} diff --git a/library/Zend/Barcode/Object/Code128.php b/library/Zend/Barcode/Object/Code128.php new file mode 100644 index 00000000..917e369f --- /dev/null +++ b/library/Zend/Barcode/Object/Code128.php @@ -0,0 +1,391 @@ + "11011001100", 1 => "11001101100", 2 => "11001100110", + 3 => "10010011000", 4 => "10010001100", 5 => "10001001100", + 6 => "10011001000", 7 => "10011000100", 8 => "10001100100", + 9 => "11001001000", 10 => "11001000100", 11 => "11000100100", + 12 => "10110011100", 13 => "10011011100", 14 => "10011001110", + 15 => "10111001100", 16 => "10011101100", 17 => "10011100110", + 18 => "11001110010", 19 => "11001011100", 20 => "11001001110", + 21 => "11011100100", 22 => "11001110100", 23 => "11101101110", + 24 => "11101001100", 25 => "11100101100", 26 => "11100100110", + 27 => "11101100100", 28 => "11100110100", 29 => "11100110010", + 30 => "11011011000", 31 => "11011000110", 32 => "11000110110", + 33 => "10100011000", 34 => "10001011000", 35 => "10001000110", + 36 => "10110001000", 37 => "10001101000", 38 => "10001100010", + 39 => "11010001000", 40 => "11000101000", 41 => "11000100010", + 42 => "10110111000", 43 => "10110001110", 44 => "10001101110", + 45 => "10111011000", 46 => "10111000110", 47 => "10001110110", + 48 => "11101110110", 49 => "11010001110", 50 => "11000101110", + 51 => "11011101000", 52 => "11011100010", 53 => "11011101110", + 54 => "11101011000", 55 => "11101000110", 56 => "11100010110", + 57 => "11101101000", 58 => "11101100010", 59 => "11100011010", + 60 => "11101111010", 61 => "11001000010", 62 => "11110001010", + 63 => "10100110000", 64 => "10100001100", 65 => "10010110000", + 66 => "10010000110", 67 => "10000101100", 68 => "10000100110", + 69 => "10110010000", 70 => "10110000100", 71 => "10011010000", + 72 => "10011000010", 73 => "10000110100", 74 => "10000110010", + 75 => "11000010010", 76 => "11001010000", 77 => "11110111010", + 78 => "11000010100", 79 => "10001111010", 80 => "10100111100", + 81 => "10010111100", 82 => "10010011110", 83 => "10111100100", + 84 => "10011110100", 85 => "10011110010", 86 => "11110100100", + 87 => "11110010100", 88 => "11110010010", 89 => "11011011110", + 90 => "11011110110", 91 => "11110110110", 92 => "10101111000", + 93 => "10100011110", 94 => "10001011110", 95 => "10111101000", + 96 => "10111100010", 97 => "11110101000", 98 => "11110100010", + 99 => "10111011110", 100 => "10111101110", 101 => "11101011110", + 102 => "11110101110", + 103 => "11010000100", 104 => "11010010000", 105 => "11010011100", + 106 => "1100011101011"); + + /** + * Character sets ABC + * @var array + */ + protected $_charSets = array( + 'A' => array( + ' ', '!', '"', '#', '$', '%', '&', "'", + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 'FNC3', 'FNC2', 'SHIFT', 'Code C', 'Code B', 'FNC4', 'FNC1', + 'START A', 'START B', 'START C', 'STOP'), + 'B' => array( + ' ', '!', '"', '#', '$', '%', '&', "'", + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', + 'x', 'y', 'z', '{', '|', '}', '~', 0x7F, + 'FNC3', 'FNC2', 'SHIFT', 'Code C', 'FNC4', 'Code A', 'FNC1', + 'START A', 'START B', 'START C', 'STOP',), + 'C' => array( + '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', + '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', + '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', + '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', + '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', + '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', + '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', + '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', + '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', + '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', + 'Code B', 'Code A', 'FNC1', 'START A', 'START B', 'START C', 'STOP')); + /*'A' => array( + ' '=>0, '!'=>1, '"'=>2, '#'=>3, '$'=>4, '%'=>5, '&'=>6, "'"=>7, + '('=>8, ')'=>9, '*'=>10, '+'=>11, ','=>12, '-'=>13, '.'=>14, '/'=>15, + '0'=>16, '1'=>17, '2'=>18, '3'=>19, '4'=>20, '5'=>21, '6'=>22, '7'=>23, + '8'=>24, '9'=>25, ':'=>26, ';'=>27, '<'=>28, '='=>29, '>'=>30, '?'=>31, + '@'=>32, 'A'=>33, 'B'=>34, 'C'=>35, 'D'=>36, 'E'=>37, 'F'=>38, 'G'=>39, + 'H'=>40, 'I'=>41, 'J'=>42, 'K'=>43, 'L'=>44, 'M'=>45, 'N'=>46, 'O'=>47, + 'P'=>48, 'Q'=>49, 'R'=>50, 'S'=>51, 'T'=>52, 'U'=>53, 'V'=>54, 'W'=>55, + 'X'=>56, 'Y'=>57, 'Z'=>58, '['=>59, '\\'=>60, ']'=>61, '^'=>62, '_'=>63, + 0x00=>64, 0x01=>65, 0x02=>66, 0x03=>67, 0x04=>68, 0x05=>69, 0x06=>70, 0x07=>71, + 0x08=>72, 0x09=>73, 0x0A=>74, 0x0B=>75, 0x0C=>76, 0x0D=>77, 0x0E=>78, 0x0F=>79, + 0x10=>80, 0x11=>81, 0x12=>82, 0x13=>83, 0x14=>84, 0x15=>85, 0x16=>86, 0x17=>87, + 0x18=>88, 0x19=>89, 0x1A=>90, 0x1B=>91, 0x1C=>92, 0x1D=>93, 0x1E=>94, 0x1F=>95, + 'FNC3'=>96, 'FNC2'=>97, 'SHIFT'=>98, 'Code C'=>99, 'Code B'=>100, 'FNC4'=>101, 'FNC1'=>102, 'START A'=>103, + 'START B'=>104, 'START C'=>105, 'STOP'=>106), + 'B' => array( + ' '=>0, '!'=>1, '"'=>2, '#'=>3, '$'=>4, '%'=>5, '&'=>6, "'"=>7, + '('=>8, ')'=>9, '*'=>10, '+'=>11, ','=>12, '-'=>13, '.'=>14, '/'=>15, + '0'=>16, '1'=>17, '2'=>18, '3'=>19, '4'=>20, '5'=>21, '6'=>22, '7'=>23, + '8'=>24, '9'=>25, ':'=>26, ';'=>27, '<'=>28, '='=>29, '>'=>30, '?'=>31, + '@'=>32, 'A'=>33, 'B'=>34, 'C'=>35, 'D'=>36, 'E'=>37, 'F'=>38, 'G'=>39, + 'H'=>40, 'I'=>41, 'J'=>42, 'K'=>43, 'L'=>44, 'M'=>45, 'N'=>46, 'O'=>47, + 'P'=>48, 'Q'=>49, 'R'=>50, 'S'=>51, 'T'=>52, 'U'=>53, 'V'=>54, 'W'=>55, + 'X'=>56, 'Y'=>57, 'Z'=>58, '['=>59, '\\'=>60, ']'=>61, '^'=>62, '_'=>63, + '`' =>64, 'a'=>65, 'b'=>66, 'c'=>67, 'd'=>68, 'e'=>69, 'f'=>70, 'g'=>71, + 'h'=>72, 'i'=>73, 'j'=>74, 'k'=>75, 'l'=>76, 'm'=>77, 'n'=>78, 'o'=>79, + 'p'=>80, 'q'=>81, 'r'=>82, 's'=>83, 't'=>84, 'u'=>85, 'v'=>86, 'w'=>87, + 'x'=>88, 'y'=>89, 'z'=>90, '{'=>91, '|'=>92, '}'=>93, '~'=>94, 0x7F=>95, + 'FNC3'=>96, 'FNC2'=>97, 'SHIFT'=>98, 'Code C'=>99, 'FNC4'=>100, 'Code A'=>101, 'FNC1'=>102, 'START A'=>103, + 'START B'=>104, 'START C'=>105, 'STOP'=>106,), + 'C' => array( + '00'=>0, '01'=>1, '02'=>2, '03'=>3, '04'=>4, '05'=>5, '06'=>6, '07'=>7, '08'=>8, '09'=>9, + '10'=>10, '11'=>11, '12'=>12, '13'=>13, '14'=>14, '15'=>15, '16'=>16, '17'=>17, '18'=>18, '19'=>19, + '20'=>20, '21'=>21, '22'=>22, '23'=>23, '24'=>24, '25'=>25, '26'=>26, '27'=>27, '28'=>28, '29'=>29, + '30'=>30, '31'=>31, '32'=>32, '33'=>33, '34'=>34, '35'=>35, '36'=>36, '37'=>37, '38'=>38, '39'=>39, + '40'=>40, '41'=>41, '42'=>42, '43'=>43, '44'=>44, '45'=>45, '46'=>46, '47'=>47, '48'=>48, '49'=>49, + '50'=>50, '51'=>51, '52'=>52, '53'=>53, '54'=>54, '55'=>55, '56'=>56, '57'=>57, '58'=>58, '59'=>59, + '60'=>60, '61'=>61, '62'=>62, '63'=>63, '64'=>64, '65'=>65, '66'=>66, '67'=>67, '68'=>68, '69'=>69, + '70'=>70, '71'=>71, '72'=>72, '73'=>73, '74'=>74, '75'=>75, '76'=>76, '77'=>77, '78'=>78, '79'=>79, + '80'=>80, '81'=>81, '82'=>82, '83'=>83, '84'=>84, '85'=>85, '86'=>86, '87'=>87, '88'=>88, '89'=>89, + '90'=>90, '91'=>91, '92'=>92, '93'=>93, '94'=>94, '95'=>95, '96'=>96, '97'=>97, '98'=>98, '99'=>99, + 'Code B'=>100, 'Code A'=>101, 'FNC1'=>102, 'START A'=>103, 'START B'=>104, 'START C'=>105, 'STOP'=>106));*/ + + /** + * Width of the barcode (in pixels) + * @return integer + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + // Each characters contain 11 bars... + $characterLength = 11 * $this->_barThinWidth * $this->_factor; + $convertedChars = count($this->_convertToBarcodeChars($this->getText())); + if ($this->_withChecksum) { + $convertedChars++; + } + $encodedData = $convertedChars * $characterLength; + // ...except the STOP character (13) + $encodedData += $characterLength + 2 * $this->_barThinWidth * $this->_factor; + $width = $quietZone + $encodedData + $quietZone; + return $width; + } + + /** + * Partial check of code128 barcode + * @return void + */ + protected function _checkParams() + { + } + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + $barcodeTable = array(); + + $convertedChars = $this->_convertToBarcodeChars($this->getText()); + + if ($this->_withChecksum) { + $convertedChars[] = $this->getChecksum($this->getText()); + } + + // STOP CHARACTER + $convertedChars[] = 106; + + foreach ($convertedChars as $barcodeChar) { + $barcodePattern = $this->_codingMap[$barcodeChar]; + foreach (str_split($barcodePattern) as $c) { + $barcodeTable[] = array($c, $this->_barThinWidth, 0, 1); + } + } + return $barcodeTable; + } + + /** + * Checks if the next $length chars of $string starting at $pos are numeric. + * Returns false if the end of the string is reached. + * @param string $string String to search + * @param int $pos Starting position + * @param int $length Length to search + * @return bool + */ + protected static function _isDigit($string, $pos, $length = 2) + { + if ($pos + $length > strlen($string)) { + return false; + } + + for ($i = $pos; $i < $pos + $length; $i++) { + if (!is_numeric($string[$i])) { + return false; + } + } + return true; + } + + /** + * Convert string to barcode string + * @return array + */ + protected function _convertToBarcodeChars($string) + { + $string = (string) $string; + if (!strlen($string)) { + return array(); + } + + if (isset($this->_convertedText[md5($string)])) { + return $this->_convertedText[md5($string)]; + } + + $currentCharset = null; + $sum = 0; + $fak = 0; + $result = array(); + + for ($pos = 0; $pos < strlen($string); $pos++) { + $char = $string[$pos]; + $code = null; + + if (self::_isDigit($string, $pos, 4) && $currentCharset != 'C' + || self::_isDigit($string, $pos, 2) && $currentCharset == 'C') { + /** + * Switch to C if the next 4 chars are numeric or stay C if the next 2 + * chars are numeric + */ + if ($currentCharset != 'C') { + if ($pos == 0) { + $code = array_search("START C", $this->_charSets['C']); + } else { + $code = array_search("Code C", $this->_charSets[$currentCharset]); + } + $result[] = $code; + $currentCharset = 'C'; + } + } else if (in_array($char, $this->_charSets['B']) && $currentCharset != 'B' + && !(in_array($char, $this->_charSets['A']) && $currentCharset == 'A')) { + /** + * Switch to B as B contains the char and B is not the current charset. + */ + if ($pos == 0) { + $code = array_search("START B", $this->_charSets['B']); + } else { + $code = array_search("Code B", $this->_charSets[$currentCharset]); + } + $result[] = $code; + $currentCharset = 'B'; + } else if (array_key_exists($char, $this->_charSets['A']) && $currentCharset != 'A' + && !(array_key_exists($char, $this->_charSets['B']) && $currentCharset == 'B')) { + /** + * Switch to C as C contains the char and C is not the current charset. + */ + if ($pos == 0) { + $code = array_search("START A", $this->_charSets['A']); + } else { + $code =array_search("Code A", $this->_charSets[$currentCharset]); + } + $result[] = $code; + $currentCharset = 'A'; + } + + if ($currentCharset == 'C') { + $code = array_search(substr($string, $pos, 2), $this->_charSets['C']); + $pos++; //Two chars from input + } else { + $code = array_search($string[$pos], $this->_charSets[$currentCharset]); + } + $result[] = $code; + } + + $this->_convertedText[md5($string)] = $result; + return $result; + } + + /** + * Set text to encode + * @param string $value + * @return Zend_Barcode_Object + */ + public function setText($value) + { + $this->_text = $value; + return $this; + } + + /** + * Retrieve text to encode + * @return string + */ + public function getText() + { + return $this->_text; + } + + /** + * Get barcode checksum + * + * @param string $text + * @return int + */ + public function getChecksum($text) + { + $tableOfChars = $this->_convertToBarcodeChars($text); + + $sum = $tableOfChars[0]; + unset($tableOfChars[0]); + + $k = 1; + foreach ($tableOfChars as $char) { + $sum += ($k++) * $char; + } + + $checksum = $sum % 103; + + return $checksum; + } + + /** + * Standard validation for most of barcode objects + * @param string $value + * @param array $options + */ + protected function _validateText($value, $options = array()) + { + // @TODO: add code128 validator + return true; + } +} diff --git a/library/Zend/Barcode/Object/Code25.php b/library/Zend/Barcode/Object/Code25.php new file mode 100644 index 00000000..d90e37ff --- /dev/null +++ b/library/Zend/Barcode/Object/Code25.php @@ -0,0 +1,143 @@ + '00110', + '1' => '10001', + '2' => '01001', + '3' => '11000', + '4' => '00101', + '5' => '10100', + '6' => '01100', + '7' => '00011', + '8' => '10010', + '9' => '01010', + ); + + /** + * Width of the barcode (in pixels) + * @return integer + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + $startCharacter = (2 * $this->_barThickWidth + 4 * $this->_barThinWidth) * $this->_factor; + $characterLength = (3 * $this->_barThinWidth + 2 * $this->_barThickWidth + 5 * $this->_barThinWidth) + * $this->_factor; + $encodedData = strlen($this->getText()) * $characterLength; + $stopCharacter = (2 * $this->_barThickWidth + 4 * $this->_barThinWidth) * $this->_factor; + return $quietZone + $startCharacter + $encodedData + $stopCharacter + $quietZone; + } + + /** + * Partial check of interleaved 2 of 5 barcode + * @return void + */ + protected function _checkParams() + { + $this->_checkRatio(); + } + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + $barcodeTable = array(); + + // Start character (30301) + $barcodeTable[] = array(1 , $this->_barThickWidth , 0 , 1); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(1 , $this->_barThickWidth , 0 , 1); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(0 , $this->_barThinWidth); + + $text = str_split($this->getText()); + foreach ($text as $char) { + $barcodeChar = str_split($this->_codingMap[$char]); + foreach ($barcodeChar as $c) { + /* visible, width, top, length */ + $width = $c ? $this->_barThickWidth : $this->_barThinWidth; + $barcodeTable[] = array(1 , $width , 0 , 1); + $barcodeTable[] = array(0 , $this->_barThinWidth); + } + } + + // Stop character (30103) + $barcodeTable[] = array(1 , $this->_barThickWidth , 0 , 1); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(1 , $this->_barThickWidth , 0 , 1); + return $barcodeTable; + } + + /** + * Get barcode checksum + * + * @param string $text + * @return int + */ + public function getChecksum($text) + { + $this->_checkText($text); + $factor = 3; + $checksum = 0; + + for ($i = strlen($text); $i > 0; $i --) { + $checksum += intval($text{$i - 1}) * $factor; + $factor = 4 - $factor; + } + + $checksum = (10 - ($checksum % 10)) % 10; + + return $checksum; + } +} diff --git a/library/Zend/Barcode/Object/Code25interleaved.php b/library/Zend/Barcode/Object/Code25interleaved.php new file mode 100644 index 00000000..8e5b0500 --- /dev/null +++ b/library/Zend/Barcode/Object/Code25interleaved.php @@ -0,0 +1,179 @@ +_barcodeLength = 'even'; + } + + /** + * Activate/deactivate drawing of bearer bars + * @param boolean $value + * @return Zend_Barcode_Object_Int25 + */ + public function setWithBearerBars($value) + { + $this->_withBearerBars = (bool) $value; + return $this; + } + + /** + * Retrieve if bearer bars are enabled + * @return boolean + */ + public function getWithBearerBars() + { + return $this->_withBearerBars; + } + + /** + * Width of the barcode (in pixels) + * @return integer + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + $startCharacter = (4 * $this->_barThinWidth) * $this->_factor; + $characterLength = (3 * $this->_barThinWidth + 2 * $this->_barThickWidth) * $this->_factor; + $encodedData = strlen($this->getText()) * $characterLength; + $stopCharacter = ($this->_barThickWidth + 2 * $this->_barThinWidth) * $this->_factor; + return $quietZone + $startCharacter + $encodedData + $stopCharacter + $quietZone; + } + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + if ($this->_withBearerBars) { + $this->_withBorder = false; + } + + // Start character (0000) + $barcodeTable[] = array(1, $this->_barThinWidth, 0, 1); + $barcodeTable[] = array(0, $this->_barThinWidth, 0, 1); + $barcodeTable[] = array(1, $this->_barThinWidth, 0, 1); + $barcodeTable[] = array(0, $this->_barThinWidth, 0, 1); + + // Encoded $text + $text = $this->getText(); + for ($i = 0; $i < strlen($text); $i += 2) { // Draw 2 chars at a time + $char1 = substr($text, $i, 1); + $char2 = substr($text, $i + 1, 1); + + // Interleave + for ($ibar = 0; $ibar < 5; $ibar ++) { + // Draws char1 bar (fore color) + $barWidth = (substr($this->_codingMap[$char1], $ibar, 1)) + ? $this->_barThickWidth + : $this->_barThinWidth; + + $barcodeTable[] = array(1, $barWidth, 0, 1); + + // Left space corresponding to char2 (background color) + $barWidth = (substr($this->_codingMap[$char2], $ibar, 1)) + ? $this->_barThickWidth + : $this->_barThinWidth; + $barcodeTable[] = array(0, $barWidth, 0 , 1); + } + } + + // Stop character (100) + $barcodeTable[] = array(1 , $this->_barThickWidth, 0, 1); + $barcodeTable[] = array(0 , $this->_barThinWidth, 0, 1); + $barcodeTable[] = array(1 , $this->_barThinWidth, 0, 1); + return $barcodeTable; + } + + /** + * Drawing of bearer bars (if enabled) + * + * @return void + */ + protected function _postDrawBarcode() + { + if (!$this->_withBearerBars) { + return; + } + + $width = $this->_barThickWidth * $this->_factor; + $point1 = $this->_rotate(-1, -1); + $point2 = $this->_rotate($this->_calculateWidth() - 1, -1); + $point3 = $this->_rotate($this->_calculateWidth() - 1, $width - 1); + $point4 = $this->_rotate(-1, $width - 1); + $this->_addPolygon(array( + $point1, + $point2, + $point3, + $point4, + )); + $point1 = $this->_rotate( + 0, + 0 + $this->_barHeight * $this->_factor - 1 + ); + $point2 = $this->_rotate( + $this->_calculateWidth() - 1, + 0 + $this->_barHeight * $this->_factor - 1 + ); + $point3 = $this->_rotate( + $this->_calculateWidth() - 1, + 0 + $this->_barHeight * $this->_factor - $width + ); + $point4 = $this->_rotate( + 0, + 0 + $this->_barHeight * $this->_factor - $width + ); + $this->_addPolygon(array( + $point1, + $point2, + $point3, + $point4, + )); + } +} diff --git a/library/Zend/Barcode/Object/Code39.php b/library/Zend/Barcode/Object/Code39.php new file mode 100644 index 00000000..03544ead --- /dev/null +++ b/library/Zend/Barcode/Object/Code39.php @@ -0,0 +1,188 @@ + '000110100', + '1' => '100100001', + '2' => '001100001', + '3' => '101100000', + '4' => '000110001', + '5' => '100110000', + '6' => '001110000', + '7' => '000100101', + '8' => '100100100', + '9' => '001100100', + 'A' => '100001001', + 'B' => '001001001', + 'C' => '101001000', + 'D' => '000011001', + 'E' => '100011000', + 'F' => '001011000', + 'G' => '000001101', + 'H' => '100001100', + 'I' => '001001100', + 'J' => '000011100', + 'K' => '100000011', + 'L' => '001000011', + 'M' => '101000010', + 'N' => '000010011', + 'O' => '100010010', + 'P' => '001010010', + 'Q' => '000000111', + 'R' => '100000110', + 'S' => '001000110', + 'T' => '000010110', + 'U' => '110000001', + 'V' => '011000001', + 'W' => '111000000', + 'X' => '010010001', + 'Y' => '110010000', + 'Z' => '011010000', + '-' => '010000101', + '.' => '110000100', + ' ' => '011000100', + '$' => '010101000', + '/' => '010100010', + '+' => '010001010', + '%' => '000101010', + '*' => '010010100', + ); + + /** + * Partial check of Code39 barcode + * @return void + */ + protected function _checkParams() + { + $this->_checkRatio(); + } + + /** + * Width of the barcode (in pixels) + * @return int + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + $characterLength = (6 * $this->_barThinWidth + 3 * $this->_barThickWidth + 1) * $this->_factor; + $encodedData = strlen($this->getText()) * $characterLength - $this->_factor; + return $quietZone + $encodedData + $quietZone; + } + + /** + * Set text to encode + * @param string $value + * @return Zend_Barcode_Object + */ + public function setText($value) + { + $this->_text = $value; + return $this; + } + + /** + * Retrieve text to display + * @return string + */ + public function getText() + { + return '*' . parent::getText() . '*'; + } + + /** + * Retrieve text to display + * @return string + */ + public function getTextToDisplay() + { + $text = parent::getTextToDisplay(); + if (substr($text, 0, 1) != '*' && substr($text, -1) != '*') { + return '*' . $text . '*'; + } else { + return $text; + } + } + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + $text = str_split($this->getText()); + $barcodeTable = array(); + foreach ($text as $char) { + $barcodeChar = str_split($this->_codingMap[$char]); + $visible = true; + foreach ($barcodeChar as $c) { + /* visible, width, top, length */ + $width = $c ? $this->_barThickWidth : $this->_barThinWidth; + $barcodeTable[] = array((int) $visible, $width, 0, 1); + $visible = ! $visible; + } + $barcodeTable[] = array(0 , $this->_barThinWidth); + } + return $barcodeTable; + } + + /** + * Get barcode checksum + * + * @param string $text + * @return int + */ + public function getChecksum($text) + { + $this->_checkText($text); + $text = str_split($text); + $charset = array_flip(array_keys($this->_codingMap)); + $checksum = 0; + foreach ($text as $character) { + $checksum += $charset[$character]; + } + return array_search(($checksum % 43), $charset); + } +} diff --git a/library/Zend/Barcode/Object/Ean13.php b/library/Zend/Barcode/Object/Ean13.php new file mode 100644 index 00000000..9b636e3e --- /dev/null +++ b/library/Zend/Barcode/Object/Ean13.php @@ -0,0 +1,225 @@ + array( + 0 => "0001101", 1 => "0011001", 2 => "0010011", 3 => "0111101", 4 => "0100011", + 5 => "0110001", 6 => "0101111", 7 => "0111011", 8 => "0110111", 9 => "0001011" + ), + 'B' => array( + 0 => "0100111", 1 => "0110011", 2 => "0011011", 3 => "0100001", 4 => "0011101", + 5 => "0111001", 6 => "0000101", 7 => "0010001", 8 => "0001001", 9 => "0010111" + ), + 'C' => array( + 0 => "1110010", 1 => "1100110", 2 => "1101100", 3 => "1000010", 4 => "1011100", + 5 => "1001110", 6 => "1010000", 7 => "1000100", 8 => "1001000", 9 => "1110100" + )); + + protected $_parities = array( + 0 => array('A','A','A','A','A','A'), + 1 => array('A','A','B','A','B','B'), + 2 => array('A','A','B','B','A','B'), + 3 => array('A','A','B','B','B','A'), + 4 => array('A','B','A','A','B','B'), + 5 => array('A','B','B','A','A','B'), + 6 => array('A','B','B','B','A','A'), + 7 => array('A','B','A','B','A','B'), + 8 => array('A','B','A','B','B','A'), + 9 => array('A','B','B','A','B','A') + ); + + /** + * Default options for Postnet barcode + * @return void + */ + protected function _getDefaultOptions() + { + $this->_barcodeLength = 13; + $this->_mandatoryChecksum = true; + $this->_mandatoryQuietZones = true; + } + + /** + * Width of the barcode (in pixels) + * @return integer + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + $startCharacter = (3 * $this->_barThinWidth) * $this->_factor; + $middleCharacter = (5 * $this->_barThinWidth) * $this->_factor; + $stopCharacter = (3 * $this->_barThinWidth) * $this->_factor; + $encodedData = (7 * $this->_barThinWidth) * $this->_factor * 12; + return $quietZone + $startCharacter + $middleCharacter + $encodedData + $stopCharacter + $quietZone; + } + + /** + * Partial check of interleaved EAN/UPC barcode + * @return void + */ + protected function _checkParams() + {} + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + $barcodeTable = array(); + $height = ($this->_drawText) ? 1.1 : 1; + + // Start character (101) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + + $textTable = str_split($this->getText()); + $parity = $this->_parities[$textTable[0]]; + + // First part + for ($i = 1; $i < 7; $i++) { + $bars = str_split($this->_codingMap[$parity[$i - 1]][$textTable[$i]]); + foreach ($bars as $b) { + $barcodeTable[] = array($b , $this->_barThinWidth , 0 , 1); + } + } + + // Middle character (01010) + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + + // Second part + for ($i = 7; $i < 13; $i++) { + $bars = str_split($this->_codingMap['C'][$textTable[$i]]); + foreach ($bars as $b) { + $barcodeTable[] = array($b , $this->_barThinWidth , 0 , 1); + } + } + + // Stop character (101) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + return $barcodeTable; + } + + /** + * Get barcode checksum + * + * @param string $text + * @return int + */ + public function getChecksum($text) + { + $this->_checkText($text); + $factor = 3; + $checksum = 0; + + for ($i = strlen($text); $i > 0; $i --) { + $checksum += intval($text{$i - 1}) * $factor; + $factor = 4 - $factor; + } + + $checksum = (10 - ($checksum % 10)) % 10; + + return $checksum; + } + + /** + * Partial function to draw text + * @return void + */ + protected function _drawText() + { + if (get_class($this) == 'Zend_Barcode_Object_Ean13') { + $this->_drawEan13Text(); + } else { + parent::_drawText(); + } + } + + protected function _drawEan13Text() + { + if ($this->_drawText) { + $text = $this->getTextToDisplay(); + $characterWidth = (7 * $this->_barThinWidth) * $this->_factor; + $leftPosition = $this->getQuietZone() - $characterWidth; + for ($i = 0; $i < $this->_barcodeLength; $i ++) { + $this->_addText( + $text{$i}, + $this->_fontSize * $this->_factor, + $this->_rotate( + $leftPosition, + (int) $this->_withBorder * 2 + + $this->_factor * ($this->_barHeight + $this->_fontSize) + 1 + ), + $this->_font, + $this->_foreColor, + 'left', + - $this->_orientation + ); + switch ($i) { + case 0: + $factor = 3; + break; + case 6: + $factor = 4; + break; + default: + $factor = 0; + } + $leftPosition = $leftPosition + $characterWidth + ($factor * $this->_barThinWidth * $this->_factor); + } + } + } +} diff --git a/library/Zend/Barcode/Object/Ean2.php b/library/Zend/Barcode/Object/Ean2.php new file mode 100644 index 00000000..df470ce7 --- /dev/null +++ b/library/Zend/Barcode/Object/Ean2.php @@ -0,0 +1,65 @@ + array('A','A'), + 1 => array('A','B'), + 2 => array('B','A'), + 3 => array('B','B') + ); + + /** + * Default options for Ean2 barcode + * @return void + */ + protected function _getDefaultOptions() + { + $this->_barcodeLength = 2; + } + + protected function _getParity($i) + { + $modulo = $this->getText() % 4; + return $this->_parities[$modulo][$i]; + } +} diff --git a/library/Zend/Barcode/Object/Ean5.php b/library/Zend/Barcode/Object/Ean5.php new file mode 100644 index 00000000..7f7ca9c5 --- /dev/null +++ b/library/Zend/Barcode/Object/Ean5.php @@ -0,0 +1,147 @@ + array('B','B','A','A','A'), + 1 => array('B','A','B','A','A'), + 2 => array('B','A','A','B','A'), + 3 => array('B','A','A','A','B'), + 4 => array('A','B','B','A','A'), + 5 => array('A','A','B','B','A'), + 6 => array('A','A','A','B','B'), + 7 => array('A','B','A','B','A'), + 8 => array('A','B','A','A','B'), + 9 => array('A','A','B','A','B') + ); + + /** + * Default options for Ean5 barcode + * @return void + */ + protected function _getDefaultOptions() + { + $this->_barcodeLength = 5; + } + + /** + * Width of the barcode (in pixels) + * @return integer + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + $startCharacter = (5 * $this->_barThinWidth) * $this->_factor; + $middleCharacter = (2 * $this->_barThinWidth) * $this->_factor; + $encodedData = (7 * $this->_barThinWidth) * $this->_factor; + return $quietZone + $startCharacter + ($this->_barcodeLength - 1) * $middleCharacter + $this->_barcodeLength * $encodedData + $quietZone; + } + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + $barcodeTable = array(); + + // Start character (01011) + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , 1); + + $firstCharacter = true; + $textTable = str_split($this->getText()); + + // Characters + for ($i = 0; $i < $this->_barcodeLength; $i++) { + if ($firstCharacter) { + $firstCharacter = false; + } else { + // Intermediate character (01) + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , 1); + } + $bars = str_split($this->_codingMap[$this->_getParity($i)][$textTable[$i]]); + foreach ($bars as $b) { + $barcodeTable[] = array($b , $this->_barThinWidth , 0 , 1); + } + } + + return $barcodeTable; + } + + /** + * Get barcode checksum + * + * @param string $text + * @return int + */ + public function getChecksum($text) + { + $this->_checkText($text); + $checksum = 0; + + for ($i = 0 ; $i < $this->_barcodeLength; $i ++) { + $checksum += intval($text{$i}) * ($i % 2 ? 9 : 3); + } + + return ($checksum % 10); + } + + protected function _getParity($i) + { + $checksum = $this->getChecksum($this->getText()); + return $this->_parities[$checksum][$i]; + } + + /** + * Retrieve text to encode + * @return string + */ + public function getText() + { + return $this->_addLeadingZeros($this->_text); + } +} diff --git a/library/Zend/Barcode/Object/Ean8.php b/library/Zend/Barcode/Object/Ean8.php new file mode 100644 index 00000000..1589d9b1 --- /dev/null +++ b/library/Zend/Barcode/Object/Ean8.php @@ -0,0 +1,175 @@ +_barcodeLength = 8; + $this->_mandatoryChecksum = true; + } + + /** + * Width of the barcode (in pixels) + * @return integer + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + $startCharacter = (3 * $this->_barThinWidth) * $this->_factor; + $middleCharacter = (5 * $this->_barThinWidth) * $this->_factor; + $stopCharacter = (3 * $this->_barThinWidth) * $this->_factor; + $encodedData = (7 * $this->_barThinWidth) * $this->_factor * 8; + return $quietZone + $startCharacter + $middleCharacter + $encodedData + $stopCharacter + $quietZone; + } + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + $barcodeTable = array(); + $height = ($this->_drawText) ? 1.1 : 1; + + // Start character (101) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + + $textTable = str_split($this->getText()); + + // First part + for ($i = 0; $i < 4; $i++) { + $bars = str_split($this->_codingMap['A'][$textTable[$i]]); + foreach ($bars as $b) { + $barcodeTable[] = array($b , $this->_barThinWidth , 0 , 1); + } + } + + // Middle character (01010) + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + + // Second part + for ($i = 4; $i < 8; $i++) { + $bars = str_split($this->_codingMap['C'][$textTable[$i]]); + foreach ($bars as $b) { + $barcodeTable[] = array($b , $this->_barThinWidth , 0 , 1); + } + } + + // Stop character (101) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + return $barcodeTable; + } + + /** + * Partial function to draw text + * @return void + */ + protected function _drawText() + { + if ($this->_drawText) { + $text = $this->getTextToDisplay(); + $characterWidth = (7 * $this->_barThinWidth) * $this->_factor; + $leftPosition = $this->getQuietZone() + (3 * $this->_barThinWidth) * $this->_factor; + for ($i = 0; $i < $this->_barcodeLength; $i ++) { + $this->_addText( + $text{$i}, + $this->_fontSize * $this->_factor, + $this->_rotate( + $leftPosition, + (int) $this->_withBorder * 2 + + $this->_factor * ($this->_barHeight + $this->_fontSize) + 1 + ), + $this->_font, + $this->_foreColor, + 'left', + - $this->_orientation + ); + switch ($i) { + case 3: + $factor = 4; + break; + default: + $factor = 0; + } + $leftPosition = $leftPosition + $characterWidth + ($factor * $this->_barThinWidth * $this->_factor); + } + } + } + + /** + * Particular validation for Ean8 barcode objects + * (to suppress checksum character substitution) + * @param string $value + * @param array $options + */ + protected function _validateText($value, $options = array()) + { + $validator = new Zend_Validate_Barcode(array( + 'adapter' => 'ean8', + 'checksum' => false, + )); + + $value = $this->_addLeadingZeros($value, true); + + if (!$validator->isValid($value)) { + $message = implode("\n", $validator->getMessages()); + + /** + * @see Zend_Barcode_Object_Exception + */ + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception($message); + } + } +} diff --git a/library/Zend/Barcode/Object/Error.php b/library/Zend/Barcode/Object/Error.php new file mode 100644 index 00000000..a9c4c733 --- /dev/null +++ b/library/Zend/Barcode/Object/Error.php @@ -0,0 +1,100 @@ +_instructions = array(); + $this->_addText('ERROR:', 10, array(5 , 18), $this->_font, 0, 'left'); + $this->_addText($this->_text, 10, array(5 , 32), $this->_font, 0, 'left'); + return $this->_instructions; + } + + /** + * For compatibility reason + * @return void + */ + protected function _prepareBarcode() + { + } + + /** + * For compatibility reason + * @return void + */ + protected function _checkParams() + { + } + + /** + * For compatibility reason + * @return void + */ + protected function _calculateBarcodeWidth() + { + } +} diff --git a/library/Zend/Barcode/Object/Exception.php b/library/Zend/Barcode/Object/Exception.php new file mode 100644 index 00000000..dab3153a --- /dev/null +++ b/library/Zend/Barcode/Object/Exception.php @@ -0,0 +1,35 @@ +_barcodeLength = 12; + $this->_mandatoryChecksum = true; + } + + /** + * Retrieve text to display + * @return string + */ + public function getTextToDisplay() + { + return preg_replace('/([0-9]{2})([0-9]{3})([0-9]{3})([0-9]{3})([0-9])/', + '$1.$2 $3.$4 $5', + $this->getText()); + } + + /** + * Check allowed characters + * @param string $value + * @return string + * @throw Zend_Barcode_Object_Exception + */ + public function validateText($value) + { + $this->_validateText($value, array('validator' => $this->getType())); + } + + /** + * Get barcode checksum + * + * @param string $text + * @return int + */ + public function getChecksum($text) + { + $this->_checkText($text); + $checksum = 0; + + for ($i = strlen($text); $i > 0; $i --) { + $checksum += intval($text{$i - 1}) * (($i % 2) ? 4 : 9); + } + + $checksum = (10 - ($checksum % 10)) % 10; + + return $checksum; + } +} diff --git a/library/Zend/Barcode/Object/Itf14.php b/library/Zend/Barcode/Object/Itf14.php new file mode 100644 index 00000000..e88db240 --- /dev/null +++ b/library/Zend/Barcode/Object/Itf14.php @@ -0,0 +1,49 @@ +_barcodeLength = 14; + $this->_mandatoryChecksum = true; + } +} diff --git a/library/Zend/Barcode/Object/Leitcode.php b/library/Zend/Barcode/Object/Leitcode.php new file mode 100644 index 00000000..a94c9044 --- /dev/null +++ b/library/Zend/Barcode/Object/Leitcode.php @@ -0,0 +1,64 @@ +_barcodeLength = 14; + $this->_mandatoryChecksum = true; + } + + /** + * Retrieve text to display + * @return string + */ + public function getTextToDisplay() + { + return preg_replace('/([0-9]{5})([0-9]{3})([0-9]{3})([0-9]{2})([0-9])/', + '$1.$2.$3.$4 $5', + $this->getText()); + } +} diff --git a/library/Zend/Barcode/Object/ObjectAbstract.php b/library/Zend/Barcode/Object/ObjectAbstract.php new file mode 100644 index 00000000..b79c711e --- /dev/null +++ b/library/Zend/Barcode/Object/ObjectAbstract.php @@ -0,0 +1,1317 @@ +_getDefaultOptions(); + if (self::$_staticFont !== null) { + $this->_font = self::$_staticFont; + } + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } + if (is_array($options)) { + $this->setOptions($options); + } + $this->_type = strtolower(substr(get_class($this), strlen($this->_barcodeNamespace) + 1)); + if ($this->_mandatoryChecksum) { + $this->_withChecksum = true; + $this->_withChecksumInText = true; + } + } + + /** + * Set default options for particular object + * @return void + */ + protected function _getDefaultOptions() + { + } + + /** + * Set barcode state from options array + * @param array $options + * @return Zend_Barcode_Object + */ + public function setOptions($options) + { + foreach ($options as $key => $value) { + $method = 'set' . $key; + if (method_exists($this, $method)) { + $this->$method($value); + } + } + return $this; + } + + /** + * Set barcode state from config object + * @param Zend_Config $config + * @return Zend_Barcode_Object + */ + public function setConfig(Zend_Config $config) + { + return $this->setOptions($config->toArray()); + } + + /** + * Set barcode namespace for autoloading + * + * @param string $namespace + * @return Zend_Barcode_Object + */ + public function setBarcodeNamespace($namespace) + { + $this->_barcodeNamespace = $namespace; + return $this; + } + + /** + * Retrieve barcode namespace + * + * @return string + */ + public function getBarcodeNamespace() + { + return $this->_barcodeNamespace; + } + + /** + * Retrieve type of barcode + * @return string + */ + public function getType() + { + return $this->_type; + } + + /** + * Set height of the barcode bar + * @param integer $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setBarHeight($value) + { + if (intval($value) <= 0) { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception( + 'Bar height must be greater than 0' + ); + } + $this->_barHeight = intval($value); + return $this; + } + + /** + * Get height of the barcode bar + * @return integer + */ + public function getBarHeight() + { + return $this->_barHeight; + } + + /** + * Set thickness of thin bar + * @param integer $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setBarThinWidth($value) + { + if (intval($value) <= 0) { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception( + 'Bar width must be greater than 0' + ); + } + $this->_barThinWidth = intval($value); + return $this; + } + + /** + * Get thickness of thin bar + * @return integer + */ + public function getBarThinWidth() + { + return $this->_barThinWidth; + } + + /** + * Set thickness of thick bar + * @param integer $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setBarThickWidth($value) + { + if (intval($value) <= 0) { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception( + 'Bar width must be greater than 0' + ); + } + $this->_barThickWidth = intval($value); + return $this; + } + + /** + * Get thickness of thick bar + * @return integer + */ + public function getBarThickWidth() + { + return $this->_barThickWidth; + } + + /** + * Set factor applying to + * thinBarWidth - thickBarWidth - barHeight - fontSize + * @param float $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setFactor($value) + { + if (floatval($value) <= 0) { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception( + 'Factor must be greater than 0' + ); + } + $this->_factor = floatval($value); + return $this; + } + + /** + * Get factor applying to + * thinBarWidth - thickBarWidth - barHeight - fontSize + * @return integer + */ + public function getFactor() + { + return $this->_factor; + } + + /** + * Set color of the barcode and text + * @param string $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setForeColor($value) + { + if (preg_match('`\#[0-9A-F]{6}`', $value)) { + $this->_foreColor = hexdec($value); + } elseif (is_numeric($value) && $value >= 0 && $value <= 16777125) { + $this->_foreColor = intval($value); + } else { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception( + 'Text color must be set as #[0-9A-F]{6}' + ); + } + return $this; + } + + /** + * Retrieve color of the barcode and text + * @return unknown + */ + public function getForeColor() + { + return $this->_foreColor; + } + + /** + * Set the color of the background + * @param integer $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setBackgroundColor($value) + { + if (preg_match('`\#[0-9A-F]{6}`', $value)) { + $this->_backgroundColor = hexdec($value); + } elseif (is_numeric($value) && $value >= 0 && $value <= 16777125) { + $this->_backgroundColor = intval($value); + } else { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception( + 'Background color must be set as #[0-9A-F]{6}' + ); + } + return $this; + } + + /** + * Retrieve background color of the image + * @return integer + */ + public function getBackgroundColor() + { + return $this->_backgroundColor; + } + + /** + * Activate/deactivate drawing of the bar + * @param boolean $value + * @return Zend_Barcode_Object + */ + public function setWithBorder($value) + { + $this->_withBorder = (bool) $value; + return $this; + } + + /** + * Retrieve if border are draw or not + * @return boolean + */ + public function getWithBorder() + { + return $this->_withBorder; + } + + /** + * Activate/deactivate drawing of the quiet zones + * @param boolean $value + * @return Zend_Barcode_Object + */ + public function setWithQuietZones($value) + { + $this->_withQuietZones = (bool) $value; + return $this; + } + + /** + * Retrieve if quiet zones are draw or not + * @return boolean + */ + public function getWithQuietZones() + { + return $this->_withQuietZones; + } + + /** + * Allow fast inversion of font/bars color and background color + * @return Zend_Barcode_Object + */ + public function setReverseColor() + { + $tmp = $this->_foreColor; + $this->_foreColor = $this->_backgroundColor; + $this->_backgroundColor = $tmp; + return $this; + } + + /** + * Set orientation of barcode and text + * @param float $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setOrientation($value) + { + $this->_orientation = floatval($value) - floor(floatval($value) / 360) * 360; + return $this; + } + + /** + * Retrieve orientation of barcode and text + * @return float + */ + public function getOrientation() + { + return $this->_orientation; + } + + /** + * Set text to encode + * @param string $value + * @return Zend_Barcode_Object + */ + public function setText($value) + { + $this->_text = trim($value); + return $this; + } + + /** + * Retrieve text to encode + * @return string + */ + public function getText() + { + $text = $this->_text; + if ($this->_withChecksum) { + $text .= $this->getChecksum($this->_text); + } + return $this->_addLeadingZeros($text); + } + + /** + * Automatically add leading zeros if barcode length is fixed + * @param string $text + * @param boolean $withoutChecksum + */ + protected function _addLeadingZeros($text, $withoutChecksum = false) + { + if ($this->_barcodeLength && $this->_addLeadingZeros) { + $omitChecksum = (int) ($this->_withChecksum && $withoutChecksum); + if (is_int($this->_barcodeLength)) { + $length = $this->_barcodeLength - $omitChecksum; + if (strlen($text) < $length) { + $text = str_repeat('0', $length - strlen($text)) . $text; + } + } else { + if ($this->_barcodeLength == 'even') { + $text = ((strlen($text) - $omitChecksum) % 2 ? '0' . $text : $text); + } + } + } + return $text; + } + + /** + * Retrieve text to encode + * @return string + */ + public function getRawText() + { + return $this->_text; + } + + /** + * Retrieve text to display + * @return string + */ + public function getTextToDisplay() + { + if ($this->_withChecksumInText) { + return $this->getText(); + } else { + return $this->_addLeadingZeros($this->_text, true); + } + } + + /** + * Activate/deactivate drawing of text to encode + * @param boolean $value + * @return Zend_Barcode_Object + */ + public function setDrawText($value) + { + $this->_drawText = (bool) $value; + return $this; + } + + /** + * Retrieve if drawing of text to encode is enabled + * @return boolean + */ + public function getDrawText() + { + return $this->_drawText; + } + + /** + * Activate/deactivate the adjustment of the position + * of the characters to the position of the bars + * @param boolean $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setStretchText($value) + { + $this->_stretchText = (bool) $value; + return $this; + } + + /** + * Retrieve if the adjustment of the position of the characters + * to the position of the bars is enabled + * @return boolean + */ + public function getStretchText() + { + return $this->_stretchText; + } + + /** + * Activate/deactivate the automatic generation + * of the checksum character + * added to the barcode text + * @param boolean $value + * @return Zend_Barcode_Object + */ + public function setWithChecksum($value) + { + if (!$this->_mandatoryChecksum) { + $this->_withChecksum = (bool) $value; + } + return $this; + } + + /** + * Retrieve if the checksum character is automatically + * added to the barcode text + * @return boolean + */ + public function getWithChecksum() + { + return $this->_withChecksum; + } + + /** + * Activate/deactivate the automatic generation + * of the checksum character + * added to the barcode text + * @param boolean $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setWithChecksumInText($value) + { + if (!$this->_mandatoryChecksum) { + $this->_withChecksumInText = (bool) $value; + } + return $this; + } + + /** + * Retrieve if the checksum character is automatically + * added to the barcode text + * @return boolean + */ + public function getWithChecksumInText() + { + return $this->_withChecksumInText; + } + + /** + * Set the font for all instances of barcode + * @param string $font + * @return void + */ + public static function setBarcodeFont($font) + { + if (is_string($font) || (is_int($font) && $font >= 1 && $font <= 5)) { + self::$_staticFont = $font; + } + } + + /** + * Set the font: + * - if integer between 1 and 5, use gd built-in fonts + * - if string, $value is assumed to be the path to a TTF font + * @param integer|string $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setFont($value) + { + if (is_int($value) && $value >= 1 && $value <= 5) { + if (!extension_loaded('gd')) { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception( + 'GD extension is required to use numeric font' + ); + } + + // Case of numeric font with GD + $this->_font = $value; + + // In this case font size is given by: + $this->_fontSize = imagefontheight($value); + } elseif (is_string($value)) { + $this->_font = $value; + } else { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception(sprintf( + 'Invalid font "%s" provided to setFont()', + $value + )); + } + return $this; + } + + /** + * Retrieve the font + * @return integer|string + */ + public function getFont() + { + return $this->_font; + } + + /** + * Set the size of the font in case of TTF + * @param float $value + * @return Zend_Barcode_Object + * @throw Zend_Barcode_Object_Exception + */ + public function setFontSize($value) + { + if (is_numeric($this->_font)) { + // Case of numeric font with GD + return $this; + } + + if (!is_numeric($value)) { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception( + 'Font size must be a numeric value' + ); + } + + $this->_fontSize = $value; + return $this; + } + + /** + * Retrieve the size of the font in case of TTF + * @return float + */ + public function getFontSize() + { + return $this->_fontSize; + } + + /** + * Quiet zone before first bar + * and after the last bar + * @return integer + */ + public function getQuietZone() + { + if ($this->_withQuietZones || $this->_mandatoryQuietZones) { + return 10 * $this->_barThinWidth * $this->_factor; + } else { + return 0; + } + } + + /** + * Add an instruction in the array of instructions + * @param array $instruction + */ + protected function _addInstruction(array $instruction) + { + $this->_instructions[] = $instruction; + } + + /** + * Retrieve the set of drawing instructions + * @return array + */ + public function getInstructions() + { + return $this->_instructions; + } + + /** + * Add a polygon drawing instruction in the set of instructions + * @param array $points + * @param integer $color + * @param boolean $filled + */ + protected function _addPolygon(array $points, $color = null, $filled = true) + { + if ($color === null) { + $color = $this->_foreColor; + } + $this->_addInstruction(array( + 'type' => 'polygon', + 'points' => $points, + 'color' => $color, + 'filled' => $filled, + )); + } + + /** + * Add a text drawing instruction in the set of instructions + * @param string $text + * @param float $size + * @param array $position + * @param string $font + * @param integer $color + * @param string $alignment + * @param float $orientation + */ + protected function _addText( + $text, + $size, + $position, + $font, + $color, + $alignment = 'center', + $orientation = 0 + ) { + if ($color === null) { + $color = $this->_foreColor; + } + $this->_addInstruction(array( + 'type' => 'text', + 'text' => $text, + 'size' => $size, + 'position' => $position, + 'font' => $font, + 'color' => $color, + 'alignment' => $alignment, + 'orientation' => $orientation, + )); + } + + /** + * Checking of parameters after all settings + * @return void + */ + public function checkParams() + { + $this->_checkText(); + $this->_checkFontAndOrientation(); + $this->_checkParams(); + return true; + } + + /** + * Check if a text is really provided to barcode + * @return void + * @throw Zend_Barcode_Object_Exception + */ + protected function _checkText($value = null) + { + if ($value === null) { + $value = $this->_text; + } + if (!strlen($value)) { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception( + 'A text must be provide to Barcode before drawing' + ); + } + $this->validateText($value); + } + + /** + * Check the ratio between the thick and the thin bar + * @param integer $min + * @param integer $max + * @return void + * @throw Zend_Barcode_Object_Exception + */ + protected function _checkRatio($min = 2, $max = 3) + { + $ratio = $this->_barThickWidth / $this->_barThinWidth; + if (!($ratio >= $min && $ratio <= $max)) { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception(sprintf( + 'Ratio thick/thin bar must be between %0.1f and %0.1f (actual %0.3f)', + $min, + $max, + $ratio + )); + } + } + + /** + * Drawing with an angle is just allow TTF font + * @return void + * @throw Zend_Barcode_Object_Exception + */ + protected function _checkFontAndOrientation() + { + if (is_numeric($this->_font) && $this->_orientation != 0) { + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception( + 'Only drawing with TTF font allow orientation of the barcode.' + ); + } + } + + /** + * Width of the result image + * (before any rotation) + * @return integer + */ + protected function _calculateWidth() + { + return (int) $this->_withBorder + + $this->_calculateBarcodeWidth() + + (int) $this->_withBorder; + } + + /** + * Calculate the width of the barcode + * @return integer + */ + abstract protected function _calculateBarcodeWidth(); + + /** + * Height of the result object + * @return integer + */ + protected function _calculateHeight() + { + return (int) $this->_withBorder * 2 + + $this->_calculateBarcodeHeight() + + (int) $this->_withBorder * 2; + } + + /** + * Height of the barcode + * @return integer + */ + protected function _calculateBarcodeHeight() + { + $textHeight = 0; + $extraHeight = 0; + if ($this->_drawText) { + $textHeight += $this->_fontSize; + $extraHeight = 2; + } + return ($this->_barHeight + $textHeight) * $this->_factor + $extraHeight; + } + + /** + * Get height of the result object + * @return integer + */ + public function getHeight($recalculate = false) + { + if ($this->_height === null || $recalculate) { + $this->_height = + abs($this->_calculateHeight() * cos($this->_orientation / 180 * pi())) + + abs($this->_calculateWidth() * sin($this->_orientation / 180 * pi())); + } + return $this->_height; + } + + /** + * Get width of the result object + * @return integer + */ + public function getWidth($recalculate = false) + { + if ($this->_width === null || $recalculate) { + $this->_width = + abs($this->_calculateWidth() * cos($this->_orientation / 180 * pi())) + + abs($this->_calculateHeight() * sin($this->_orientation / 180 * pi())); + } + return $this->_width; + } + + /** + * Calculate the offset from the left of the object + * if an orientation is activated + * @param boolean $recalculate + * @return float + */ + public function getOffsetLeft($recalculate = false) + { + if ($this->_offsetLeft === null || $recalculate) { + $this->_offsetLeft = - min(array( + 0 * cos( + $this->_orientation / 180 * pi()) - 0 * sin( + $this->_orientation / 180 * pi()), + 0 * cos( + $this->_orientation / 180 * pi()) - $this->_calculateBarcodeHeight() * sin( + $this->_orientation / 180 * pi()), + $this->_calculateBarcodeWidth() * cos( + $this->_orientation / 180 * pi()) - $this->_calculateBarcodeHeight() * sin( + $this->_orientation / 180 * pi()), + $this->_calculateBarcodeWidth() * cos( + $this->_orientation / 180 * pi()) - 0 * sin( + $this->_orientation / 180 * pi()), + )); + } + return $this->_offsetLeft; + } + + /** + * Calculate the offset from the top of the object + * if an orientation is activated + * @param boolean $recalculate + * @return float + */ + public function getOffsetTop($recalculate = false) + { + if ($this->_offsetTop === null || $recalculate) { + $this->_offsetTop = - min(array( + 0 * cos( + $this->_orientation / 180 * pi()) + 0 * sin( + $this->_orientation / 180 * pi()), + $this->_calculateBarcodeHeight() * cos( + $this->_orientation / 180 * pi()) + 0 * sin( + $this->_orientation / 180 * pi()), + $this->_calculateBarcodeHeight() * cos( + $this->_orientation / 180 * pi()) + $this->_calculateBarcodeWidth() * sin( + $this->_orientation / 180 * pi()), + 0 * cos( + $this->_orientation / 180 * pi()) + $this->_calculateBarcodeWidth() * sin( + $this->_orientation / 180 * pi()), + )); + } + return $this->_offsetTop; + } + + /** + * Apply rotation on a point in X/Y dimensions + * @param float $x1 x-position before rotation + * @param float $y1 y-position before rotation + * @return array Array of two elements corresponding to the new XY point + */ + protected function _rotate($x1, $y1) + { + $x2 = $x1 * cos($this->_orientation / 180 * pi()) + - $y1 * sin($this->_orientation / 180 * pi()) + + $this->getOffsetLeft(); + $y2 = $y1 * cos($this->_orientation / 180 * pi()) + + $x1 * sin($this->_orientation / 180 * pi()) + + $this->getOffsetTop(); + return array(intval($x2) , intval($y2)); + } + + /** + * Complete drawing of the barcode + * @return array Table of instructions + */ + public function draw() + { + $this->checkParams(); + $this->_drawBarcode(); + $this->_drawBorder(); + $this->_drawText(); + return $this->getInstructions(); + } + + /** + * Draw the barcode + * @return void + */ + protected function _drawBarcode() + { + $barcodeTable = $this->_prepareBarcode(); + + $this->_preDrawBarcode(); + + $xpos = (int) $this->_withBorder; + $ypos = (int) $this->_withBorder; + + $point1 = $this->_rotate(0, 0); + $point2 = $this->_rotate(0, $this->_calculateHeight() - 1); + $point3 = $this->_rotate( + $this->_calculateWidth() - 1, + $this->_calculateHeight() - 1 + ); + $point4 = $this->_rotate($this->_calculateWidth() - 1, 0); + + $this->_addPolygon(array( + $point1, + $point2, + $point3, + $point4 + ), $this->_backgroundColor); + + $xpos += $this->getQuietZone(); + $barLength = $this->_barHeight * $this->_factor; + + foreach ($barcodeTable as $bar) { + $width = $bar[1] * $this->_factor; + if ($bar[0]) { + $point1 = $this->_rotate($xpos, $ypos + $bar[2] * $barLength); + $point2 = $this->_rotate($xpos, $ypos + $bar[3] * $barLength); + $point3 = $this->_rotate( + $xpos + $width - 1, + $ypos + $bar[3] * $barLength + ); + $point4 = $this->_rotate( + $xpos + $width - 1, + $ypos + $bar[2] * $barLength + ); + $this->_addPolygon(array( + $point1, + $point2, + $point3, + $point4, + )); + } + $xpos += $width; + } + + $this->_postDrawBarcode(); + } + + /** + * Partial function to draw border + * @return void + */ + protected function _drawBorder() + { + if ($this->_withBorder) { + $point1 = $this->_rotate(0, 0); + $point2 = $this->_rotate($this->_calculateWidth() - 1, 0); + $point3 = $this->_rotate( + $this->_calculateWidth() - 1, + $this->_calculateHeight() - 1 + ); + $point4 = $this->_rotate(0, $this->_calculateHeight() - 1); + $this->_addPolygon(array( + $point1, + $point2, + $point3, + $point4, + $point1, + ), $this->_foreColor, false); + } + } + + /** + * Partial function to draw text + * @return void + */ + protected function _drawText() + { + if ($this->_drawText) { + $text = $this->getTextToDisplay(); + if ($this->_stretchText) { + $textLength = strlen($text); + $space = ($this->_calculateWidth() - 2 * $this->getQuietZone()) / $textLength; + for ($i = 0; $i < $textLength; $i ++) { + $leftPosition = $this->getQuietZone() + $space * ($i + 0.5); + $this->_addText( + $text{$i}, + $this->_fontSize * $this->_factor, + $this->_rotate( + $leftPosition, + (int) $this->_withBorder * 2 + + $this->_factor * ($this->_barHeight + $this->_fontSize) + 1 + ), + $this->_font, + $this->_foreColor, + 'center', + - $this->_orientation + ); + } + } else { + $this->_addText( + $text, + $this->_fontSize * $this->_factor, + $this->_rotate( + $this->_calculateWidth() / 2, + (int) $this->_withBorder * 2 + + $this->_factor * ($this->_barHeight + $this->_fontSize) + 1 + ), + $this->_font, + $this->_foreColor, + 'center', + - $this->_orientation + ); + } + } + } + + /** + * Check for invalid characters + * @param string $value Text to be ckecked + * @return void + */ + public function validateText($value) + { + $this->_validateText($value); + } + + /** + * Standard validation for most of barcode objects + * @param string $value + * @param array $options + */ + protected function _validateText($value, $options = array()) + { + $validatorName = (isset($options['validator'])) ? $options['validator'] : $this->getType(); + + $validator = new Zend_Validate_Barcode(array( + 'adapter' => $validatorName, + 'checksum' => false, + )); + + $checksumCharacter = ''; + $withChecksum = false; + if ($this->_mandatoryChecksum) { + $checksumCharacter = $this->_substituteChecksumCharacter; + $withChecksum = true; + } + + $value = $this->_addLeadingZeros($value, $withChecksum) . $checksumCharacter; + + if (!$validator->isValid($value)) { + $message = implode("\n", $validator->getMessages()); + + /** + * @see Zend_Barcode_Object_Exception + */ + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception($message); + } + } + + /** + * Each child must prepare the barcode and return + * a table like array( + * 0 => array( + * 0 => int (visible(black) or not(white)) + * 1 => int (width of the bar) + * 2 => float (0->1 position from the top of the beginning of the bar in %) + * 3 => float (0->1 position from the top of the end of the bar in %) + * ), + * 1 => ... + * ) + * + * @return array + */ + abstract protected function _prepareBarcode(); + + /** + * Checking of parameters after all settings + * + * @return void + */ + abstract protected function _checkParams(); + + /** + * Allow each child to draw something else + * + * @return void + */ + protected function _preDrawBarcode() + { + } + + /** + * Allow each child to draw something else + * (ex: bearer bars in interleaved 2 of 5 code) + * + * @return void + */ + protected function _postDrawBarcode() + { + } +} diff --git a/library/Zend/Barcode/Object/Planet.php b/library/Zend/Barcode/Object/Planet.php new file mode 100644 index 00000000..20952e50 --- /dev/null +++ b/library/Zend/Barcode/Object/Planet.php @@ -0,0 +1,62 @@ + "00111", + 1 => "11100", + 2 => "11010", + 3 => "11001", + 4 => "10110", + 5 => "10101", + 6 => "10011", + 7 => "01110", + 8 => "01101", + 9 => "01011" + ); +} \ No newline at end of file diff --git a/library/Zend/Barcode/Object/Postnet.php b/library/Zend/Barcode/Object/Postnet.php new file mode 100644 index 00000000..5613dc67 --- /dev/null +++ b/library/Zend/Barcode/Object/Postnet.php @@ -0,0 +1,136 @@ + "11000", + 1 => "00011", + 2 => "00101", + 3 => "00110", + 4 => "01001", + 5 => "01010", + 6 => "01100", + 7 => "10001", + 8 => "10010", + 9 => "10100" + ); + + /** + * Default options for Postnet barcode + * @return void + */ + protected function _getDefaultOptions() + { + $this->_barThinWidth = 2; + $this->_barHeight = 20; + $this->_drawText = false; + $this->_stretchText = true; + $this->_mandatoryChecksum = true; + } + + /** + * Width of the barcode (in pixels) + * @return integer + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + $startCharacter = (2 * $this->_barThinWidth) * $this->_factor; + $stopCharacter = (1 * $this->_barThinWidth) * $this->_factor; + $encodedData = (10 * $this->_barThinWidth) * $this->_factor * strlen($this->getText()); + return $quietZone + $startCharacter + $encodedData + $stopCharacter + $quietZone; + } + + /** + * Partial check of interleaved Postnet barcode + * @return void + */ + protected function _checkParams() + {} + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + $barcodeTable = array(); + + // Start character (1) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , 1); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + + // Text to encode + $textTable = str_split($this->getText()); + foreach ($textTable as $char) { + $bars = str_split($this->_codingMap[$char]); + foreach ($bars as $b) { + $barcodeTable[] = array(1 , $this->_barThinWidth , 0.5 - $b * 0.5 , 1); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + } + } + + // Stop character (1) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , 1); + return $barcodeTable; + } + + /** + * Get barcode checksum + * + * @param string $text + * @return int + */ + public function getChecksum($text) + { + $this->_checkText($text); + $sum = array_sum(str_split($text)); + $checksum = (10 - ($sum % 10)) % 10; + return $checksum; + } +} diff --git a/library/Zend/Barcode/Object/Royalmail.php b/library/Zend/Barcode/Object/Royalmail.php new file mode 100644 index 00000000..534a1d15 --- /dev/null +++ b/library/Zend/Barcode/Object/Royalmail.php @@ -0,0 +1,163 @@ + '3300', '1' => '3210', '2' => '3201', '3' => '2310', '4' => '2301', '5' => '2211', + '6' => '3120', '7' => '3030', '8' => '3021', '9' => '2130', 'A' => '2121', 'B' => '2031', + 'C' => '3102', 'D' => '3012', 'E' => '3003', 'F' => '2112', 'G' => '2103', 'H' => '2013', + 'I' => '1320', 'J' => '1230', 'K' => '1221', 'L' => '0330', 'M' => '0321', 'N' => '0231', + 'O' => '1302', 'P' => '1212', 'Q' => '1203', 'R' => '0312', 'S' => '0303', 'T' => '0213', + 'U' => '1122', 'V' => '1032', 'W' => '1023', 'X' => '0132', 'Y' => '0123', 'Z' => '0033' + ); + + protected $_rows = array( + '0' => 1, '1' => 1, '2' => 1, '3' => 1, '4' => 1, '5' => 1, + '6' => 2, '7' => 2, '8' => 2, '9' => 2, 'A' => 2, 'B' => 2, + 'C' => 3, 'D' => 3, 'E' => 3, 'F' => 3, 'G' => 3, 'H' => 3, + 'I' => 4, 'J' => 4, 'K' => 4, 'L' => 4, 'M' => 4, 'N' => 4, + 'O' => 5, 'P' => 5, 'Q' => 5, 'R' => 5, 'S' => 5, 'T' => 5, + 'U' => 0, 'V' => 0, 'W' => 0, 'X' => 0, 'Y' => 0, 'Z' => 0, + ); + + protected $_columns = array( + '0' => 1, '1' => 2, '2' => 3, '3' => 4, '4' => 5, '5' => 0, + '6' => 1, '7' => 2, '8' => 3, '9' => 4, 'A' => 5, 'B' => 0, + 'C' => 1, 'D' => 2, 'E' => 3, 'F' => 4, 'G' => 5, 'H' => 0, + 'I' => 1, 'J' => 2, 'K' => 3, 'L' => 4, 'M' => 5, 'N' => 0, + 'O' => 1, 'P' => 2, 'Q' => 3, 'R' => 4, 'S' => 5, 'T' => 0, + 'U' => 1, 'V' => 2, 'W' => 3, 'X' => 4, 'Y' => 5, 'Z' => 0, + ); + + /** + * Default options for Postnet barcode + * @return void + */ + protected function _getDefaultOptions() + { + $this->_barThinWidth = 2; + $this->_barHeight = 20; + $this->_drawText = false; + $this->_stretchText = true; + $this->_mandatoryChecksum = true; + } + + /** + * Width of the barcode (in pixels) + * @return integer + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + $startCharacter = (2 * $this->_barThinWidth) * $this->_factor; + $stopCharacter = (1 * $this->_barThinWidth) * $this->_factor; + $encodedData = (8 * $this->_barThinWidth) * $this->_factor * strlen($this->getText()); + return $quietZone + $startCharacter + $encodedData + $stopCharacter + $quietZone; + } + + /** + * Partial check of interleaved Postnet barcode + * @return void + */ + protected function _checkParams() + {} + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + $barcodeTable = array(); + + // Start character (1) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , 5/8); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + + // Text to encode + $textTable = str_split($this->getText()); + foreach ($textTable as $char) { + $bars = str_split($this->_codingMap[$char]); + foreach ($bars as $b) { + $barcodeTable[] = array(1 , $this->_barThinWidth , ($b > 1 ? 3/8 : 0) , ($b % 2 ? 5/8 : 1)); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , 1); + } + } + + // Stop character (1) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , 1); + return $barcodeTable; + } + + /** + * Get barcode checksum + * + * @param string $text + * @return int + */ + public function getChecksum($text) + { + $this->_checkText($text); + $values = str_split($text); + $rowvalue = 0; + $colvalue = 0; + foreach($values as $row) { + $rowvalue += $this->_rows[$row]; + $colvalue += $this->_columns[$row]; + } + + $rowvalue %= 6; + $colvalue %= 6; + + $rowchkvalue = array_keys($this->_rows, $rowvalue); + $colchkvalue = array_keys($this->_columns, $colvalue); + return current(array_intersect($rowchkvalue, $colchkvalue)); + } +} diff --git a/library/Zend/Barcode/Object/Upca.php b/library/Zend/Barcode/Object/Upca.php new file mode 100644 index 00000000..9f821443 --- /dev/null +++ b/library/Zend/Barcode/Object/Upca.php @@ -0,0 +1,172 @@ +_barcodeLength = 12; + $this->_mandatoryChecksum = true; + $this->_mandatoryQuietZones = true; + } + + /** + * Width of the barcode (in pixels) + * @return integer + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + $startCharacter = (3 * $this->_barThinWidth) * $this->_factor; + $middleCharacter = (5 * $this->_barThinWidth) * $this->_factor; + $stopCharacter = (3 * $this->_barThinWidth) * $this->_factor; + $encodedData = (7 * $this->_barThinWidth) * $this->_factor * 12; + return $quietZone + $startCharacter + $middleCharacter + $encodedData + $stopCharacter + $quietZone; + } + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + $barcodeTable = array(); + $height = ($this->_drawText) ? 1.1 : 1; + + // Start character (101) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + + $textTable = str_split($this->getText()); + + // First character + $bars = str_split($this->_codingMap['A'][$textTable[0]]); + foreach ($bars as $b) { + $barcodeTable[] = array($b , $this->_barThinWidth , 0 , $height); + } + + // First part + for ($i = 1; $i < 6; $i++) { + $bars = str_split($this->_codingMap['A'][$textTable[$i]]); + foreach ($bars as $b) { + $barcodeTable[] = array($b , $this->_barThinWidth , 0 , 1); + } + } + + // Middle character (01010) + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + + // Second part + for ($i = 6; $i < 11; $i++) { + $bars = str_split($this->_codingMap['C'][$textTable[$i]]); + foreach ($bars as $b) { + $barcodeTable[] = array($b , $this->_barThinWidth , 0 , 1); + } + } + + // Last character + $bars = str_split($this->_codingMap['C'][$textTable[11]]); + foreach ($bars as $b) { + $barcodeTable[] = array($b , $this->_barThinWidth , 0 , $height); + } + + // Stop character (101) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + return $barcodeTable; + } + + /** + * Partial function to draw text + * @return void + */ + protected function _drawText() + { + if ($this->_drawText) { + $text = $this->getTextToDisplay(); + $characterWidth = (7 * $this->_barThinWidth) * $this->_factor; + $leftPosition = $this->getQuietZone() - $characterWidth; + for ($i = 0; $i < $this->_barcodeLength; $i ++) { + $fontSize = $this->_fontSize; + if ($i == 0 || $i == 11) { + $fontSize *= 0.8; + } + $this->_addText( + $text{$i}, + $fontSize * $this->_factor, + $this->_rotate( + $leftPosition, + (int) $this->_withBorder * 2 + + $this->_factor * ($this->_barHeight + $fontSize) + 1 + ), + $this->_font, + $this->_foreColor, + 'left', + - $this->_orientation + ); + switch ($i) { + case 0: + $factor = 10; + break; + case 5: + $factor = 4; + break; + case 10: + $factor = 11; + break; + default: + $factor = 0; + } + $leftPosition = $leftPosition + $characterWidth + ($factor * $this->_barThinWidth * $this->_factor); + } + } + } +} diff --git a/library/Zend/Barcode/Object/Upce.php b/library/Zend/Barcode/Object/Upce.php new file mode 100644 index 00000000..59f799f4 --- /dev/null +++ b/library/Zend/Barcode/Object/Upce.php @@ -0,0 +1,228 @@ + array( + 0 => array('B','B','B','A','A','A'), + 1 => array('B','B','A','B','A','A'), + 2 => array('B','B','A','A','B','A'), + 3 => array('B','B','A','A','A','B'), + 4 => array('B','A','B','B','A','A'), + 5 => array('B','A','A','B','B','A'), + 6 => array('B','A','A','A','B','B'), + 7 => array('B','A','B','A','B','A'), + 8 => array('B','A','B','A','A','B'), + 9 => array('B','A','A','B','A','B')), + 1 => array( + 0 => array('A','A','A','B','B','B'), + 1 => array('A','A','B','A','B','B'), + 2 => array('A','A','B','B','A','B'), + 3 => array('A','A','B','B','B','A'), + 4 => array('A','B','A','A','B','B'), + 5 => array('A','B','B','A','A','B'), + 6 => array('A','B','B','B','A','A'), + 7 => array('A','B','A','B','A','B'), + 8 => array('A','B','A','B','B','A'), + 9 => array('A','B','B','A','B','A')) + ); + + /** + * Default options for Postnet barcode + * @return void + */ + protected function _getDefaultOptions() + { + $this->_barcodeLength = 8; + $this->_mandatoryChecksum = true; + $this->_mandatoryQuietZones = true; + } + + /** + * Retrieve text to encode + * @return string + */ + public function getText() + { + $text = parent::getText(); + if ($text{0} != 1) { + $text{0} = 0; + } + return $text; + } + + /** + * Width of the barcode (in pixels) + * @return integer + */ + protected function _calculateBarcodeWidth() + { + $quietZone = $this->getQuietZone(); + $startCharacter = (3 * $this->_barThinWidth) * $this->_factor; + $stopCharacter = (6 * $this->_barThinWidth) * $this->_factor; + $encodedData = (7 * $this->_barThinWidth) * $this->_factor * 6; + return $quietZone + $startCharacter + $encodedData + $stopCharacter + $quietZone; + } + + /** + * Prepare array to draw barcode + * @return array + */ + protected function _prepareBarcode() + { + $barcodeTable = array(); + $height = ($this->_drawText) ? 1.1 : 1; + + // Start character (101) + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + + $textTable = str_split($this->getText()); + $system = 0; + if ($textTable[0] == 1) { + $system = 1; + } + $checksum = $textTable[7]; + $parity = $this->_parities[$system][$checksum]; + + for ($i = 1; $i < 7; $i++) { + $bars = str_split($this->_codingMap[$parity[$i - 1]][$textTable[$i]]); + foreach ($bars as $b) { + $barcodeTable[] = array($b , $this->_barThinWidth , 0 , 1); + } + } + + // Stop character (10101) + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(0 , $this->_barThinWidth , 0 , $height); + $barcodeTable[] = array(1 , $this->_barThinWidth , 0 , $height); + return $barcodeTable; + } + + /** + * Partial function to draw text + * @return void + */ + protected function _drawText() + { + if ($this->_drawText) { + $text = $this->getTextToDisplay(); + $characterWidth = (7 * $this->_barThinWidth) * $this->_factor; + $leftPosition = $this->getQuietZone() - $characterWidth; + for ($i = 0; $i < $this->_barcodeLength; $i ++) { + $fontSize = $this->_fontSize; + if ($i == 0 || $i == 7) { + $fontSize *= 0.8; + } + $this->_addText( + $text{$i}, + $fontSize * $this->_factor, + $this->_rotate( + $leftPosition, + (int) $this->_withBorder * 2 + + $this->_factor * ($this->_barHeight + $fontSize) + 1 + ), + $this->_font, + $this->_foreColor, + 'left', + - $this->_orientation + ); + switch ($i) { + case 0: + $factor = 3; + break; + case 6: + $factor = 5; + break; + default: + $factor = 0; + } + $leftPosition = $leftPosition + $characterWidth + ($factor * $this->_barThinWidth * $this->_factor); + } + } + } + + /** + * Particular validation for Upce barcode objects + * (to suppress checksum character substitution) + * @param string $value + * @param array $options + */ + protected function _validateText($value, $options = array()) + { + $validator = new Zend_Validate_Barcode(array( + 'adapter' => 'upce', + 'checksum' => false, + )); + + $value = $this->_addLeadingZeros($value, true); + + if (!$validator->isValid($value)) { + $message = implode("\n", $validator->getMessages()); + + /** + * @see Zend_Barcode_Object_Exception + */ + require_once 'Zend/Barcode/Object/Exception.php'; + throw new Zend_Barcode_Object_Exception($message); + } + } + + /** + * Get barcode checksum + * + * @param string $text + * @return int + */ + public function getChecksum($text) + { + $text = $this->_addLeadingZeros($text, true); + if ($text{0} != 1) { + $text{0} = 0; + } + return parent::getChecksum($text); + } +} diff --git a/library/Zend/Barcode/Renderer/Exception.php b/library/Zend/Barcode/Renderer/Exception.php new file mode 100644 index 00000000..d042351c --- /dev/null +++ b/library/Zend/Barcode/Renderer/Exception.php @@ -0,0 +1,35 @@ +_userHeight = intval($value); + return $this; + } + + /** + * Get barcode height + * + * @return int + */ + public function getHeight() + { + return $this->_userHeight; + } + + /** + * Set barcode width + * + * @param mixed $value + * @return void + */ + public function setWidth($value) + { + if (!is_numeric($value) || intval($value) < 0) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Image width must be greater than or equals 0' + ); + } + $this->_userWidth = intval($value); + return $this; + } + + /** + * Get barcode width + * + * @return int + */ + public function getWidth() + { + return $this->_userWidth; + } + + /** + * Set an image resource to draw the barcode inside + * + * @param resource $value + * @return Zend_Barcode_Renderer + * @throw Zend_Barcode_Renderer_Exception + */ + public function setResource($image) + { + if (gettype($image) != 'resource' || get_resource_type($image) != 'gd') { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Invalid image resource provided to setResource()' + ); + } + $this->_resource = $image; + return $this; + } + + /** + * Set the image type to produce (png, jpeg, gif) + * + * @param string $value + * @return Zend_Barcode_RendererAbstract + * @throw Zend_Barcode_Renderer_Exception + */ + public function setImageType($value) + { + if ($value == 'jpg') { + $value = 'jpeg'; + } + + if (!in_array($value, $this->_allowedImageType)) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception(sprintf( + 'Invalid type "%s" provided to setImageType()', + $value + )); + } + + $this->_imageType = $value; + return $this; + } + + /** + * Retrieve the image type to produce + * + * @return string + */ + public function getImageType() + { + return $this->_imageType; + } + + /** + * Initialize the image resource + * + * @return void + */ + protected function _initRenderer() + { + if (!extension_loaded('gd')) { + require_once 'Zend/Barcode/Exception.php'; + $e = new Zend_Barcode_Exception( + 'Gd extension must be loaded to render barcode as image' + ); + $e->setIsRenderable(false); + throw $e; + } + + $barcodeWidth = $this->_barcode->getWidth(true); + $barcodeHeight = $this->_barcode->getHeight(true); + + if ($this->_resource !== null) { + $foreColor = $this->_barcode->getForeColor(); + $backgroundColor = $this->_barcode->getBackgroundColor(); + $this->_imageBackgroundColor = imagecolorallocate( + $this->_resource, + ($backgroundColor & 0xFF0000) >> 16, + ($backgroundColor & 0x00FF00) >> 8, + $backgroundColor & 0x0000FF + ); + $this->_imageForeColor = imagecolorallocate( + $this->_resource, + ($foreColor & 0xFF0000) >> 16, + ($foreColor & 0x00FF00) >> 8, + $foreColor & 0x0000FF + ); + } else { + $width = $barcodeWidth; + $height = $barcodeHeight; + if ($this->_userWidth && $this->_barcode->getType() != 'error') { + $width = $this->_userWidth; + } + if ($this->_userHeight && $this->_barcode->getType() != 'error') { + $height = $this->_userHeight; + } + + $foreColor = $this->_barcode->getForeColor(); + $backgroundColor = $this->_barcode->getBackgroundColor(); + $this->_resource = imagecreatetruecolor($width, $height); + + $this->_imageBackgroundColor = imagecolorallocate( + $this->_resource, + ($backgroundColor & 0xFF0000) >> 16, + ($backgroundColor & 0x00FF00) >> 8, + $backgroundColor & 0x0000FF + ); + $this->_imageForeColor = imagecolorallocate( + $this->_resource, + ($foreColor & 0xFF0000) >> 16, + ($foreColor & 0x00FF00) >> 8, + $foreColor & 0x0000FF + ); + $white = imagecolorallocate($this->_resource, 255, 255, 255); + imagefilledrectangle($this->_resource, 0, 0, $width - 1, $height - 1, $white); + } + $this->_adjustPosition(imagesy($this->_resource), imagesx($this->_resource)); + imagefilledrectangle( + $this->_resource, + $this->_leftOffset, + $this->_topOffset, + $this->_leftOffset + $barcodeWidth - 1, + $this->_topOffset + $barcodeHeight - 1, + $this->_imageBackgroundColor + ); + } + + /** + * Check barcode parameters + * + * @return void + */ + protected function _checkParams() + { + $this->_checkDimensions(); + } + + /** + * Check barcode dimensions + * + * @return void + */ + protected function _checkDimensions() + { + if ($this->_resource !== null) { + if (imagesy($this->_resource) < $this->_barcode->getHeight(true)) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Barcode is define outside the image (height)' + ); + } + } else { + if ($this->_userHeight) { + $height = $this->_barcode->getHeight(true); + if ($this->_userHeight < $height) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception(sprintf( + "Barcode is define outside the image (calculated: '%d', provided: '%d')", + $height, + $this->_userHeight + )); + } + } + } + if ($this->_resource !== null) { + if (imagesx($this->_resource) < $this->_barcode->getWidth(true)) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Barcode is define outside the image (width)' + ); + } + } else { + if ($this->_userWidth) { + $width = $this->_barcode->getWidth(true); + if ($this->_userWidth < $width) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception(sprintf( + "Barcode is define outside the image (calculated: '%d', provided: '%d')", + $width, + $this->_userWidth + )); + } + } + } + } + + /** + * Draw and render the barcode with correct headers + * + * @return mixed + */ + public function render() + { + $this->draw(); + header("Content-Type: image/" . $this->_imageType); + $functionName = 'image' . $this->_imageType; + call_user_func($functionName, $this->_resource); + @imagedestroy($this->_resource); + } + + /** + * Draw a polygon in the image resource + * + * @param array $points + * @param integer $color + * @param boolean $filled + */ + protected function _drawPolygon($points, $color, $filled = true) + { + $newPoints = array( + $points[0][0] + $this->_leftOffset, + $points[0][1] + $this->_topOffset, + $points[1][0] + $this->_leftOffset, + $points[1][1] + $this->_topOffset, + $points[2][0] + $this->_leftOffset, + $points[2][1] + $this->_topOffset, + $points[3][0] + $this->_leftOffset, + $points[3][1] + $this->_topOffset, + ); + + $allocatedColor = imagecolorallocate( + $this->_resource, + ($color & 0xFF0000) >> 16, + ($color & 0x00FF00) >> 8, + $color & 0x0000FF + ); + + if ($filled) { + imagefilledpolygon($this->_resource, $newPoints, 4, $allocatedColor); + } else { + imagepolygon($this->_resource, $newPoints, 4, $allocatedColor); + } + } + + /** + * Draw a polygon in the image resource + * + * @param string $text + * @param float $size + * @param array $position + * @param string $font + * @param integer $color + * @param string $alignment + * @param float $orientation + */ + protected function _drawText($text, $size, $position, $font, $color, $alignment = 'center', $orientation = 0) + { + $allocatedColor = imagecolorallocate( + $this->_resource, + ($color & 0xFF0000) >> 16, + ($color & 0x00FF00) >> 8, + $color & 0x0000FF + ); + + if ($font == null) { + $font = 3; + } + $position[0] += $this->_leftOffset; + $position[1] += $this->_topOffset; + + if (is_numeric($font)) { + if ($orientation) { + /** + * imagestring() doesn't allow orientation, if orientation + * needed: a TTF font is required. + * Throwing an exception here, allow to use automaticRenderError + * to informe user of the problem instead of simply not drawing + * the text + */ + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'No orientation possible with GD internal font' + ); + } + $fontWidth = imagefontwidth($font); + $positionY = $position[1] - imagefontheight($font) + 1; + switch ($alignment) { + case 'left': + $positionX = $position[0]; + break; + case 'center': + $positionX = $position[0] - ceil(($fontWidth * strlen($text)) / 2); + break; + case 'right': + $positionX = $position[0] - ($fontWidth * strlen($text)); + break; + } + imagestring($this->_resource, $font, $positionX, $positionY, $text, $color); + } else { + + if (!function_exists('imagettfbbox')) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'A font was provided, but this instance of PHP does not have TTF (FreeType) support' + ); + } + + $box = imagettfbbox($size, 0, $font, $text); + switch ($alignment) { + case 'left': + $width = 0; + break; + case 'center': + $width = ($box[2] - $box[0]) / 2; + break; + case 'right': + $width = ($box[2] - $box[0]); + break; + } + imagettftext( + $this->_resource, + $size, + $orientation, + $position[0] - ($width * cos(pi() * $orientation / 180)), + $position[1] + ($width * sin(pi() * $orientation / 180)), + $allocatedColor, + $font, + $text + ); + } + } +} diff --git a/library/Zend/Barcode/Renderer/Pdf.php b/library/Zend/Barcode/Renderer/Pdf.php new file mode 100644 index 00000000..89bd9be2 --- /dev/null +++ b/library/Zend/Barcode/Renderer/Pdf.php @@ -0,0 +1,242 @@ +_resource = $pdf; + $this->_page = intval($page); + + if (!count($this->_resource->pages)) { + $this->_page = 0; + $this->_resource->pages[] = new Zend_Pdf_Page( + Zend_Pdf_Page::SIZE_A4 + ); + } + return $this; + } + + /** + * Check renderer parameters + * + * @return void + */ + protected function _checkParams() + { + } + + /** + * Draw the barcode in the PDF, send headers and the PDF + * @return mixed + */ + public function render() + { + $this->draw(); + header("Content-Type: application/pdf"); + echo $this->_resource->render(); + } + + /** + * Initialize the PDF resource + * @return void + */ + protected function _initRenderer() + { + if ($this->_resource === null) { + $this->_resource = new Zend_Pdf(); + $this->_resource->pages[] = new Zend_Pdf_Page( + Zend_Pdf_Page::SIZE_A4 + ); + } + + $pdfPage = $this->_resource->pages[$this->_page]; + $this->_adjustPosition($pdfPage->getHeight(), $pdfPage->getWidth()); + } + + /** + * Draw a polygon in the rendering resource + * @param array $points + * @param integer $color + * @param boolean $filled + */ + protected function _drawPolygon($points, $color, $filled = true) + { + $page = $this->_resource->pages[$this->_page]; + foreach ($points as $point) { + $x[] = $point[0] * $this->_moduleSize + $this->_leftOffset; + $y[] = $page->getHeight() - $point[1] * $this->_moduleSize - $this->_topOffset; + } + if (count($y) == 4) { + if ($x[0] != $x[3] && $y[0] == $y[3]) { + $y[0] -= ($this->_moduleSize / 2); + $y[3] -= ($this->_moduleSize / 2); + } + if ($x[1] != $x[2] && $y[1] == $y[2]) { + $y[1] += ($this->_moduleSize / 2); + $y[2] += ($this->_moduleSize / 2); + } + } + + $color = new Zend_Pdf_Color_Rgb( + (($color & 0xFF0000) >> 16) / 255.0, + (($color & 0x00FF00) >> 8) / 255.0, + ($color & 0x0000FF) / 255.0 + ); + + $page->setLineColor($color); + $page->setFillColor($color); + $page->setLineWidth($this->_moduleSize); + + $fillType = ($filled) + ? Zend_Pdf_Page::SHAPE_DRAW_FILL_AND_STROKE + : Zend_Pdf_Page::SHAPE_DRAW_STROKE; + + $page->drawPolygon($x, $y, $fillType); + } + + /** + * Draw a text in the rendering resource + * @param string $text + * @param float $size + * @param array $position + * @param string $font + * @param integer $color + * @param string $alignment + * @param float $orientation + */ + protected function _drawText( + $text, + $size, + $position, + $font, + $color, + $alignment = 'center', + $orientation = 0 + ) { + $page = $this->_resource->pages[$this->_page]; + $color = new Zend_Pdf_Color_Rgb( + (($color & 0xFF0000) >> 16) / 255.0, + (($color & 0x00FF00) >> 8) / 255.0, + ($color & 0x0000FF) / 255.0 + ); + + $page->setLineColor($color); + $page->setFillColor($color); + $page->setFont(Zend_Pdf_Font::fontWithPath($font), $size * $this->_moduleSize * 1.2); + + $width = $this->widthForStringUsingFontSize( + $text, + Zend_Pdf_Font::fontWithPath($font), + $size * $this->_moduleSize + ); + + $angle = pi() * $orientation / 180; + $left = $position[0] * $this->_moduleSize + $this->_leftOffset; + $top = $page->getHeight() - $position[1] * $this->_moduleSize - $this->_topOffset; + + switch ($alignment) { + case 'center': + $left -= ($width / 2) * cos($angle); + $top -= ($width / 2) * sin($angle); + break; + case 'right': + $left -= $width; + break; + } + $page->rotate($left, $top, $angle); + $page->drawText($text, $left, $top); + $page->rotate($left, $top, - $angle); + } + + /** + * Calculate the width of a string: + * in case of using alignment parameter in drawText + * @param string $text + * @param Zend_Pdf_Font $font + * @param float $fontSize + * @return float + */ + public function widthForStringUsingFontSize($text, $font, $fontSize) + { + $drawingString = iconv('UTF-8', 'UTF-16BE//IGNORE', $text); + $characters = array(); + for ($i = 0; $i < strlen($drawingString); $i ++) { + $characters[] = (ord($drawingString[$i ++]) << 8) | ord($drawingString[$i]); + } + $glyphs = $font->glyphNumbersForCharacters($characters); + $widths = $font->widthsForGlyphs($glyphs); + $stringWidth = (array_sum($widths) / $font->getUnitsPerEm()) * $fontSize; + return $stringWidth; + } +} diff --git a/library/Zend/Barcode/Renderer/RendererAbstract.php b/library/Zend/Barcode/Renderer/RendererAbstract.php new file mode 100644 index 00000000..b7ef94a0 --- /dev/null +++ b/library/Zend/Barcode/Renderer/RendererAbstract.php @@ -0,0 +1,540 @@ +toArray(); + } + if (is_array($options)) { + $this->setOptions($options); + } + $this->_type = strtolower(substr( + get_class($this), + strlen($this->_rendererNamespace) + 1 + )); + } + + /** + * Set renderer state from options array + * @param array $options + * @return Zend_Renderer_Object + */ + public function setOptions($options) + { + foreach ($options as $key => $value) { + $method = 'set' . $key; + if (method_exists($this, $method)) { + $this->$method($value); + } + } + return $this; + } + + /** + * Set renderer state from config object + * @param Zend_Config $config + * @return Zend_Renderer_Object + */ + public function setConfig(Zend_Config $config) + { + return $this->setOptions($config->toArray()); + } + + /** + * Set renderer namespace for autoloading + * + * @param string $namespace + * @return Zend_Renderer_Object + */ + public function setRendererNamespace($namespace) + { + $this->_rendererNamespace = $namespace; + return $this; + } + + /** + * Retrieve renderer namespace + * + * @return string + */ + public function getRendererNamespace() + { + return $this->_rendererNamespace; + } + + /** + * Retrieve renderer type + * @return string + */ + public function getType() + { + return $this->_type; + } + + /** + * Manually adjust top position + * @param integer $value + * @return Zend_Barcode_Renderer + * @throw Zend_Barcode_Renderer_Exception + */ + public function setTopOffset($value) + { + if (!is_numeric($value) || intval($value) < 0) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Vertical position must be greater than or equals 0' + ); + } + $this->_topOffset = intval($value); + return $this; + } + + /** + * Retrieve vertical adjustment + * @return integer + */ + public function getTopOffset() + { + return $this->_topOffset; + } + + /** + * Manually adjust left position + * @param integer $value + * @return Zend_Barcode_Renderer + * @throw Zend_Barcode_Renderer_Exception + */ + public function setLeftOffset($value) + { + if (!is_numeric($value) || intval($value) < 0) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Horizontal position must be greater than or equals 0' + ); + } + $this->_leftOffset = intval($value); + return $this; + } + + /** + * Retrieve vertical adjustment + * @return integer + */ + public function getLeftOffset() + { + return $this->_leftOffset; + } + + /** + * Activate/Deactivate the automatic rendering of exception + * @param boolean $value + */ + public function setAutomaticRenderError($value) + { + $this->_automaticRenderError = (bool) $value; + return $this; + } + + /** + * Horizontal position of the barcode in the rendering resource + * @param string $value + * @return Zend_Barcode_Renderer + * @throw Zend_Barcode_Renderer_Exception + */ + public function setHorizontalPosition($value) + { + if (!in_array($value, array('left' , 'center' , 'right'))) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + "Invalid barcode position provided must be 'left', 'center' or 'right'" + ); + } + $this->_horizontalPosition = $value; + return $this; + } + + /** + * Horizontal position of the barcode in the rendering resource + * @return string + */ + public function getHorizontalPosition() + { + return $this->_horizontalPosition; + } + + /** + * Vertical position of the barcode in the rendering resource + * @param string $value + * @return Zend_Barcode_Renderer + * @throw Zend_Barcode_Renderer_Exception + */ + public function setVerticalPosition($value) + { + if (!in_array($value, array('top' , 'middle' , 'bottom'))) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + "Invalid barcode position provided must be 'top', 'middle' or 'bottom'" + ); + } + $this->_verticalPosition = $value; + return $this; + } + + /** + * Vertical position of the barcode in the rendering resource + * @return string + */ + public function getVerticalPosition() + { + return $this->_verticalPosition; + } + + /** + * Set the size of a module + * @param float $value + * @return Zend_Barcode_Renderer + * @throw Zend_Barcode_Renderer_Exception + */ + public function setModuleSize($value) + { + if (!is_numeric($value) || floatval($value) <= 0) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Float size must be greater than 0' + ); + } + $this->_moduleSize = floatval($value); + return $this; + } + + + /** + * Set the size of a module + * @return float + */ + public function getModuleSize() + { + return $this->_moduleSize; + } + + /** + * Retrieve the automatic rendering of exception + * @return boolean + */ + public function getAutomaticRenderError() + { + return $this->_automaticRenderError; + } + + /** + * Set the barcode object + * @param Zend_Barcode_Object $barcode + * @return Zend_Barcode_Renderer + */ + public function setBarcode($barcode) + { + if (!$barcode instanceof Zend_Barcode_Object_ObjectAbstract) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Invalid barcode object provided to setBarcode()' + ); + } + $this->_barcode = $barcode; + return $this; + } + + /** + * Retrieve the barcode object + * @return Zend_Barcode_Object + */ + public function getBarcode() + { + return $this->_barcode; + } + + /** + * Checking of parameters after all settings + * @return boolean + */ + public function checkParams() + { + $this->_checkBarcodeObject(); + $this->_checkParams(); + return true; + } + + /** + * Check if a barcode object is correctly provided + * @return void + * @throw Zend_Barcode_Renderer_Exception + */ + protected function _checkBarcodeObject() + { + if ($this->_barcode === null) { + /** + * @see Zend_Barcode_Renderer_Exception + */ + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'No barcode object provided' + ); + } + } + + /** + * Calculate the left and top offset of the barcode in the + * rendering support + * + * @param float $supportHeight + * @param float $supportWidth + * @return void + */ + protected function _adjustPosition($supportHeight, $supportWidth) + { + $barcodeHeight = $this->_barcode->getHeight(true) * $this->_moduleSize; + if ($barcodeHeight != $supportHeight && $this->_topOffset == 0) { + switch ($this->_verticalPosition) { + case 'middle': + $this->_topOffset = floor( + ($supportHeight - $barcodeHeight) / 2); + break; + case 'bottom': + $this->_topOffset = $supportHeight - $barcodeHeight; + break; + case 'top': + default: + $this->_topOffset = 0; + break; + } + } + $barcodeWidth = $this->_barcode->getWidth(true) * $this->_moduleSize; + if ($barcodeWidth != $supportWidth && $this->_leftOffset == 0) { + switch ($this->_horizontalPosition) { + case 'center': + $this->_leftOffset = floor( + ($supportWidth - $barcodeWidth) / 2); + break; + case 'right': + $this->_leftOffset = $supportWidth - $barcodeWidth; + break; + case 'left': + default: + $this->_leftOffset = 0; + break; + } + } + } + + /** + * Draw the barcode in the rendering resource + * @return mixed + */ + public function draw() + { + try { + $this->checkParams(); + $this->_initRenderer(); + $this->_drawInstructionList(); + } catch (Zend_Exception $e) { + $renderable = false; + if ($e instanceof Zend_Barcode_Exception) { + $renderable = $e->isRenderable(); + } + if ($this->_automaticRenderError && $renderable) { + $barcode = Zend_Barcode::makeBarcode( + 'error', + array('text' => $e->getMessage()) + ); + $this->setBarcode($barcode); + $this->_resource = null; + $this->_initRenderer(); + $this->_drawInstructionList(); + } else { + if ($e instanceof Zend_Barcode_Exception) { + $e->setIsRenderable(false); + } + throw $e; + } + } + return $this->_resource; + } + + /** + * Sub process to draw the barcode instructions + * Needed by the automatic error rendering + */ + private function _drawInstructionList() + { + $instructionList = $this->_barcode->draw(); + foreach ($instructionList as $instruction) { + switch ($instruction['type']) { + case 'polygon': + $this->_drawPolygon( + $instruction['points'], + $instruction['color'], + $instruction['filled'] + ); + break; + case 'text': //$text, $size, $position, $font, $color, $alignment = 'center', $orientation = 0) + $this->_drawText( + $instruction['text'], + $instruction['size'], + $instruction['position'], + $instruction['font'], + $instruction['color'], + $instruction['alignment'], + $instruction['orientation'] + ); + break; + default: + /** + * @see Zend_Barcode_Renderer_Exception + */ + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Unkown drawing command' + ); + } + } + } + + /** + * Checking of parameters after all settings + * @return void + */ + abstract protected function _checkParams(); + + /** + * Render the resource by sending headers and drawed resource + * @return mixed + */ + abstract public function render(); + + /** + * Initialize the rendering resource + * @return void + */ + abstract protected function _initRenderer(); + + /** + * Draw a polygon in the rendering resource + * @param array $points + * @param integer $color + * @param boolean $filled + */ + abstract protected function _drawPolygon($points, $color, $filled = true); + + /** + * Draw a polygon in the rendering resource + * @param string $text + * @param float $size + * @param array $position + * @param string $font + * @param integer $color + * @param string $alignment + * @param float $orientation + */ + abstract protected function _drawText( + $text, + $size, + $position, + $font, + $color, + $alignment = 'center', + $orientation = 0 + ); +} diff --git a/library/Zend/Barcode/Renderer/Svg.php b/library/Zend/Barcode/Renderer/Svg.php new file mode 100644 index 00000000..301dc845 --- /dev/null +++ b/library/Zend/Barcode/Renderer/Svg.php @@ -0,0 +1,382 @@ +_userHeight = intval($value); + return $this; + } + + /** + * Get barcode height + * + * @return int + */ + public function getHeight() + { + return $this->_userHeight; + } + + /** + * Set barcode width + * + * @param mixed $value + * @return void + */ + public function setWidth($value) + { + if (!is_numeric($value) || intval($value) < 0) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Svg width must be greater than or equals 0' + ); + } + $this->_userWidth = intval($value); + return $this; + } + + /** + * Get barcode width + * + * @return int + */ + public function getWidth() + { + return $this->_userWidth; + } + + /** + * Set an image resource to draw the barcode inside + * + * @param DOMDocument $value + * @return Zend_Barcode_Renderer + * @throw Zend_Barcode_Renderer_Exception + */ + public function setResource($svg) + { + if (!$svg instanceof DOMDocument) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Invalid DOMDocument resource provided to setResource()' + ); + } + $this->_resource = $svg; + return $this; + } + + /** + * Initialize the image resource + * + * @return void + */ + protected function _initRenderer() + { + $barcodeWidth = $this->_barcode->getWidth(true); + $barcodeHeight = $this->_barcode->getHeight(true); + + $backgroundColor = $this->_barcode->getBackgroundColor(); + $imageBackgroundColor = 'rgb(' . implode(', ', array(($backgroundColor & 0xFF0000) >> 16, + ($backgroundColor & 0x00FF00) >> 8, + ($backgroundColor & 0x0000FF))) . ')'; + + $width = $barcodeWidth; + $height = $barcodeHeight; + if ($this->_userWidth && $this->_barcode->getType() != 'error') { + $width = $this->_userWidth; + } + if ($this->_userHeight && $this->_barcode->getType() != 'error') { + $height = $this->_userHeight; + } + if ($this->_resource === null) { + $this->_resource = new DOMDocument('1.0', 'utf-8'); + $this->_resource->formatOutput = true; + $this->_rootElement = $this->_resource->createElement('svg'); + $this->_rootElement->setAttribute('xmlns', "http://www.w3.org/2000/svg"); + $this->_rootElement->setAttribute('version', '1.1'); + $this->_rootElement->setAttribute('width', $width); + $this->_rootElement->setAttribute('height', $height); + + $this->_appendRootElement('title', + array(), + "Barcode " . strtoupper($this->_barcode->getType()) . " " . $this->_barcode->getText()); + } else { + $this->_readRootElement(); + $width = $this->_rootElement->getAttribute('width'); + $height = $this->_rootElement->getAttribute('height'); + } + $this->_adjustPosition($height, $width); + + $this->_appendRootElement('rect', + array('x' => $this->_leftOffset, + 'y' => $this->_topOffset, + 'width' => ($this->_leftOffset + $barcodeWidth - 1), + 'height' => ($this->_topOffset + $barcodeHeight - 1), + 'fill' => $imageBackgroundColor)); + } + + protected function _readRootElement() + { + if ($this->_resource !== null) { + $this->_rootElement = $this->_resource->documentElement; + } + } + + /** + * Append a new DOMElement to the root element + * + * @param string $tagName + * @param array $attributes + * @param string $textContent + */ + protected function _appendRootElement($tagName, $attributes = array(), $textContent = null) + { + $newElement = $this->_createElement($tagName, $attributes, $textContent); + $this->_rootElement->appendChild($newElement); + } + + /** + * Create DOMElement + * + * @param string $tagName + * @param array $attributes + * @param string $textContent + * @return DOMElement + */ + protected function _createElement($tagName, $attributes = array(), $textContent = null) + { + $element = $this->_resource->createElement($tagName); + foreach ($attributes as $k =>$v) { + $element->setAttribute($k, $v); + } + if ($textContent !== null) { + $element->appendChild(new DOMText((string) $textContent)); + } + return $element; + } + + /** + * Check barcode parameters + * + * @return void + */ + protected function _checkParams() + { + $this->_checkDimensions(); + } + + /** + * Check barcode dimensions + * + * @return void + */ + protected function _checkDimensions() + { + if ($this->_resource !== null) { + $this->_readRootElement(); + $height = (float) $this->_rootElement->getAttribute('height'); + if ($height < $this->_barcode->getHeight(true)) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Barcode is define outside the image (height)' + ); + } + } else { + if ($this->_userHeight) { + $height = $this->_barcode->getHeight(true); + if ($this->_userHeight < $height) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception(sprintf( + "Barcode is define outside the image (calculated: '%d', provided: '%d')", + $height, + $this->_userHeight + )); + } + } + } + if ($this->_resource !== null) { + $this->_readRootElement(); + $width = $this->_rootElement->getAttribute('width'); + if ($width < $this->_barcode->getWidth(true)) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception( + 'Barcode is define outside the image (width)' + ); + } + } else { + if ($this->_userWidth) { + $width = (float) $this->_barcode->getWidth(true); + if ($this->_userWidth < $width) { + require_once 'Zend/Barcode/Renderer/Exception.php'; + throw new Zend_Barcode_Renderer_Exception(sprintf( + "Barcode is define outside the image (calculated: '%d', provided: '%d')", + $width, + $this->_userWidth + )); + } + } + } + } + + /** + * Draw the barcode in the rendering resource + * @return mixed + */ + public function draw() + { + parent::draw(); + $this->_resource->appendChild($this->_rootElement); + return $this->_resource; + } + + /** + * Draw and render the barcode with correct headers + * + * @return mixed + */ + public function render() + { + $this->draw(); + header("Content-Type: image/svg+xml"); + echo $this->_resource->saveXML(); + } + + /** + * Draw a polygon in the svg resource + * + * @param array $points + * @param integer $color + * @param boolean $filled + */ + protected function _drawPolygon($points, $color, $filled = true) + { + $color = 'rgb(' . implode(', ', array(($color & 0xFF0000) >> 16, + ($color & 0x00FF00) >> 8, + ($color & 0x0000FF))) . ')'; + $orientation = $this->getBarcode()->getOrientation(); + $newPoints = array( + $points[0][0] + $this->_leftOffset, + $points[0][1] + $this->_topOffset, + $points[1][0] + $this->_leftOffset, + $points[1][1] + $this->_topOffset, + $points[2][0] + $this->_leftOffset + cos(-$orientation), + $points[2][1] + $this->_topOffset - sin($orientation), + $points[3][0] + $this->_leftOffset + cos(-$orientation), + $points[3][1] + $this->_topOffset - sin($orientation), + ); + $newPoints = implode(' ', $newPoints); + $attributes['points'] = $newPoints; + $attributes['fill'] = $color; + $this->_appendRootElement('polygon', $attributes); + } + + /** + * Draw a polygon in the svg resource + * + * @param string $text + * @param float $size + * @param array $position + * @param string $font + * @param integer $color + * @param string $alignment + * @param float $orientation + */ + protected function _drawText($text, $size, $position, $font, $color, $alignment = 'center', $orientation = 0) + { + $color = 'rgb(' . implode(', ', array(($color & 0xFF0000) >> 16, + ($color & 0x00FF00) >> 8, + ($color & 0x0000FF))) . ')'; + $attributes['x'] = $position[0] + $this->_leftOffset; + $attributes['y'] = $position[1] + $this->_topOffset; + //$attributes['font-family'] = $font; + $attributes['color'] = $color; + $attributes['font-size'] = $size * 1.2; + switch ($alignment) { + case 'left': + $textAnchor = 'start'; + break; + case 'right': + $textAnchor = 'end'; + break; + case 'center': + default: + $textAnchor = 'middle'; + } + $attributes['style'] = 'text-anchor: ' . $textAnchor; + $attributes['transform'] = 'rotate(' + . (- $orientation) + . ', ' + . ($position[0] + $this->_leftOffset) + . ', ' . ($position[1] + $this->_topOffset) + . ')'; + $this->_appendRootElement('text', $attributes, $text); + } +} diff --git a/library/Zend/Cache.php b/library/Zend/Cache.php new file mode 100644 index 00000000..9ecbe8ec --- /dev/null +++ b/library/Zend/Cache.php @@ -0,0 +1,250 @@ +setBackend($backendObject); + return $frontendObject; + } + + /** + * Backend Constructor + * + * @param string $backend + * @param array $backendOptions + * @param boolean $customBackendNaming + * @param boolean $autoload + * @return Zend_Cache_Backend + */ + public static function _makeBackend($backend, $backendOptions, $customBackendNaming = false, $autoload = false) + { + if (!$customBackendNaming) { + $backend = self::_normalizeName($backend); + } + if (in_array($backend, Zend_Cache::$standardBackends)) { + // we use a standard backend + $backendClass = 'Zend_Cache_Backend_' . $backend; + // security controls are explicit + require_once str_replace('_', DIRECTORY_SEPARATOR, $backendClass) . '.php'; + } else { + // we use a custom backend + if (!preg_match('~^[\w]+$~D', $backend)) { + Zend_Cache::throwException("Invalid backend name [$backend]"); + } + if (!$customBackendNaming) { + // we use this boolean to avoid an API break + $backendClass = 'Zend_Cache_Backend_' . $backend; + } else { + $backendClass = $backend; + } + if (!$autoload) { + $file = str_replace('_', DIRECTORY_SEPARATOR, $backendClass) . '.php'; + if (!(self::_isReadable($file))) { + self::throwException("file $file not found in include_path"); + } + require_once $file; + } + } + return new $backendClass($backendOptions); + } + + /** + * Frontend Constructor + * + * @param string $frontend + * @param array $frontendOptions + * @param boolean $customFrontendNaming + * @param boolean $autoload + * @return Zend_Cache_Core|Zend_Cache_Frontend + */ + public static function _makeFrontend($frontend, $frontendOptions = array(), $customFrontendNaming = false, $autoload = false) + { + if (!$customFrontendNaming) { + $frontend = self::_normalizeName($frontend); + } + if (in_array($frontend, self::$standardFrontends)) { + // we use a standard frontend + // For perfs reasons, with frontend == 'Core', we can interact with the Core itself + $frontendClass = 'Zend_Cache_' . ($frontend != 'Core' ? 'Frontend_' : '') . $frontend; + // security controls are explicit + require_once str_replace('_', DIRECTORY_SEPARATOR, $frontendClass) . '.php'; + } else { + // we use a custom frontend + if (!preg_match('~^[\w]+$~D', $frontend)) { + Zend_Cache::throwException("Invalid frontend name [$frontend]"); + } + if (!$customFrontendNaming) { + // we use this boolean to avoid an API break + $frontendClass = 'Zend_Cache_Frontend_' . $frontend; + } else { + $frontendClass = $frontend; + } + if (!$autoload) { + $file = str_replace('_', DIRECTORY_SEPARATOR, $frontendClass) . '.php'; + if (!(self::_isReadable($file))) { + self::throwException("file $file not found in include_path"); + } + require_once $file; + } + } + return new $frontendClass($frontendOptions); + } + + /** + * Throw an exception + * + * Note : for perf reasons, the "load" of Zend/Cache/Exception is dynamic + * @param string $msg Message for the exception + * @throws Zend_Cache_Exception + */ + public static function throwException($msg, Exception $e = null) + { + // For perfs reasons, we use this dynamic inclusion + require_once 'Zend/Cache/Exception.php'; + throw new Zend_Cache_Exception($msg, 0, $e); + } + + /** + * Normalize frontend and backend names to allow multiple words TitleCased + * + * @param string $name Name to normalize + * @return string + */ + protected static function _normalizeName($name) + { + $name = ucfirst(strtolower($name)); + $name = str_replace(array('-', '_', '.'), ' ', $name); + $name = ucwords($name); + $name = str_replace(' ', '', $name); + if (stripos($name, 'ZendServer') === 0) { + $name = 'ZendServer_' . substr($name, strlen('ZendServer')); + } + + return $name; + } + + /** + * Returns TRUE if the $filename is readable, or FALSE otherwise. + * This function uses the PHP include_path, where PHP's is_readable() + * does not. + * + * Note : this method comes from Zend_Loader (see #ZF-2891 for details) + * + * @param string $filename + * @return boolean + */ + private static function _isReadable($filename) + { + if (!$fh = @fopen($filename, 'r', true)) { + return false; + } + @fclose($fh); + return true; + } + +} diff --git a/library/Zend/Cache/Backend.php b/library/Zend/Cache/Backend.php new file mode 100644 index 00000000..469180b7 --- /dev/null +++ b/library/Zend/Cache/Backend.php @@ -0,0 +1,268 @@ + (int) lifetime : + * - Cache lifetime (in seconds) + * - If null, the cache is valid forever + * + * =====> (int) logging : + * - if set to true, a logging is activated throw Zend_Log + * + * @var array directives + */ + protected $_directives = array( + 'lifetime' => 3600, + 'logging' => false, + 'logger' => null + ); + + /** + * Available options + * + * @var array available options + */ + protected $_options = array(); + + /** + * Constructor + * + * @param array $options Associative array of options + * @throws Zend_Cache_Exception + * @return void + */ + public function __construct(array $options = array()) + { + while (list($name, $value) = each($options)) { + $this->setOption($name, $value); + } + } + + /** + * Set the frontend directives + * + * @param array $directives Assoc of directives + * @throws Zend_Cache_Exception + * @return void + */ + public function setDirectives($directives) + { + if (!is_array($directives)) Zend_Cache::throwException('Directives parameter must be an array'); + while (list($name, $value) = each($directives)) { + if (!is_string($name)) { + Zend_Cache::throwException("Incorrect option name : $name"); + } + $name = strtolower($name); + if (array_key_exists($name, $this->_directives)) { + $this->_directives[$name] = $value; + } + + } + + $this->_loggerSanity(); + } + + /** + * Set an option + * + * @param string $name + * @param mixed $value + * @throws Zend_Cache_Exception + * @return void + */ + public function setOption($name, $value) + { + if (!is_string($name)) { + Zend_Cache::throwException("Incorrect option name : $name"); + } + $name = strtolower($name); + if (array_key_exists($name, $this->_options)) { + $this->_options[$name] = $value; + } + } + + /** + * Get the life time + * + * if $specificLifetime is not false, the given specific life time is used + * else, the global lifetime is used + * + * @param int $specificLifetime + * @return int Cache life time + */ + public function getLifetime($specificLifetime) + { + if ($specificLifetime === false) { + return $this->_directives['lifetime']; + } + return $specificLifetime; + } + + /** + * Return true if the automatic cleaning is available for the backend + * + * DEPRECATED : use getCapabilities() instead + * + * @deprecated + * @return boolean + */ + public function isAutomaticCleaningAvailable() + { + return true; + } + + /** + * Determine system TMP directory and detect if we have read access + * + * inspired from Zend_File_Transfer_Adapter_Abstract + * + * @return string + * @throws Zend_Cache_Exception if unable to determine directory + */ + public function getTmpDir() + { + $tmpdir = array(); + foreach (array($_ENV, $_SERVER) as $tab) { + foreach (array('TMPDIR', 'TEMP', 'TMP', 'windir', 'SystemRoot') as $key) { + if (isset($tab[$key])) { + if (($key == 'windir') or ($key == 'SystemRoot')) { + $dir = realpath($tab[$key] . '\\temp'); + } else { + $dir = realpath($tab[$key]); + } + if ($this->_isGoodTmpDir($dir)) { + return $dir; + } + } + } + } + $upload = ini_get('upload_tmp_dir'); + if ($upload) { + $dir = realpath($upload); + if ($this->_isGoodTmpDir($dir)) { + return $dir; + } + } + if (function_exists('sys_get_temp_dir')) { + $dir = sys_get_temp_dir(); + if ($this->_isGoodTmpDir($dir)) { + return $dir; + } + } + // Attemp to detect by creating a temporary file + $tempFile = tempnam(md5(uniqid(rand(), TRUE)), ''); + if ($tempFile) { + $dir = realpath(dirname($tempFile)); + unlink($tempFile); + if ($this->_isGoodTmpDir($dir)) { + return $dir; + } + } + if ($this->_isGoodTmpDir('/tmp')) { + return '/tmp'; + } + if ($this->_isGoodTmpDir('\\temp')) { + return '\\temp'; + } + Zend_Cache::throwException('Could not determine temp directory, please specify a cache_dir manually'); + } + + /** + * Verify if the given temporary directory is readable and writable + * + * @param string $dir temporary directory + * @return boolean true if the directory is ok + */ + protected function _isGoodTmpDir($dir) + { + if (is_readable($dir)) { + if (is_writable($dir)) { + return true; + } + } + return false; + } + + /** + * Make sure if we enable logging that the Zend_Log class + * is available. + * Create a default log object if none is set. + * + * @throws Zend_Cache_Exception + * @return void + */ + protected function _loggerSanity() + { + if (!isset($this->_directives['logging']) || !$this->_directives['logging']) { + return; + } + + if (isset($this->_directives['logger'])) { + if ($this->_directives['logger'] instanceof Zend_Log) { + return; + } + Zend_Cache::throwException('Logger object is not an instance of Zend_Log class.'); + } + + // Create a default logger to the standard output stream + require_once 'Zend/Log.php'; + require_once 'Zend/Log/Writer/Stream.php'; + require_once 'Zend/Log/Filter/Priority.php'; + $logger = new Zend_Log(new Zend_Log_Writer_Stream('php://output')); + $logger->addFilter(new Zend_Log_Filter_Priority(Zend_Log::WARN, '<=')); + $this->_directives['logger'] = $logger; + } + + /** + * Log a message at the WARN (4) priority. + * + * @param string $message + * @throws Zend_Cache_Exception + * @return void + */ + protected function _log($message, $priority = 4) + { + if (!$this->_directives['logging']) { + return; + } + + if (!isset($this->_directives['logger'])) { + Zend_Cache::throwException('Logging is enabled but logger is not set.'); + } + $logger = $this->_directives['logger']; + if (!$logger instanceof Zend_Log) { + Zend_Cache::throwException('Logger object is not an instance of Zend_Log class.'); + } + $logger->log($message, $priority); + } +} diff --git a/library/Zend/Cache/Backend/Apc.php b/library/Zend/Cache/Backend/Apc.php new file mode 100644 index 00000000..7616742a --- /dev/null +++ b/library/Zend/Cache/Backend/Apc.php @@ -0,0 +1,355 @@ + infinite lifetime) + * @return boolean true if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + $lifetime = $this->getLifetime($specificLifetime); + $result = apc_store($id, array($data, time(), $lifetime), $lifetime); + if (count($tags) > 0) { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_APC_BACKEND); + } + return $result; + } + + /** + * Remove a cache record + * + * @param string $id cache id + * @return boolean true if no problem + */ + public function remove($id) + { + return apc_delete($id); + } + + /** + * Clean some cache records + * + * Available modes are : + * 'all' (default) => remove all cache entries ($tags is not used) + * 'old' => unsupported + * 'matchingTag' => unsupported + * 'notMatchingTag' => unsupported + * 'matchingAnyTag' => unsupported + * + * @param string $mode clean mode + * @param array $tags array of tags + * @throws Zend_Cache_Exception + * @return boolean true if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + switch ($mode) { + case Zend_Cache::CLEANING_MODE_ALL: + return apc_clear_cache('user'); + break; + case Zend_Cache::CLEANING_MODE_OLD: + $this->_log("Zend_Cache_Backend_Apc::clean() : CLEANING_MODE_OLD is unsupported by the Apc backend"); + break; + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + $this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_APC_BACKEND); + break; + default: + Zend_Cache::throwException('Invalid mode for clean() method'); + break; + } + } + + /** + * Return true if the automatic cleaning is available for the backend + * + * DEPRECATED : use getCapabilities() instead + * + * @deprecated + * @return boolean + */ + public function isAutomaticCleaningAvailable() + { + return false; + } + + /** + * Return the filling percentage of the backend storage + * + * @throws Zend_Cache_Exception + * @return int integer between 0 and 100 + */ + public function getFillingPercentage() + { + $mem = apc_sma_info(true); + $memSize = $mem['num_seg'] * $mem['seg_size']; + $memAvailable= $mem['avail_mem']; + $memUsed = $memSize - $memAvailable; + if ($memSize == 0) { + Zend_Cache::throwException('can\'t get apc memory size'); + } + if ($memUsed > $memSize) { + return 100; + } + return ((int) (100. * ($memUsed / $memSize))); + } + + /** + * Return an array of stored tags + * + * @return array array of stored tags (string) + */ + public function getTags() + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_APC_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which match given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of matching cache ids (string) + */ + public function getIdsMatchingTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_APC_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which don't match given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of not matching cache ids (string) + */ + public function getIdsNotMatchingTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_APC_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which match any given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of any matching cache ids (string) + */ + public function getIdsMatchingAnyTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_APC_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids + * + * @return array array of stored cache ids (string) + */ + public function getIds() + { + $res = array(); + $array = apc_cache_info('user', false); + $records = $array['cache_list']; + foreach ($records as $record) { + $res[] = $record['info']; + } + return $res; + } + + /** + * Return an array of metadatas for the given cache id + * + * The array must include these keys : + * - expire : the expire timestamp + * - tags : a string array of tags + * - mtime : timestamp of last modification time + * + * @param string $id cache id + * @return array array of metadatas (false if the cache id is not found) + */ + public function getMetadatas($id) + { + $tmp = apc_fetch($id); + if (is_array($tmp)) { + $data = $tmp[0]; + $mtime = $tmp[1]; + if (!isset($tmp[2])) { + // because this record is only with 1.7 release + // if old cache records are still there... + return false; + } + $lifetime = $tmp[2]; + return array( + 'expire' => $mtime + $lifetime, + 'tags' => array(), + 'mtime' => $mtime + ); + } + return false; + } + + /** + * Give (if possible) an extra lifetime to the given cache id + * + * @param string $id cache id + * @param int $extraLifetime + * @return boolean true if ok + */ + public function touch($id, $extraLifetime) + { + $tmp = apc_fetch($id); + if (is_array($tmp)) { + $data = $tmp[0]; + $mtime = $tmp[1]; + if (!isset($tmp[2])) { + // because this record is only with 1.7 release + // if old cache records are still there... + return false; + } + $lifetime = $tmp[2]; + $newLifetime = $lifetime - (time() - $mtime) + $extraLifetime; + if ($newLifetime <=0) { + return false; + } + apc_store($id, array($data, time(), $newLifetime), $newLifetime); + return true; + } + return false; + } + + /** + * Return an associative array of capabilities (booleans) of the backend + * + * The array must include these keys : + * - automatic_cleaning (is automating cleaning necessary) + * - tags (are tags supported) + * - expired_read (is it possible to read expired cache records + * (for doNotTestCacheValidity option for example)) + * - priority does the backend deal with priority when saving + * - infinite_lifetime (is infinite lifetime can work with this backend) + * - get_list (is it possible to get the list of cache ids and the complete list of tags) + * + * @return array associative of with capabilities + */ + public function getCapabilities() + { + return array( + 'automatic_cleaning' => false, + 'tags' => false, + 'expired_read' => false, + 'priority' => false, + 'infinite_lifetime' => false, + 'get_list' => true + ); + } + +} diff --git a/library/Zend/Cache/Backend/BlackHole.php b/library/Zend/Cache/Backend/BlackHole.php new file mode 100644 index 00000000..577fcdad --- /dev/null +++ b/library/Zend/Cache/Backend/BlackHole.php @@ -0,0 +1,250 @@ + infinite lifetime) + * @return boolean true if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + return true; + } + + /** + * Remove a cache record + * + * @param string $id cache id + * @return boolean true if no problem + */ + public function remove($id) + { + return true; + } + + /** + * Clean some cache records + * + * Available modes are : + * 'all' (default) => remove all cache entries ($tags is not used) + * 'old' => remove too old cache entries ($tags is not used) + * 'matchingTag' => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * 'notMatchingTag' => remove cache entries not matching one of the given tags + * ($tags can be an array of strings or a single string) + * 'matchingAnyTag' => remove cache entries matching any given tags + * ($tags can be an array of strings or a single string) + * + * @param string $mode clean mode + * @param tags array $tags array of tags + * @return boolean true if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + return true; + } + + /** + * Return an array of stored cache ids + * + * @return array array of stored cache ids (string) + */ + public function getIds() + { + return array(); + } + + /** + * Return an array of stored tags + * + * @return array array of stored tags (string) + */ + public function getTags() + { + return array(); + } + + /** + * Return an array of stored cache ids which match given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of matching cache ids (string) + */ + public function getIdsMatchingTags($tags = array()) + { + return array(); + } + + /** + * Return an array of stored cache ids which don't match given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of not matching cache ids (string) + */ + public function getIdsNotMatchingTags($tags = array()) + { + return array(); + } + + /** + * Return an array of stored cache ids which match any given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of any matching cache ids (string) + */ + public function getIdsMatchingAnyTags($tags = array()) + { + return array(); + } + + /** + * Return the filling percentage of the backend storage + * + * @return int integer between 0 and 100 + * @throws Zend_Cache_Exception + */ + public function getFillingPercentage() + { + return 0; + } + + /** + * Return an array of metadatas for the given cache id + * + * The array must include these keys : + * - expire : the expire timestamp + * - tags : a string array of tags + * - mtime : timestamp of last modification time + * + * @param string $id cache id + * @return array array of metadatas (false if the cache id is not found) + */ + public function getMetadatas($id) + { + return false; + } + + /** + * Give (if possible) an extra lifetime to the given cache id + * + * @param string $id cache id + * @param int $extraLifetime + * @return boolean true if ok + */ + public function touch($id, $extraLifetime) + { + return false; + } + + /** + * Return an associative array of capabilities (booleans) of the backend + * + * The array must include these keys : + * - automatic_cleaning (is automating cleaning necessary) + * - tags (are tags supported) + * - expired_read (is it possible to read expired cache records + * (for doNotTestCacheValidity option for example)) + * - priority does the backend deal with priority when saving + * - infinite_lifetime (is infinite lifetime can work with this backend) + * - get_list (is it possible to get the list of cache ids and the complete list of tags) + * + * @return array associative of with capabilities + */ + public function getCapabilities() + { + return array( + 'automatic_cleaning' => true, + 'tags' => true, + 'expired_read' => true, + 'priority' => true, + 'infinite_lifetime' => true, + 'get_list' => true, + ); + } + + /** + * PUBLIC METHOD FOR UNIT TESTING ONLY ! + * + * Force a cache record to expire + * + * @param string $id cache id + */ + public function ___expire($id) + { + } +} diff --git a/library/Zend/Cache/Backend/ExtendedInterface.php b/library/Zend/Cache/Backend/ExtendedInterface.php new file mode 100644 index 00000000..d4039171 --- /dev/null +++ b/library/Zend/Cache/Backend/ExtendedInterface.php @@ -0,0 +1,126 @@ + (string) cache_dir : + * - Directory where to put the cache files + * + * =====> (boolean) file_locking : + * - Enable / disable file_locking + * - Can avoid cache corruption under bad circumstances but it doesn't work on multithread + * webservers and on NFS filesystems for example + * + * =====> (boolean) read_control : + * - Enable / disable read control + * - If enabled, a control key is embeded in cache file and this key is compared with the one + * calculated after the reading. + * + * =====> (string) read_control_type : + * - Type of read control (only if read control is enabled). Available values are : + * 'md5' for a md5 hash control (best but slowest) + * 'crc32' for a crc32 hash control (lightly less safe but faster, better choice) + * 'adler32' for an adler32 hash control (excellent choice too, faster than crc32) + * 'strlen' for a length only test (fastest) + * + * =====> (int) hashed_directory_level : + * - Hashed directory level + * - Set the hashed directory structure level. 0 means "no hashed directory + * structure", 1 means "one level of directory", 2 means "two levels"... + * This option can speed up the cache only when you have many thousands of + * cache file. Only specific benchs can help you to choose the perfect value + * for you. Maybe, 1 or 2 is a good start. + * + * =====> (int) hashed_directory_umask : + * - Umask for hashed directory structure + * + * =====> (string) file_name_prefix : + * - prefix for cache files + * - be really carefull with this option because a too generic value in a system cache dir + * (like /tmp) can cause disasters when cleaning the cache + * + * =====> (int) cache_file_umask : + * - Umask for cache files + * + * =====> (int) metatadatas_array_max_size : + * - max size for the metadatas array (don't change this value unless you + * know what you are doing) + * + * @var array available options + */ + protected $_options = array( + 'cache_dir' => null, + 'file_locking' => true, + 'read_control' => true, + 'read_control_type' => 'crc32', + 'hashed_directory_level' => 0, + 'hashed_directory_umask' => 0700, + 'file_name_prefix' => 'zend_cache', + 'cache_file_umask' => 0600, + 'metadatas_array_max_size' => 100 + ); + + /** + * Array of metadatas (each item is an associative array) + * + * @var array + */ + protected $_metadatasArray = array(); + + + /** + * Constructor + * + * @param array $options associative array of options + * @throws Zend_Cache_Exception + * @return void + */ + public function __construct(array $options = array()) + { + parent::__construct($options); + if ($this->_options['cache_dir'] !== null) { // particular case for this option + $this->setCacheDir($this->_options['cache_dir']); + } else { + $this->setCacheDir(self::getTmpDir() . DIRECTORY_SEPARATOR, false); + } + if (isset($this->_options['file_name_prefix'])) { // particular case for this option + if (!preg_match('~^[a-zA-Z0-9_]+$~D', $this->_options['file_name_prefix'])) { + Zend_Cache::throwException('Invalid file_name_prefix : must use only [a-zA-Z0-9_]'); + } + } + if ($this->_options['metadatas_array_max_size'] < 10) { + Zend_Cache::throwException('Invalid metadatas_array_max_size, must be > 10'); + } + if (isset($options['hashed_directory_umask']) && is_string($options['hashed_directory_umask'])) { + // See #ZF-4422 + $this->_options['hashed_directory_umask'] = octdec($this->_options['hashed_directory_umask']); + } + if (isset($options['cache_file_umask']) && is_string($options['cache_file_umask'])) { + // See #ZF-4422 + $this->_options['cache_file_umask'] = octdec($this->_options['cache_file_umask']); + } + } + + /** + * Set the cache_dir (particular case of setOption() method) + * + * @param string $value + * @param boolean $trailingSeparator If true, add a trailing separator is necessary + * @throws Zend_Cache_Exception + * @return void + */ + public function setCacheDir($value, $trailingSeparator = true) + { + if (!is_dir($value)) { + Zend_Cache::throwException('cache_dir must be a directory'); + } + if (!is_writable($value)) { + Zend_Cache::throwException('cache_dir is not writable'); + } + if ($trailingSeparator) { + // add a trailing DIRECTORY_SEPARATOR if necessary + $value = rtrim(realpath($value), '\\/') . DIRECTORY_SEPARATOR; + } + $this->_options['cache_dir'] = $value; + } + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * @param string $id cache id + * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested + * @return string|false cached datas + */ + public function load($id, $doNotTestCacheValidity = false) + { + if (!($this->_test($id, $doNotTestCacheValidity))) { + // The cache is not hit ! + return false; + } + $metadatas = $this->_getMetadatas($id); + $file = $this->_file($id); + $data = $this->_fileGetContents($file); + if ($this->_options['read_control']) { + $hashData = $this->_hash($data, $this->_options['read_control_type']); + $hashControl = $metadatas['hash']; + if ($hashData != $hashControl) { + // Problem detected by the read control ! + $this->_log('Zend_Cache_Backend_File::load() / read_control : stored hash and computed hash do not match'); + $this->remove($id); + return false; + } + } + return $data; + } + + /** + * Test if a cache is available or not (for the given id) + * + * @param string $id cache id + * @return mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record + */ + public function test($id) + { + clearstatcache(); + return $this->_test($id, false); + } + + /** + * Save some string datas into a cache record + * + * Note : $data is always "string" (serialization is done by the + * core not by the backend) + * + * @param string $data Datas to cache + * @param string $id Cache id + * @param array $tags Array of strings, the cache record will be tagged by each string entry + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @return boolean true if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + clearstatcache(); + $file = $this->_file($id); + $path = $this->_path($id); + if ($this->_options['hashed_directory_level'] > 0) { + if (!is_writable($path)) { + // maybe, we just have to build the directory structure + $this->_recursiveMkdirAndChmod($id); + } + if (!is_writable($path)) { + return false; + } + } + if ($this->_options['read_control']) { + $hash = $this->_hash($data, $this->_options['read_control_type']); + } else { + $hash = ''; + } + $metadatas = array( + 'hash' => $hash, + 'mtime' => time(), + 'expire' => $this->_expireTime($this->getLifetime($specificLifetime)), + 'tags' => $tags + ); + $res = $this->_setMetadatas($id, $metadatas); + if (!$res) { + $this->_log('Zend_Cache_Backend_File::save() / error on saving metadata'); + return false; + } + $res = $this->_filePutContents($file, $data); + return $res; + } + + /** + * Remove a cache record + * + * @param string $id cache id + * @return boolean true if no problem + */ + public function remove($id) + { + $file = $this->_file($id); + $boolRemove = $this->_remove($file); + $boolMetadata = $this->_delMetadatas($id); + return $boolMetadata && $boolRemove; + } + + /** + * Clean some cache records + * + * Available modes are : + * + * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags + * ($tags can be an array of strings or a single string) + * + * @param string $mode clean mode + * @param tags array $tags array of tags + * @return boolean true if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + // We use this protected method to hide the recursive stuff + clearstatcache(); + return $this->_clean($this->_options['cache_dir'], $mode, $tags); + } + + /** + * Return an array of stored cache ids + * + * @return array array of stored cache ids (string) + */ + public function getIds() + { + return $this->_get($this->_options['cache_dir'], 'ids', array()); + } + + /** + * Return an array of stored tags + * + * @return array array of stored tags (string) + */ + public function getTags() + { + return $this->_get($this->_options['cache_dir'], 'tags', array()); + } + + /** + * Return an array of stored cache ids which match given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of matching cache ids (string) + */ + public function getIdsMatchingTags($tags = array()) + { + return $this->_get($this->_options['cache_dir'], 'matching', $tags); + } + + /** + * Return an array of stored cache ids which don't match given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of not matching cache ids (string) + */ + public function getIdsNotMatchingTags($tags = array()) + { + return $this->_get($this->_options['cache_dir'], 'notMatching', $tags); + } + + /** + * Return an array of stored cache ids which match any given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of any matching cache ids (string) + */ + public function getIdsMatchingAnyTags($tags = array()) + { + return $this->_get($this->_options['cache_dir'], 'matchingAny', $tags); + } + + /** + * Return the filling percentage of the backend storage + * + * @throws Zend_Cache_Exception + * @return int integer between 0 and 100 + */ + public function getFillingPercentage() + { + $free = disk_free_space($this->_options['cache_dir']); + $total = disk_total_space($this->_options['cache_dir']); + if ($total == 0) { + Zend_Cache::throwException('can\'t get disk_total_space'); + } else { + if ($free >= $total) { + return 100; + } + return ((int) (100. * ($total - $free) / $total)); + } + } + + /** + * Return an array of metadatas for the given cache id + * + * The array must include these keys : + * - expire : the expire timestamp + * - tags : a string array of tags + * - mtime : timestamp of last modification time + * + * @param string $id cache id + * @return array array of metadatas (false if the cache id is not found) + */ + public function getMetadatas($id) + { + $metadatas = $this->_getMetadatas($id); + if (!$metadatas) { + return false; + } + if (time() > $metadatas['expire']) { + return false; + } + return array( + 'expire' => $metadatas['expire'], + 'tags' => $metadatas['tags'], + 'mtime' => $metadatas['mtime'] + ); + } + + /** + * Give (if possible) an extra lifetime to the given cache id + * + * @param string $id cache id + * @param int $extraLifetime + * @return boolean true if ok + */ + public function touch($id, $extraLifetime) + { + $metadatas = $this->_getMetadatas($id); + if (!$metadatas) { + return false; + } + if (time() > $metadatas['expire']) { + return false; + } + $newMetadatas = array( + 'hash' => $metadatas['hash'], + 'mtime' => time(), + 'expire' => $metadatas['expire'] + $extraLifetime, + 'tags' => $metadatas['tags'] + ); + $res = $this->_setMetadatas($id, $newMetadatas); + if (!$res) { + return false; + } + return true; + } + + /** + * Return an associative array of capabilities (booleans) of the backend + * + * The array must include these keys : + * - automatic_cleaning (is automating cleaning necessary) + * - tags (are tags supported) + * - expired_read (is it possible to read expired cache records + * (for doNotTestCacheValidity option for example)) + * - priority does the backend deal with priority when saving + * - infinite_lifetime (is infinite lifetime can work with this backend) + * - get_list (is it possible to get the list of cache ids and the complete list of tags) + * + * @return array associative of with capabilities + */ + public function getCapabilities() + { + return array( + 'automatic_cleaning' => true, + 'tags' => true, + 'expired_read' => true, + 'priority' => false, + 'infinite_lifetime' => true, + 'get_list' => true + ); + } + + /** + * PUBLIC METHOD FOR UNIT TESTING ONLY ! + * + * Force a cache record to expire + * + * @param string $id cache id + */ + public function ___expire($id) + { + $metadatas = $this->_getMetadatas($id); + if ($metadatas) { + $metadatas['expire'] = 1; + $this->_setMetadatas($id, $metadatas); + } + } + + /** + * Get a metadatas record + * + * @param string $id Cache id + * @return array|false Associative array of metadatas + */ + protected function _getMetadatas($id) + { + if (isset($this->_metadatasArray[$id])) { + return $this->_metadatasArray[$id]; + } else { + $metadatas = $this->_loadMetadatas($id); + if (!$metadatas) { + return false; + } + $this->_setMetadatas($id, $metadatas, false); + return $metadatas; + } + } + + /** + * Set a metadatas record + * + * @param string $id Cache id + * @param array $metadatas Associative array of metadatas + * @param boolean $save optional pass false to disable saving to file + * @return boolean True if no problem + */ + protected function _setMetadatas($id, $metadatas, $save = true) + { + if (count($this->_metadatasArray) >= $this->_options['metadatas_array_max_size']) { + $n = (int) ($this->_options['metadatas_array_max_size'] / 10); + $this->_metadatasArray = array_slice($this->_metadatasArray, $n); + } + if ($save) { + $result = $this->_saveMetadatas($id, $metadatas); + if (!$result) { + return false; + } + } + $this->_metadatasArray[$id] = $metadatas; + return true; + } + + /** + * Drop a metadata record + * + * @param string $id Cache id + * @return boolean True if no problem + */ + protected function _delMetadatas($id) + { + if (isset($this->_metadatasArray[$id])) { + unset($this->_metadatasArray[$id]); + } + $file = $this->_metadatasFile($id); + return $this->_remove($file); + } + + /** + * Clear the metadatas array + * + * @return void + */ + protected function _cleanMetadatas() + { + $this->_metadatasArray = array(); + } + + /** + * Load metadatas from disk + * + * @param string $id Cache id + * @return array|false Metadatas associative array + */ + protected function _loadMetadatas($id) + { + $file = $this->_metadatasFile($id); + $result = $this->_fileGetContents($file); + if (!$result) { + return false; + } + $tmp = @unserialize($result); + return $tmp; + } + + /** + * Save metadatas to disk + * + * @param string $id Cache id + * @param array $metadatas Associative array + * @return boolean True if no problem + */ + protected function _saveMetadatas($id, $metadatas) + { + $file = $this->_metadatasFile($id); + $result = $this->_filePutContents($file, serialize($metadatas)); + if (!$result) { + return false; + } + return true; + } + + /** + * Make and return a file name (with path) for metadatas + * + * @param string $id Cache id + * @return string Metadatas file name (with path) + */ + protected function _metadatasFile($id) + { + $path = $this->_path($id); + $fileName = $this->_idToFileName('internal-metadatas---' . $id); + return $path . $fileName; + } + + /** + * Check if the given filename is a metadatas one + * + * @param string $fileName File name + * @return boolean True if it's a metadatas one + */ + protected function _isMetadatasFile($fileName) + { + $id = $this->_fileNameToId($fileName); + if (substr($id, 0, 21) == 'internal-metadatas---') { + return true; + } else { + return false; + } + } + + /** + * Remove a file + * + * If we can't remove the file (because of locks or any problem), we will touch + * the file to invalidate it + * + * @param string $file Complete file path + * @return boolean True if ok + */ + protected function _remove($file) + { + if (!is_file($file)) { + return false; + } + if (!@unlink($file)) { + # we can't remove the file (because of locks or any problem) + $this->_log("Zend_Cache_Backend_File::_remove() : we can't remove $file"); + return false; + } + return true; + } + + /** + * Clean some cache records (protected method used for recursive stuff) + * + * Available modes are : + * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags + * ($tags can be an array of strings or a single string) + * + * @param string $dir Directory to clean + * @param string $mode Clean mode + * @param array $tags Array of tags + * @throws Zend_Cache_Exception + * @return boolean True if no problem + */ + protected function _clean($dir, $mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + if (!is_dir($dir)) { + return false; + } + $result = true; + $prefix = $this->_options['file_name_prefix']; + $glob = @glob($dir . $prefix . '--*'); + if ($glob === false) { + // On some systems it is impossible to distinguish between empty match and an error. + return true; + } + foreach ($glob as $file) { + if (is_file($file)) { + $fileName = basename($file); + if ($this->_isMetadatasFile($fileName)) { + // in CLEANING_MODE_ALL, we drop anything, even remainings old metadatas files + if ($mode != Zend_Cache::CLEANING_MODE_ALL) { + continue; + } + } + $id = $this->_fileNameToId($fileName); + $metadatas = $this->_getMetadatas($id); + if ($metadatas === FALSE) { + $metadatas = array('expire' => 1, 'tags' => array()); + } + switch ($mode) { + case Zend_Cache::CLEANING_MODE_ALL: + $res = $this->remove($id); + if (!$res) { + // in this case only, we accept a problem with the metadatas file drop + $res = $this->_remove($file); + } + $result = $result && $res; + break; + case Zend_Cache::CLEANING_MODE_OLD: + if (time() > $metadatas['expire']) { + $result = $this->remove($id) && $result; + } + break; + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + $matching = true; + foreach ($tags as $tag) { + if (!in_array($tag, $metadatas['tags'])) { + $matching = false; + break; + } + } + if ($matching) { + $result = $this->remove($id) && $result; + } + break; + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + $matching = false; + foreach ($tags as $tag) { + if (in_array($tag, $metadatas['tags'])) { + $matching = true; + break; + } + } + if (!$matching) { + $result = $this->remove($id) && $result; + } + break; + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + $matching = false; + foreach ($tags as $tag) { + if (in_array($tag, $metadatas['tags'])) { + $matching = true; + break; + } + } + if ($matching) { + $result = $this->remove($id) && $result; + } + break; + default: + Zend_Cache::throwException('Invalid mode for clean() method'); + break; + } + } + if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) { + // Recursive call + $result = $this->_clean($file . DIRECTORY_SEPARATOR, $mode, $tags) && $result; + if ($mode == Zend_Cache::CLEANING_MODE_ALL) { + // we try to drop the structure too + @rmdir($file); + } + } + } + return $result; + } + + protected function _get($dir, $mode, $tags = array()) + { + if (!is_dir($dir)) { + return false; + } + $result = array(); + $prefix = $this->_options['file_name_prefix']; + $glob = @glob($dir . $prefix . '--*'); + if ($glob === false) { + // On some systems it is impossible to distinguish between empty match and an error. + return array(); + } + foreach ($glob as $file) { + if (is_file($file)) { + $fileName = basename($file); + $id = $this->_fileNameToId($fileName); + $metadatas = $this->_getMetadatas($id); + if ($metadatas === FALSE) { + continue; + } + if (time() > $metadatas['expire']) { + continue; + } + switch ($mode) { + case 'ids': + $result[] = $id; + break; + case 'tags': + $result = array_unique(array_merge($result, $metadatas['tags'])); + break; + case 'matching': + $matching = true; + foreach ($tags as $tag) { + if (!in_array($tag, $metadatas['tags'])) { + $matching = false; + break; + } + } + if ($matching) { + $result[] = $id; + } + break; + case 'notMatching': + $matching = false; + foreach ($tags as $tag) { + if (in_array($tag, $metadatas['tags'])) { + $matching = true; + break; + } + } + if (!$matching) { + $result[] = $id; + } + break; + case 'matchingAny': + $matching = false; + foreach ($tags as $tag) { + if (in_array($tag, $metadatas['tags'])) { + $matching = true; + break; + } + } + if ($matching) { + $result[] = $id; + } + break; + default: + Zend_Cache::throwException('Invalid mode for _get() method'); + break; + } + } + if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) { + // Recursive call + $recursiveRs = $this->_get($file . DIRECTORY_SEPARATOR, $mode, $tags); + if ($recursiveRs === false) { + $this->_log('Zend_Cache_Backend_File::_get() / recursive call : can\'t list entries of "'.$file.'"'); + } else { + $result = array_unique(array_merge($result, $recursiveRs)); + } + } + } + return array_unique($result); + } + + /** + * Compute & return the expire time + * + * @return int expire time (unix timestamp) + */ + protected function _expireTime($lifetime) + { + if ($lifetime === null) { + return 9999999999; + } + return time() + $lifetime; + } + + /** + * Make a control key with the string containing datas + * + * @param string $data Data + * @param string $controlType Type of control 'md5', 'crc32' or 'strlen' + * @throws Zend_Cache_Exception + * @return string Control key + */ + protected function _hash($data, $controlType) + { + switch ($controlType) { + case 'md5': + return md5($data); + case 'crc32': + return crc32($data); + case 'strlen': + return strlen($data); + case 'adler32': + return hash('adler32', $data); + default: + Zend_Cache::throwException("Incorrect hash function : $controlType"); + } + } + + /** + * Transform a cache id into a file name and return it + * + * @param string $id Cache id + * @return string File name + */ + protected function _idToFileName($id) + { + $prefix = $this->_options['file_name_prefix']; + $result = $prefix . '---' . $id; + return $result; + } + + /** + * Make and return a file name (with path) + * + * @param string $id Cache id + * @return string File name (with path) + */ + protected function _file($id) + { + $path = $this->_path($id); + $fileName = $this->_idToFileName($id); + return $path . $fileName; + } + + /** + * Return the complete directory path of a filename (including hashedDirectoryStructure) + * + * @param string $id Cache id + * @param boolean $parts if true, returns array of directory parts instead of single string + * @return string Complete directory path + */ + protected function _path($id, $parts = false) + { + $partsArray = array(); + $root = $this->_options['cache_dir']; + $prefix = $this->_options['file_name_prefix']; + if ($this->_options['hashed_directory_level']>0) { + $hash = hash('adler32', $id); + for ($i=0 ; $i < $this->_options['hashed_directory_level'] ; $i++) { + $root = $root . $prefix . '--' . substr($hash, 0, $i + 1) . DIRECTORY_SEPARATOR; + $partsArray[] = $root; + } + } + if ($parts) { + return $partsArray; + } else { + return $root; + } + } + + /** + * Make the directory strucuture for the given id + * + * @param string $id cache id + * @return boolean true + */ + protected function _recursiveMkdirAndChmod($id) + { + if ($this->_options['hashed_directory_level'] <=0) { + return true; + } + $partsArray = $this->_path($id, true); + foreach ($partsArray as $part) { + if (!is_dir($part)) { + @mkdir($part, $this->_options['hashed_directory_umask']); + @chmod($part, $this->_options['hashed_directory_umask']); // see #ZF-320 (this line is required in some configurations) + } + } + return true; + } + + /** + * Test if the given cache id is available (and still valid as a cache record) + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @return boolean|mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record + */ + protected function _test($id, $doNotTestCacheValidity) + { + $metadatas = $this->_getMetadatas($id); + if (!$metadatas) { + return false; + } + if ($doNotTestCacheValidity || (time() <= $metadatas['expire'])) { + return $metadatas['mtime']; + } + return false; + } + + /** + * Return the file content of the given file + * + * @param string $file File complete path + * @return string File content (or false if problem) + */ + protected function _fileGetContents($file) + { + $result = false; + if (!is_file($file)) { + return false; + } + $f = @fopen($file, 'rb'); + if ($f) { + if ($this->_options['file_locking']) @flock($f, LOCK_SH); + $result = stream_get_contents($f); + if ($this->_options['file_locking']) @flock($f, LOCK_UN); + @fclose($f); + } + return $result; + } + + /** + * Put the given string into the given file + * + * @param string $file File complete path + * @param string $string String to put in file + * @return boolean true if no problem + */ + protected function _filePutContents($file, $string) + { + $result = false; + $f = @fopen($file, 'ab+'); + if ($f) { + if ($this->_options['file_locking']) @flock($f, LOCK_EX); + fseek($f, 0); + ftruncate($f, 0); + $tmp = @fwrite($f, $string); + if (!($tmp === FALSE)) { + $result = true; + } + @fclose($f); + } + @chmod($file, $this->_options['cache_file_umask']); + return $result; + } + + /** + * Transform a file name into cache id and return it + * + * @param string $fileName File name + * @return string Cache id + */ + protected function _fileNameToId($fileName) + { + $prefix = $this->_options['file_name_prefix']; + return preg_replace('~^' . $prefix . '---(.*)$~', '$1', $fileName); + } + +} diff --git a/library/Zend/Cache/Backend/Interface.php b/library/Zend/Cache/Backend/Interface.php new file mode 100644 index 00000000..1d58363d --- /dev/null +++ b/library/Zend/Cache/Backend/Interface.php @@ -0,0 +1,99 @@ + infinite lifetime) + * @return boolean true if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false); + + /** + * Remove a cache record + * + * @param string $id Cache id + * @return boolean True if no problem + */ + public function remove($id); + + /** + * Clean some cache records + * + * Available modes are : + * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags + * ($tags can be an array of strings or a single string) + * + * @param string $mode Clean mode + * @param array $tags Array of tags + * @return boolean true if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()); + +} diff --git a/library/Zend/Cache/Backend/Libmemcached.php b/library/Zend/Cache/Backend/Libmemcached.php new file mode 100644 index 00000000..b3a530a3 --- /dev/null +++ b/library/Zend/Cache/Backend/Libmemcached.php @@ -0,0 +1,484 @@ + (array) servers : + * an array of memcached server ; each memcached server is described by an associative array : + * 'host' => (string) : the name of the memcached server + * 'port' => (int) : the port of the memcached server + * 'weight' => (int) : number of buckets to create for this server which in turn control its + * probability of it being selected. The probability is relative to the total + * weight of all servers. + * =====> (array) client : + * an array of memcached client options ; the memcached client is described by an associative array : + * @see http://php.net/manual/memcached.constants.php + * - The option name can be the name of the constant without the prefix 'OPT_' + * or the integer value of this option constant + * + * @var array available options + */ + protected $_options = array( + 'servers' => array(array( + 'host' => self::DEFAULT_HOST, + 'port' => self::DEFAULT_PORT, + 'weight' => self::DEFAULT_WEIGHT, + )), + 'client' => array() + ); + + /** + * Memcached object + * + * @var mixed memcached object + */ + protected $_memcache = null; + + /** + * Constructor + * + * @param array $options associative array of options + * @throws Zend_Cache_Exception + * @return void + */ + public function __construct(array $options = array()) + { + if (!extension_loaded('memcached')) { + Zend_Cache::throwException('The memcached extension must be loaded for using this backend !'); + } + + // override default client options + $this->_options['client'] = array( + Memcached::OPT_DISTRIBUTION => Memcached::DISTRIBUTION_CONSISTENT, + Memcached::OPT_HASH => Memcached::HASH_MD5, + Memcached::OPT_LIBKETAMA_COMPATIBLE => true, + ); + + parent::__construct($options); + + if (isset($this->_options['servers'])) { + $value = $this->_options['servers']; + if (isset($value['host'])) { + // in this case, $value seems to be a simple associative array (one server only) + $value = array(0 => $value); // let's transform it into a classical array of associative arrays + } + $this->setOption('servers', $value); + } + $this->_memcache = new Memcached; + + // setup memcached client options + foreach ($this->_options['client'] as $name => $value) { + $optId = null; + if (is_int($name)) { + $optId = $name; + } else { + $optConst = 'Memcached::OPT_' . strtoupper($name); + if (defined($optConst)) { + $optId = constant($optConst); + } else { + $this->_log("Unknown memcached client option '{$name}' ({$optConst})"); + } + } + if ($optId) { + if (!$this->_memcache->setOption($optId, $value)) { + $this->_log("Setting memcached client option '{$optId}' failed"); + } + } + } + + // setup memcached servers + $servers = array(); + foreach ($this->_options['servers'] as $server) { + if (!array_key_exists('port', $server)) { + $server['port'] = self::DEFAULT_PORT; + } + if (!array_key_exists('weight', $server)) { + $server['weight'] = self::DEFAULT_WEIGHT; + } + + $servers[] = array($server['host'], $server['port'], $server['weight']); + } + $this->_memcache->addServers($servers); + } + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @return string|false cached datas + */ + public function load($id, $doNotTestCacheValidity = false) + { + $tmp = $this->_memcache->get($id); + if (isset($tmp[0])) { + return $tmp[0]; + } + return false; + } + + /** + * Test if a cache is available or not (for the given id) + * + * @param string $id Cache id + * @return int|false (a cache is not available) or "last modified" timestamp (int) of the available cache record + */ + public function test($id) + { + $tmp = $this->_memcache->get($id); + if (isset($tmp[0], $tmp[1])) { + return (int)$tmp[1]; + } + return false; + } + + /** + * Save some string datas into a cache record + * + * Note : $data is always "string" (serialization is done by the + * core not by the backend) + * + * @param string $data Datas to cache + * @param string $id Cache id + * @param array $tags Array of strings, the cache record will be tagged by each string entry + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @return boolean True if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + $lifetime = $this->getLifetime($specificLifetime); + + // ZF-8856: using set because add needs a second request if item already exists + $result = @$this->_memcache->set($id, array($data, time(), $lifetime), $lifetime); + if ($result === false) { + $rsCode = $this->_memcache->getResultCode(); + $rsMsg = $this->_memcache->getResultMessage(); + $this->_log("Memcached::set() failed: [{$rsCode}] {$rsMsg}"); + } + + if (count($tags) > 0) { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND); + } + + return $result; + } + + /** + * Remove a cache record + * + * @param string $id Cache id + * @return boolean True if no problem + */ + public function remove($id) + { + return $this->_memcache->delete($id); + } + + /** + * Clean some cache records + * + * Available modes are : + * 'all' (default) => remove all cache entries ($tags is not used) + * 'old' => unsupported + * 'matchingTag' => unsupported + * 'notMatchingTag' => unsupported + * 'matchingAnyTag' => unsupported + * + * @param string $mode Clean mode + * @param array $tags Array of tags + * @throws Zend_Cache_Exception + * @return boolean True if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + switch ($mode) { + case Zend_Cache::CLEANING_MODE_ALL: + return $this->_memcache->flush(); + break; + case Zend_Cache::CLEANING_MODE_OLD: + $this->_log("Zend_Cache_Backend_Libmemcached::clean() : CLEANING_MODE_OLD is unsupported by the Libmemcached backend"); + break; + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + $this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_LIBMEMCACHED_BACKEND); + break; + default: + Zend_Cache::throwException('Invalid mode for clean() method'); + break; + } + } + + /** + * Return true if the automatic cleaning is available for the backend + * + * @return boolean + */ + public function isAutomaticCleaningAvailable() + { + return false; + } + + /** + * Set the frontend directives + * + * @param array $directives Assoc of directives + * @throws Zend_Cache_Exception + * @return void + */ + public function setDirectives($directives) + { + parent::setDirectives($directives); + $lifetime = $this->getLifetime(false); + if ($lifetime > 2592000) { + // #ZF-3490 : For the memcached backend, there is a lifetime limit of 30 days (2592000 seconds) + $this->_log('memcached backend has a limit of 30 days (2592000 seconds) for the lifetime'); + } + if ($lifetime === null) { + // #ZF-4614 : we tranform null to zero to get the maximal lifetime + parent::setDirectives(array('lifetime' => 0)); + } + } + + /** + * Return an array of stored cache ids + * + * @return array array of stored cache ids (string) + */ + public function getIds() + { + $this->_log("Zend_Cache_Backend_Libmemcached::save() : getting the list of cache ids is unsupported by the Libmemcached backend"); + return array(); + } + + /** + * Return an array of stored tags + * + * @return array array of stored tags (string) + */ + public function getTags() + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which match given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of matching cache ids (string) + */ + public function getIdsMatchingTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which don't match given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of not matching cache ids (string) + */ + public function getIdsNotMatchingTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which match any given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of any matching cache ids (string) + */ + public function getIdsMatchingAnyTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND); + return array(); + } + + /** + * Return the filling percentage of the backend storage + * + * @throws Zend_Cache_Exception + * @return int integer between 0 and 100 + */ + public function getFillingPercentage() + { + $mems = $this->_memcache->getStats(); + if ($mems === false) { + return 0; + } + + $memSize = null; + $memUsed = null; + foreach ($mems as $key => $mem) { + if ($mem === false) { + $this->_log('can\'t get stat from ' . $key); + continue; + } + + $eachSize = $mem['limit_maxbytes']; + $eachUsed = $mem['bytes']; + if ($eachUsed > $eachSize) { + $eachUsed = $eachSize; + } + + $memSize += $eachSize; + $memUsed += $eachUsed; + } + + if ($memSize === null || $memUsed === null) { + Zend_Cache::throwException('Can\'t get filling percentage'); + } + + return ((int) (100. * ($memUsed / $memSize))); + } + + /** + * Return an array of metadatas for the given cache id + * + * The array must include these keys : + * - expire : the expire timestamp + * - tags : a string array of tags + * - mtime : timestamp of last modification time + * + * @param string $id cache id + * @return array array of metadatas (false if the cache id is not found) + */ + public function getMetadatas($id) + { + $tmp = $this->_memcache->get($id); + if (isset($tmp[0], $tmp[1], $tmp[2])) { + $data = $tmp[0]; + $mtime = $tmp[1]; + $lifetime = $tmp[2]; + return array( + 'expire' => $mtime + $lifetime, + 'tags' => array(), + 'mtime' => $mtime + ); + } + + return false; + } + + /** + * Give (if possible) an extra lifetime to the given cache id + * + * @param string $id cache id + * @param int $extraLifetime + * @return boolean true if ok + */ + public function touch($id, $extraLifetime) + { + $tmp = $this->_memcache->get($id); + if (isset($tmp[0], $tmp[1], $tmp[2])) { + $data = $tmp[0]; + $mtime = $tmp[1]; + $lifetime = $tmp[2]; + $newLifetime = $lifetime - (time() - $mtime) + $extraLifetime; + if ($newLifetime <=0) { + return false; + } + // #ZF-5702 : we try replace() first becase set() seems to be slower + if (!($result = $this->_memcache->replace($id, array($data, time(), $newLifetime), $newLifetime))) { + $result = $this->_memcache->set($id, array($data, time(), $newLifetime), $newLifetime); + if ($result === false) { + $rsCode = $this->_memcache->getResultCode(); + $rsMsg = $this->_memcache->getResultMessage(); + $this->_log("Memcached::set() failed: [{$rsCode}] {$rsMsg}"); + } + } + return $result; + } + return false; + } + + /** + * Return an associative array of capabilities (booleans) of the backend + * + * The array must include these keys : + * - automatic_cleaning (is automating cleaning necessary) + * - tags (are tags supported) + * - expired_read (is it possible to read expired cache records + * (for doNotTestCacheValidity option for example)) + * - priority does the backend deal with priority when saving + * - infinite_lifetime (is infinite lifetime can work with this backend) + * - get_list (is it possible to get the list of cache ids and the complete list of tags) + * + * @return array associative of with capabilities + */ + public function getCapabilities() + { + return array( + 'automatic_cleaning' => false, + 'tags' => false, + 'expired_read' => false, + 'priority' => false, + 'infinite_lifetime' => false, + 'get_list' => false + ); + } + +} diff --git a/library/Zend/Cache/Backend/Memcached.php b/library/Zend/Cache/Backend/Memcached.php new file mode 100644 index 00000000..f8e228f9 --- /dev/null +++ b/library/Zend/Cache/Backend/Memcached.php @@ -0,0 +1,504 @@ + (array) servers : + * an array of memcached server ; each memcached server is described by an associative array : + * 'host' => (string) : the name of the memcached server + * 'port' => (int) : the port of the memcached server + * 'persistent' => (bool) : use or not persistent connections to this memcached server + * 'weight' => (int) : number of buckets to create for this server which in turn control its + * probability of it being selected. The probability is relative to the total + * weight of all servers. + * 'timeout' => (int) : value in seconds which will be used for connecting to the daemon. Think twice + * before changing the default value of 1 second - you can lose all the + * advantages of caching if your connection is too slow. + * 'retry_interval' => (int) : controls how often a failed server will be retried, the default value + * is 15 seconds. Setting this parameter to -1 disables automatic retry. + * 'status' => (bool) : controls if the server should be flagged as online. + * 'failure_callback' => (callback) : Allows the user to specify a callback function to run upon + * encountering an error. The callback is run before failover + * is attempted. The function takes two parameters, the hostname + * and port of the failed server. + * + * =====> (boolean) compression : + * true if you want to use on-the-fly compression + * + * =====> (boolean) compatibility : + * true if you use old memcache server or extension + * + * @var array available options + */ + protected $_options = array( + 'servers' => array(array( + 'host' => self::DEFAULT_HOST, + 'port' => self::DEFAULT_PORT, + 'persistent' => self::DEFAULT_PERSISTENT, + 'weight' => self::DEFAULT_WEIGHT, + 'timeout' => self::DEFAULT_TIMEOUT, + 'retry_interval' => self::DEFAULT_RETRY_INTERVAL, + 'status' => self::DEFAULT_STATUS, + 'failure_callback' => self::DEFAULT_FAILURE_CALLBACK + )), + 'compression' => false, + 'compatibility' => false, + ); + + /** + * Memcache object + * + * @var mixed memcache object + */ + protected $_memcache = null; + + /** + * Constructor + * + * @param array $options associative array of options + * @throws Zend_Cache_Exception + * @return void + */ + public function __construct(array $options = array()) + { + if (!extension_loaded('memcache')) { + Zend_Cache::throwException('The memcache extension must be loaded for using this backend !'); + } + parent::__construct($options); + if (isset($this->_options['servers'])) { + $value= $this->_options['servers']; + if (isset($value['host'])) { + // in this case, $value seems to be a simple associative array (one server only) + $value = array(0 => $value); // let's transform it into a classical array of associative arrays + } + $this->setOption('servers', $value); + } + $this->_memcache = new Memcache; + foreach ($this->_options['servers'] as $server) { + if (!array_key_exists('port', $server)) { + $server['port'] = self::DEFAULT_PORT; + } + if (!array_key_exists('persistent', $server)) { + $server['persistent'] = self::DEFAULT_PERSISTENT; + } + if (!array_key_exists('weight', $server)) { + $server['weight'] = self::DEFAULT_WEIGHT; + } + if (!array_key_exists('timeout', $server)) { + $server['timeout'] = self::DEFAULT_TIMEOUT; + } + if (!array_key_exists('retry_interval', $server)) { + $server['retry_interval'] = self::DEFAULT_RETRY_INTERVAL; + } + if (!array_key_exists('status', $server)) { + $server['status'] = self::DEFAULT_STATUS; + } + if (!array_key_exists('failure_callback', $server)) { + $server['failure_callback'] = self::DEFAULT_FAILURE_CALLBACK; + } + if ($this->_options['compatibility']) { + // No status for compatibility mode (#ZF-5887) + $this->_memcache->addServer($server['host'], $server['port'], $server['persistent'], + $server['weight'], $server['timeout'], + $server['retry_interval']); + } else { + $this->_memcache->addServer($server['host'], $server['port'], $server['persistent'], + $server['weight'], $server['timeout'], + $server['retry_interval'], + $server['status'], $server['failure_callback']); + } + } + } + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @return string|false cached datas + */ + public function load($id, $doNotTestCacheValidity = false) + { + $tmp = $this->_memcache->get($id); + if (is_array($tmp) && isset($tmp[0])) { + return $tmp[0]; + } + return false; + } + + /** + * Test if a cache is available or not (for the given id) + * + * @param string $id Cache id + * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record + */ + public function test($id) + { + $tmp = $this->_memcache->get($id); + if (is_array($tmp)) { + return $tmp[1]; + } + return false; + } + + /** + * Save some string datas into a cache record + * + * Note : $data is always "string" (serialization is done by the + * core not by the backend) + * + * @param string $data Datas to cache + * @param string $id Cache id + * @param array $tags Array of strings, the cache record will be tagged by each string entry + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @return boolean True if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + $lifetime = $this->getLifetime($specificLifetime); + if ($this->_options['compression']) { + $flag = MEMCACHE_COMPRESSED; + } else { + $flag = 0; + } + + // ZF-8856: using set because add needs a second request if item already exists + $result = @$this->_memcache->set($id, array($data, time(), $lifetime), $flag, $lifetime); + + if (count($tags) > 0) { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND); + } + + return $result; + } + + /** + * Remove a cache record + * + * @param string $id Cache id + * @return boolean True if no problem + */ + public function remove($id) + { + return $this->_memcache->delete($id, 0); + } + + /** + * Clean some cache records + * + * Available modes are : + * 'all' (default) => remove all cache entries ($tags is not used) + * 'old' => unsupported + * 'matchingTag' => unsupported + * 'notMatchingTag' => unsupported + * 'matchingAnyTag' => unsupported + * + * @param string $mode Clean mode + * @param array $tags Array of tags + * @throws Zend_Cache_Exception + * @return boolean True if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + switch ($mode) { + case Zend_Cache::CLEANING_MODE_ALL: + return $this->_memcache->flush(); + break; + case Zend_Cache::CLEANING_MODE_OLD: + $this->_log("Zend_Cache_Backend_Memcached::clean() : CLEANING_MODE_OLD is unsupported by the Memcached backend"); + break; + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + $this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_MEMCACHED_BACKEND); + break; + default: + Zend_Cache::throwException('Invalid mode for clean() method'); + break; + } + } + + /** + * Return true if the automatic cleaning is available for the backend + * + * @return boolean + */ + public function isAutomaticCleaningAvailable() + { + return false; + } + + /** + * Set the frontend directives + * + * @param array $directives Assoc of directives + * @throws Zend_Cache_Exception + * @return void + */ + public function setDirectives($directives) + { + parent::setDirectives($directives); + $lifetime = $this->getLifetime(false); + if ($lifetime > 2592000) { + // #ZF-3490 : For the memcached backend, there is a lifetime limit of 30 days (2592000 seconds) + $this->_log('memcached backend has a limit of 30 days (2592000 seconds) for the lifetime'); + } + if ($lifetime === null) { + // #ZF-4614 : we tranform null to zero to get the maximal lifetime + parent::setDirectives(array('lifetime' => 0)); + } + } + + /** + * Return an array of stored cache ids + * + * @return array array of stored cache ids (string) + */ + public function getIds() + { + $this->_log("Zend_Cache_Backend_Memcached::save() : getting the list of cache ids is unsupported by the Memcache backend"); + return array(); + } + + /** + * Return an array of stored tags + * + * @return array array of stored tags (string) + */ + public function getTags() + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which match given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of matching cache ids (string) + */ + public function getIdsMatchingTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which don't match given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of not matching cache ids (string) + */ + public function getIdsNotMatchingTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which match any given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of any matching cache ids (string) + */ + public function getIdsMatchingAnyTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND); + return array(); + } + + /** + * Return the filling percentage of the backend storage + * + * @throws Zend_Cache_Exception + * @return int integer between 0 and 100 + */ + public function getFillingPercentage() + { + $mems = $this->_memcache->getExtendedStats(); + + $memSize = null; + $memUsed = null; + foreach ($mems as $key => $mem) { + if ($mem === false) { + $this->_log('can\'t get stat from ' . $key); + continue; + } + + $eachSize = $mem['limit_maxbytes']; + $eachUsed = $mem['bytes']; + if ($eachUsed > $eachSize) { + $eachUsed = $eachSize; + } + + $memSize += $eachSize; + $memUsed += $eachUsed; + } + + if ($memSize === null || $memUsed === null) { + Zend_Cache::throwException('Can\'t get filling percentage'); + } + + return ((int) (100. * ($memUsed / $memSize))); + } + + /** + * Return an array of metadatas for the given cache id + * + * The array must include these keys : + * - expire : the expire timestamp + * - tags : a string array of tags + * - mtime : timestamp of last modification time + * + * @param string $id cache id + * @return array array of metadatas (false if the cache id is not found) + */ + public function getMetadatas($id) + { + $tmp = $this->_memcache->get($id); + if (is_array($tmp)) { + $data = $tmp[0]; + $mtime = $tmp[1]; + if (!isset($tmp[2])) { + // because this record is only with 1.7 release + // if old cache records are still there... + return false; + } + $lifetime = $tmp[2]; + return array( + 'expire' => $mtime + $lifetime, + 'tags' => array(), + 'mtime' => $mtime + ); + } + return false; + } + + /** + * Give (if possible) an extra lifetime to the given cache id + * + * @param string $id cache id + * @param int $extraLifetime + * @return boolean true if ok + */ + public function touch($id, $extraLifetime) + { + if ($this->_options['compression']) { + $flag = MEMCACHE_COMPRESSED; + } else { + $flag = 0; + } + $tmp = $this->_memcache->get($id); + if (is_array($tmp)) { + $data = $tmp[0]; + $mtime = $tmp[1]; + if (!isset($tmp[2])) { + // because this record is only with 1.7 release + // if old cache records are still there... + return false; + } + $lifetime = $tmp[2]; + $newLifetime = $lifetime - (time() - $mtime) + $extraLifetime; + if ($newLifetime <=0) { + return false; + } + // #ZF-5702 : we try replace() first becase set() seems to be slower + if (!($result = $this->_memcache->replace($id, array($data, time(), $newLifetime), $flag, $newLifetime))) { + $result = $this->_memcache->set($id, array($data, time(), $newLifetime), $flag, $newLifetime); + } + return $result; + } + return false; + } + + /** + * Return an associative array of capabilities (booleans) of the backend + * + * The array must include these keys : + * - automatic_cleaning (is automating cleaning necessary) + * - tags (are tags supported) + * - expired_read (is it possible to read expired cache records + * (for doNotTestCacheValidity option for example)) + * - priority does the backend deal with priority when saving + * - infinite_lifetime (is infinite lifetime can work with this backend) + * - get_list (is it possible to get the list of cache ids and the complete list of tags) + * + * @return array associative of with capabilities + */ + public function getCapabilities() + { + return array( + 'automatic_cleaning' => false, + 'tags' => false, + 'expired_read' => false, + 'priority' => false, + 'infinite_lifetime' => false, + 'get_list' => false + ); + } + +} diff --git a/library/Zend/Cache/Backend/Sqlite.php b/library/Zend/Cache/Backend/Sqlite.php new file mode 100644 index 00000000..5b964a1c --- /dev/null +++ b/library/Zend/Cache/Backend/Sqlite.php @@ -0,0 +1,679 @@ + (string) cache_db_complete_path : + * - the complete path (filename included) of the SQLITE database + * + * ====> (int) automatic_vacuum_factor : + * - Disable / Tune the automatic vacuum process + * - The automatic vacuum process defragment the database file (and make it smaller) + * when a clean() or delete() is called + * 0 => no automatic vacuum + * 1 => systematic vacuum (when delete() or clean() methods are called) + * x (integer) > 1 => automatic vacuum randomly 1 times on x clean() or delete() + * + * @var array Available options + */ + protected $_options = array( + 'cache_db_complete_path' => null, + 'automatic_vacuum_factor' => 10 + ); + + /** + * DB ressource + * + * @var mixed $_db + */ + private $_db = null; + + /** + * Boolean to store if the structure has benn checked or not + * + * @var boolean $_structureChecked + */ + private $_structureChecked = false; + + /** + * Constructor + * + * @param array $options Associative array of options + * @throws Zend_cache_Exception + * @return void + */ + public function __construct(array $options = array()) + { + parent::__construct($options); + if ($this->_options['cache_db_complete_path'] === null) { + Zend_Cache::throwException('cache_db_complete_path option has to set'); + } + if (!extension_loaded('sqlite')) { + Zend_Cache::throwException("Cannot use SQLite storage because the 'sqlite' extension is not loaded in the current PHP environment"); + } + $this->_getConnection(); + } + + /** + * Destructor + * + * @return void + */ + public function __destruct() + { + @sqlite_close($this->_getConnection()); + } + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @return string|false Cached datas + */ + public function load($id, $doNotTestCacheValidity = false) + { + $this->_checkAndBuildStructure(); + $sql = "SELECT content FROM cache WHERE id='$id'"; + if (!$doNotTestCacheValidity) { + $sql = $sql . " AND (expire=0 OR expire>" . time() . ')'; + } + $result = $this->_query($sql); + $row = @sqlite_fetch_array($result); + if ($row) { + return $row['content']; + } + return false; + } + + /** + * Test if a cache is available or not (for the given id) + * + * @param string $id Cache id + * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record + */ + public function test($id) + { + $this->_checkAndBuildStructure(); + $sql = "SELECT lastModified FROM cache WHERE id='$id' AND (expire=0 OR expire>" . time() . ')'; + $result = $this->_query($sql); + $row = @sqlite_fetch_array($result); + if ($row) { + return ((int) $row['lastModified']); + } + return false; + } + + /** + * Save some string datas into a cache record + * + * Note : $data is always "string" (serialization is done by the + * core not by the backend) + * + * @param string $data Datas to cache + * @param string $id Cache id + * @param array $tags Array of strings, the cache record will be tagged by each string entry + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @throws Zend_Cache_Exception + * @return boolean True if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + $this->_checkAndBuildStructure(); + $lifetime = $this->getLifetime($specificLifetime); + $data = @sqlite_escape_string($data); + $mktime = time(); + if ($lifetime === null) { + $expire = 0; + } else { + $expire = $mktime + $lifetime; + } + $this->_query("DELETE FROM cache WHERE id='$id'"); + $sql = "INSERT INTO cache (id, content, lastModified, expire) VALUES ('$id', '$data', $mktime, $expire)"; + $res = $this->_query($sql); + if (!$res) { + $this->_log("Zend_Cache_Backend_Sqlite::save() : impossible to store the cache id=$id"); + return false; + } + $res = true; + foreach ($tags as $tag) { + $res = $this->_registerTag($id, $tag) && $res; + } + return $res; + } + + /** + * Remove a cache record + * + * @param string $id Cache id + * @return boolean True if no problem + */ + public function remove($id) + { + $this->_checkAndBuildStructure(); + $res = $this->_query("SELECT COUNT(*) AS nbr FROM cache WHERE id='$id'"); + $result1 = @sqlite_fetch_single($res); + $result2 = $this->_query("DELETE FROM cache WHERE id='$id'"); + $result3 = $this->_query("DELETE FROM tag WHERE id='$id'"); + $this->_automaticVacuum(); + return ($result1 && $result2 && $result3); + } + + /** + * Clean some cache records + * + * Available modes are : + * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags + * ($tags can be an array of strings or a single string) + * + * @param string $mode Clean mode + * @param array $tags Array of tags + * @return boolean True if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + $this->_checkAndBuildStructure(); + $return = $this->_clean($mode, $tags); + $this->_automaticVacuum(); + return $return; + } + + /** + * Return an array of stored cache ids + * + * @return array array of stored cache ids (string) + */ + public function getIds() + { + $this->_checkAndBuildStructure(); + $res = $this->_query("SELECT id FROM cache WHERE (expire=0 OR expire>" . time() . ")"); + $result = array(); + while ($id = @sqlite_fetch_single($res)) { + $result[] = $id; + } + return $result; + } + + /** + * Return an array of stored tags + * + * @return array array of stored tags (string) + */ + public function getTags() + { + $this->_checkAndBuildStructure(); + $res = $this->_query("SELECT DISTINCT(name) AS name FROM tag"); + $result = array(); + while ($id = @sqlite_fetch_single($res)) { + $result[] = $id; + } + return $result; + } + + /** + * Return an array of stored cache ids which match given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of matching cache ids (string) + */ + public function getIdsMatchingTags($tags = array()) + { + $first = true; + $ids = array(); + foreach ($tags as $tag) { + $res = $this->_query("SELECT DISTINCT(id) AS id FROM tag WHERE name='$tag'"); + if (!$res) { + return array(); + } + $rows = @sqlite_fetch_all($res, SQLITE_ASSOC); + $ids2 = array(); + foreach ($rows as $row) { + $ids2[] = $row['id']; + } + if ($first) { + $ids = $ids2; + $first = false; + } else { + $ids = array_intersect($ids, $ids2); + } + } + $result = array(); + foreach ($ids as $id) { + $result[] = $id; + } + return $result; + } + + /** + * Return an array of stored cache ids which don't match given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of not matching cache ids (string) + */ + public function getIdsNotMatchingTags($tags = array()) + { + $res = $this->_query("SELECT id FROM cache"); + $rows = @sqlite_fetch_all($res, SQLITE_ASSOC); + $result = array(); + foreach ($rows as $row) { + $id = $row['id']; + $matching = false; + foreach ($tags as $tag) { + $res = $this->_query("SELECT COUNT(*) AS nbr FROM tag WHERE name='$tag' AND id='$id'"); + if (!$res) { + return array(); + } + $nbr = (int) @sqlite_fetch_single($res); + if ($nbr > 0) { + $matching = true; + } + } + if (!$matching) { + $result[] = $id; + } + } + return $result; + } + + /** + * Return an array of stored cache ids which match any given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of any matching cache ids (string) + */ + public function getIdsMatchingAnyTags($tags = array()) + { + $first = true; + $ids = array(); + foreach ($tags as $tag) { + $res = $this->_query("SELECT DISTINCT(id) AS id FROM tag WHERE name='$tag'"); + if (!$res) { + return array(); + } + $rows = @sqlite_fetch_all($res, SQLITE_ASSOC); + $ids2 = array(); + foreach ($rows as $row) { + $ids2[] = $row['id']; + } + if ($first) { + $ids = $ids2; + $first = false; + } else { + $ids = array_merge($ids, $ids2); + } + } + $result = array(); + foreach ($ids as $id) { + $result[] = $id; + } + return $result; + } + + /** + * Return the filling percentage of the backend storage + * + * @throws Zend_Cache_Exception + * @return int integer between 0 and 100 + */ + public function getFillingPercentage() + { + $dir = dirname($this->_options['cache_db_complete_path']); + $free = disk_free_space($dir); + $total = disk_total_space($dir); + if ($total == 0) { + Zend_Cache::throwException('can\'t get disk_total_space'); + } else { + if ($free >= $total) { + return 100; + } + return ((int) (100. * ($total - $free) / $total)); + } + } + + /** + * Return an array of metadatas for the given cache id + * + * The array must include these keys : + * - expire : the expire timestamp + * - tags : a string array of tags + * - mtime : timestamp of last modification time + * + * @param string $id cache id + * @return array array of metadatas (false if the cache id is not found) + */ + public function getMetadatas($id) + { + $tags = array(); + $res = $this->_query("SELECT name FROM tag WHERE id='$id'"); + if ($res) { + $rows = @sqlite_fetch_all($res, SQLITE_ASSOC); + foreach ($rows as $row) { + $tags[] = $row['name']; + } + } + $this->_query('CREATE TABLE cache (id TEXT PRIMARY KEY, content BLOB, lastModified INTEGER, expire INTEGER)'); + $res = $this->_query("SELECT lastModified,expire FROM cache WHERE id='$id'"); + if (!$res) { + return false; + } + $row = @sqlite_fetch_array($res, SQLITE_ASSOC); + return array( + 'tags' => $tags, + 'mtime' => $row['lastModified'], + 'expire' => $row['expire'] + ); + } + + /** + * Give (if possible) an extra lifetime to the given cache id + * + * @param string $id cache id + * @param int $extraLifetime + * @return boolean true if ok + */ + public function touch($id, $extraLifetime) + { + $sql = "SELECT expire FROM cache WHERE id='$id' AND (expire=0 OR expire>" . time() . ')'; + $res = $this->_query($sql); + if (!$res) { + return false; + } + $expire = @sqlite_fetch_single($res); + $newExpire = $expire + $extraLifetime; + $res = $this->_query("UPDATE cache SET lastModified=" . time() . ", expire=$newExpire WHERE id='$id'"); + if ($res) { + return true; + } else { + return false; + } + } + + /** + * Return an associative array of capabilities (booleans) of the backend + * + * The array must include these keys : + * - automatic_cleaning (is automating cleaning necessary) + * - tags (are tags supported) + * - expired_read (is it possible to read expired cache records + * (for doNotTestCacheValidity option for example)) + * - priority does the backend deal with priority when saving + * - infinite_lifetime (is infinite lifetime can work with this backend) + * - get_list (is it possible to get the list of cache ids and the complete list of tags) + * + * @return array associative of with capabilities + */ + public function getCapabilities() + { + return array( + 'automatic_cleaning' => true, + 'tags' => true, + 'expired_read' => true, + 'priority' => false, + 'infinite_lifetime' => true, + 'get_list' => true + ); + } + + /** + * PUBLIC METHOD FOR UNIT TESTING ONLY ! + * + * Force a cache record to expire + * + * @param string $id Cache id + */ + public function ___expire($id) + { + $time = time() - 1; + $this->_query("UPDATE cache SET lastModified=$time, expire=$time WHERE id='$id'"); + } + + /** + * Return the connection resource + * + * If we are not connected, the connection is made + * + * @throws Zend_Cache_Exception + * @return resource Connection resource + */ + private function _getConnection() + { + if (is_resource($this->_db)) { + return $this->_db; + } else { + $this->_db = @sqlite_open($this->_options['cache_db_complete_path']); + if (!(is_resource($this->_db))) { + Zend_Cache::throwException("Impossible to open " . $this->_options['cache_db_complete_path'] . " cache DB file"); + } + return $this->_db; + } + } + + /** + * Execute an SQL query silently + * + * @param string $query SQL query + * @return mixed|false query results + */ + private function _query($query) + { + $db = $this->_getConnection(); + if (is_resource($db)) { + $res = @sqlite_query($db, $query); + if ($res === false) { + return false; + } else { + return $res; + } + } + return false; + } + + /** + * Deal with the automatic vacuum process + * + * @return void + */ + private function _automaticVacuum() + { + if ($this->_options['automatic_vacuum_factor'] > 0) { + $rand = rand(1, $this->_options['automatic_vacuum_factor']); + if ($rand == 1) { + $this->_query('VACUUM'); + @sqlite_close($this->_getConnection()); + } + } + } + + /** + * Register a cache id with the given tag + * + * @param string $id Cache id + * @param string $tag Tag + * @return boolean True if no problem + */ + private function _registerTag($id, $tag) { + $res = $this->_query("DELETE FROM TAG WHERE name='$tag' AND id='$id'"); + $res = $this->_query("INSERT INTO tag (name, id) VALUES ('$tag', '$id')"); + if (!$res) { + $this->_log("Zend_Cache_Backend_Sqlite::_registerTag() : impossible to register tag=$tag on id=$id"); + return false; + } + return true; + } + + /** + * Build the database structure + * + * @return false + */ + private function _buildStructure() + { + $this->_query('DROP INDEX tag_id_index'); + $this->_query('DROP INDEX tag_name_index'); + $this->_query('DROP INDEX cache_id_expire_index'); + $this->_query('DROP TABLE version'); + $this->_query('DROP TABLE cache'); + $this->_query('DROP TABLE tag'); + $this->_query('CREATE TABLE version (num INTEGER PRIMARY KEY)'); + $this->_query('CREATE TABLE cache (id TEXT PRIMARY KEY, content BLOB, lastModified INTEGER, expire INTEGER)'); + $this->_query('CREATE TABLE tag (name TEXT, id TEXT)'); + $this->_query('CREATE INDEX tag_id_index ON tag(id)'); + $this->_query('CREATE INDEX tag_name_index ON tag(name)'); + $this->_query('CREATE INDEX cache_id_expire_index ON cache(id, expire)'); + $this->_query('INSERT INTO version (num) VALUES (1)'); + } + + /** + * Check if the database structure is ok (with the good version) + * + * @return boolean True if ok + */ + private function _checkStructureVersion() + { + $result = $this->_query("SELECT num FROM version"); + if (!$result) return false; + $row = @sqlite_fetch_array($result); + if (!$row) { + return false; + } + if (((int) $row['num']) != 1) { + // old cache structure + $this->_log('Zend_Cache_Backend_Sqlite::_checkStructureVersion() : old cache structure version detected => the cache is going to be dropped'); + return false; + } + return true; + } + + /** + * Clean some cache records + * + * Available modes are : + * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags + * ($tags can be an array of strings or a single string) + * + * @param string $mode Clean mode + * @param array $tags Array of tags + * @return boolean True if no problem + */ + private function _clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + switch ($mode) { + case Zend_Cache::CLEANING_MODE_ALL: + $res1 = $this->_query('DELETE FROM cache'); + $res2 = $this->_query('DELETE FROM tag'); + return $res1 && $res2; + break; + case Zend_Cache::CLEANING_MODE_OLD: + $mktime = time(); + $res1 = $this->_query("DELETE FROM tag WHERE id IN (SELECT id FROM cache WHERE expire>0 AND expire<=$mktime)"); + $res2 = $this->_query("DELETE FROM cache WHERE expire>0 AND expire<=$mktime"); + return $res1 && $res2; + break; + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + $ids = $this->getIdsMatchingTags($tags); + $result = true; + foreach ($ids as $id) { + $result = $this->remove($id) && $result; + } + return $result; + break; + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + $ids = $this->getIdsNotMatchingTags($tags); + $result = true; + foreach ($ids as $id) { + $result = $this->remove($id) && $result; + } + return $result; + break; + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + $ids = $this->getIdsMatchingAnyTags($tags); + $result = true; + foreach ($ids as $id) { + $result = $this->remove($id) && $result; + } + return $result; + break; + default: + break; + } + return false; + } + + /** + * Check if the database structure is ok (with the good version), if no : build it + * + * @throws Zend_Cache_Exception + * @return boolean True if ok + */ + private function _checkAndBuildStructure() + { + if (!($this->_structureChecked)) { + if (!$this->_checkStructureVersion()) { + $this->_buildStructure(); + if (!$this->_checkStructureVersion()) { + Zend_Cache::throwException("Impossible to build cache structure in " . $this->_options['cache_db_complete_path']); + } + } + $this->_structureChecked = true; + } + return true; + } + +} diff --git a/library/Zend/Cache/Backend/Static.php b/library/Zend/Cache/Backend/Static.php new file mode 100644 index 00000000..6b80f6b4 --- /dev/null +++ b/library/Zend/Cache/Backend/Static.php @@ -0,0 +1,564 @@ + null, + 'sub_dir' => 'html', + 'file_extension' => '.html', + 'index_filename' => 'index', + 'file_locking' => true, + 'cache_file_umask' => 0600, + 'cache_directory_umask' => 0700, + 'debug_header' => false, + 'tag_cache' => null, + 'disable_caching' => false + ); + + /** + * Cache for handling tags + * @var Zend_Cache_Core + */ + protected $_tagCache = null; + + /** + * Tagged items + * @var array + */ + protected $_tagged = null; + + /** + * Interceptor child method to handle the case where an Inner + * Cache object is being set since it's not supported by the + * standard backend interface + * + * @param string $name + * @param mixed $value + * @return Zend_Cache_Backend_Static + */ + public function setOption($name, $value) + { + if ($name == 'tag_cache') { + $this->setInnerCache($value); + } else { + parent::setOption($name, $value); + } + return $this; + } + + /** + * Retrieve any option via interception of the parent's statically held + * options including the local option for a tag cache. + * + * @param string $name + * @return mixed + */ + public function getOption($name) + { + if ($name == 'tag_cache') { + return $this->getInnerCache(); + } else { + if (in_array($name, $this->_options)) { + return $this->_options[$name]; + } + if ($name == 'lifetime') { + return parent::getLifetime(); + } + return null; + } + } + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * Note : return value is always "string" (unserialization is done by the core not by the backend) + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @return string|false cached datas + */ + public function load($id, $doNotTestCacheValidity = false) + { + if (($id = (string)$id) === '') { + $id = $this->_detectId(); + } else { + $id = $this->_decodeId($id); + } + if (!$this->_verifyPath($id)) { + Zend_Cache::throwException('Invalid cache id: does not match expected public_dir path'); + } + if ($doNotTestCacheValidity) { + $this->_log("Zend_Cache_Backend_Static::load() : \$doNotTestCacheValidity=true is unsupported by the Static backend"); + } + + $fileName = basename($id); + if ($fileName === '') { + $fileName = $this->_options['index_filename']; + } + $pathName = $this->_options['public_dir'] . dirname($id); + $file = rtrim($pathName, '/') . '/' . $fileName . $this->_options['file_extension']; + if (file_exists($file)) { + $content = file_get_contents($file); + return $content; + } + + return false; + } + + /** + * Test if a cache is available or not (for the given id) + * + * @param string $id cache id + * @return bool + */ + public function test($id) + { + $id = $this->_decodeId($id); + if (!$this->_verifyPath($id)) { + Zend_Cache::throwException('Invalid cache id: does not match expected public_dir path'); + } + + $fileName = basename($id); + if ($fileName === '') { + $fileName = $this->_options['index_filename']; + } + if ($this->_tagged === null && $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME)) { + $this->_tagged = $tagged; + } elseif (!$this->_tagged) { + return false; + } + $pathName = $this->_options['public_dir'] . dirname($id); + + // Switch extension if needed + if (isset($this->_tagged[$id])) { + $extension = $this->_tagged[$id]['extension']; + } else { + $extension = $this->_options['file_extension']; + } + $file = $pathName . '/' . $fileName . $extension; + if (file_exists($file)) { + return true; + } + return false; + } + + /** + * Save some string datas into a cache record + * + * Note : $data is always "string" (serialization is done by the + * core not by the backend) + * + * @param string $data Datas to cache + * @param string $id Cache id + * @param array $tags Array of strings, the cache record will be tagged by each string entry + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @return boolean true if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + if ($this->_options['disable_caching']) { + return true; + } + $extension = null; + if ($this->_isSerialized($data)) { + $data = unserialize($data); + $extension = '.' . ltrim($data[1], '.'); + $data = $data[0]; + } + + clearstatcache(); + if (($id = (string)$id) === '') { + $id = $this->_detectId(); + } else { + $id = $this->_decodeId($id); + } + + $fileName = basename($id); + if ($fileName === '') { + $fileName = $this->_options['index_filename']; + } + + $pathName = realpath($this->_options['public_dir']) . dirname($id); + $this->_createDirectoriesFor($pathName); + + if ($id === null || strlen($id) == 0) { + $dataUnserialized = unserialize($data); + $data = $dataUnserialized['data']; + } + $ext = $this->_options['file_extension']; + if ($extension) $ext = $extension; + $file = rtrim($pathName, '/') . '/' . $fileName . $ext; + if ($this->_options['file_locking']) { + $result = file_put_contents($file, $data, LOCK_EX); + } else { + $result = file_put_contents($file, $data); + } + @chmod($file, $this->_octdec($this->_options['cache_file_umask'])); + + if ($this->_tagged === null && $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME)) { + $this->_tagged = $tagged; + } elseif ($this->_tagged === null) { + $this->_tagged = array(); + } + if (!isset($this->_tagged[$id])) { + $this->_tagged[$id] = array(); + } + if (!isset($this->_tagged[$id]['tags'])) { + $this->_tagged[$id]['tags'] = array(); + } + $this->_tagged[$id]['tags'] = array_unique(array_merge($this->_tagged[$id]['tags'], $tags)); + $this->_tagged[$id]['extension'] = $ext; + $this->getInnerCache()->save($this->_tagged, self::INNER_CACHE_NAME); + return (bool) $result; + } + + /** + * Recursively create the directories needed to write the static file + */ + protected function _createDirectoriesFor($path) + { + if (!is_dir($path)) { + $oldUmask = umask(0); + if ( !@mkdir($path, $this->_octdec($this->_options['cache_directory_umask']), true)) { + $lastErr = error_get_last(); + umask($oldUmask); + Zend_Cache::throwException("Can't create directory: {$lastErr['message']}"); + } + umask($oldUmask); + } + } + + /** + * Detect serialization of data (cannot predict since this is the only way + * to obey the interface yet pass in another parameter). + * + * In future, ZF 2.0, check if we can just avoid the interface restraints. + * + * This format is the only valid one possible for the class, so it's simple + * to just run a regular expression for the starting serialized format. + */ + protected function _isSerialized($data) + { + return preg_match("/a:2:\{i:0;s:\d+:\"/", $data); + } + + /** + * Remove a cache record + * + * @param string $id Cache id + * @return boolean True if no problem + */ + public function remove($id) + { + if (!$this->_verifyPath($id)) { + Zend_Cache::throwException('Invalid cache id: does not match expected public_dir path'); + } + $fileName = basename($id); + if ($this->_tagged === null && $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME)) { + $this->_tagged = $tagged; + } elseif (!$this->_tagged) { + return false; + } + if (isset($this->_tagged[$id])) { + $extension = $this->_tagged[$id]['extension']; + } else { + $extension = $this->_options['file_extension']; + } + if ($fileName === '') { + $fileName = $this->_options['index_filename']; + } + $pathName = $this->_options['public_dir'] . dirname($id); + $file = realpath($pathName) . '/' . $fileName . $extension; + if (!file_exists($file)) { + return false; + } + return unlink($file); + } + + /** + * Remove a cache record recursively for the given directory matching a + * REQUEST_URI based relative path (deletes the actual file matching this + * in addition to the matching directory) + * + * @param string $id Cache id + * @return boolean True if no problem + */ + public function removeRecursively($id) + { + if (!$this->_verifyPath($id)) { + Zend_Cache::throwException('Invalid cache id: does not match expected public_dir path'); + } + $fileName = basename($id); + if ($fileName === '') { + $fileName = $this->_options['index_filename']; + } + $pathName = $this->_options['public_dir'] . dirname($id); + $file = $pathName . '/' . $fileName . $this->_options['file_extension']; + $directory = $pathName . '/' . $fileName; + if (file_exists($directory)) { + if (!is_writable($directory)) { + return false; + } + if (is_dir($directory)) { + foreach (new DirectoryIterator($directory) as $file) { + if (true === $file->isFile()) { + if (false === unlink($file->getPathName())) { + return false; + } + } + } + } + rmdir($directory); + } + if (file_exists($file)) { + if (!is_writable($file)) { + return false; + } + return unlink($file); + } + return true; + } + + /** + * Clean some cache records + * + * Available modes are : + * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags + * ($tags can be an array of strings or a single string) + * + * @param string $mode Clean mode + * @param array $tags Array of tags + * @return boolean true if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + $result = false; + switch ($mode) { + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + if (empty($tags)) { + throw new Zend_Exception('Cannot use tag matching modes as no tags were defined'); + } + if ($this->_tagged === null && $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME)) { + $this->_tagged = $tagged; + } elseif (!$this->_tagged) { + return true; + } + foreach ($tags as $tag) { + $urls = array_keys($this->_tagged); + foreach ($urls as $url) { + if (isset($this->_tagged[$url]['tags']) && in_array($tag, $this->_tagged[$url]['tags'])) { + $this->remove($url); + unset($this->_tagged[$url]); + } + } + } + $this->getInnerCache()->save($this->_tagged, self::INNER_CACHE_NAME); + $result = true; + break; + case Zend_Cache::CLEANING_MODE_ALL: + if ($this->_tagged === null) { + $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME); + $this->_tagged = $tagged; + } + if ($this->_tagged === null || empty($this->_tagged)) { + return true; + } + $urls = array_keys($this->_tagged); + foreach ($urls as $url) { + $this->remove($url); + unset($this->_tagged[$url]); + } + $this->getInnerCache()->save($this->_tagged, self::INNER_CACHE_NAME); + $result = true; + break; + case Zend_Cache::CLEANING_MODE_OLD: + $this->_log("Zend_Cache_Backend_Static : Selected Cleaning Mode Currently Unsupported By This Backend"); + break; + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + if (empty($tags)) { + throw new Zend_Exception('Cannot use tag matching modes as no tags were defined'); + } + if ($this->_tagged === null) { + $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME); + $this->_tagged = $tagged; + } + if ($this->_tagged === null || empty($this->_tagged)) { + return true; + } + $urls = array_keys($this->_tagged); + foreach ($urls as $url) { + $difference = array_diff($tags, $this->_tagged[$url]['tags']); + if (count($tags) == count($difference)) { + $this->remove($url); + unset($this->_tagged[$url]); + } + } + $this->getInnerCache()->save($this->_tagged, self::INNER_CACHE_NAME); + $result = true; + break; + default: + Zend_Cache::throwException('Invalid mode for clean() method'); + break; + } + return $result; + } + + /** + * Set an Inner Cache, used here primarily to store Tags associated + * with caches created by this backend. Note: If Tags are lost, the cache + * should be completely cleaned as the mapping of tags to caches will + * have been irrevocably lost. + * + * @param Zend_Cache_Core + * @return void + */ + public function setInnerCache(Zend_Cache_Core $cache) + { + $this->_tagCache = $cache; + $this->_options['tag_cache'] = $cache; + } + + /** + * Get the Inner Cache if set + * + * @return Zend_Cache_Core + */ + public function getInnerCache() + { + if ($this->_tagCache === null) { + Zend_Cache::throwException('An Inner Cache has not been set; use setInnerCache()'); + } + return $this->_tagCache; + } + + /** + * Verify path exists and is non-empty + * + * @param string $path + * @return bool + */ + protected function _verifyPath($path) + { + $path = realpath($path); + $base = realpath($this->_options['public_dir']); + return strncmp($path, $base, strlen($base)) !== 0; + } + + /** + * Determine the page to save from the request + * + * @return string + */ + protected function _detectId() + { + return $_SERVER['REQUEST_URI']; + } + + /** + * Validate a cache id or a tag (security, reliable filenames, reserved prefixes...) + * + * Throw an exception if a problem is found + * + * @param string $string Cache id or tag + * @throws Zend_Cache_Exception + * @return void + * @deprecated Not usable until perhaps ZF 2.0 + */ + protected static function _validateIdOrTag($string) + { + if (!is_string($string)) { + Zend_Cache::throwException('Invalid id or tag : must be a string'); + } + + // Internal only checked in Frontend - not here! + if (substr($string, 0, 9) == 'internal-') { + return; + } + + // Validation assumes no query string, fragments or scheme included - only the path + if (!preg_match( + '/^(?:\/(?:(?:%[[:xdigit:]]{2}|[A-Za-z0-9-_.!~*\'()\[\]:@&=+$,;])*)?)+$/', + $string + ) + ) { + Zend_Cache::throwException("Invalid id or tag '$string' : must be a valid URL path"); + } + } + + /** + * Detect an octal string and return its octal value for file permission ops + * otherwise return the non-string (assumed octal or decimal int already) + * + * @param string $val The potential octal in need of conversion + * @return int + */ + protected function _octdec($val) + { + if (is_string($val) && decoct(octdec($val)) == $val) { + return octdec($val); + } + return $val; + } + + /** + * Decode a request URI from the provided ID + * + * @param string $id + * @return string + */ + protected function _decodeId($id) + { + return pack('H*', $id); + } +} diff --git a/library/Zend/Cache/Backend/Test.php b/library/Zend/Cache/Backend/Test.php new file mode 100644 index 00000000..d009ea0e --- /dev/null +++ b/library/Zend/Cache/Backend/Test.php @@ -0,0 +1,413 @@ +_addLog('construct', array($options)); + } + + /** + * Set the frontend directives + * + * @param array $directives assoc of directives + * @return void + */ + public function setDirectives($directives) + { + $this->_addLog('setDirectives', array($directives)); + } + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * For this test backend only, if $id == 'false', then the method will return false + * if $id == 'serialized', the method will return a serialized array + * ('foo' else) + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @return string Cached datas (or false) + */ + public function load($id, $doNotTestCacheValidity = false) + { + $this->_addLog('get', array($id, $doNotTestCacheValidity)); + + if ( $id == 'false' + || $id == 'd8523b3ee441006261eeffa5c3d3a0a7' + || $id == 'e83249ea22178277d5befc2c5e2e9ace' + || $id == '40f649b94977c0a6e76902e2a0b43587' + || $id == '88161989b73a4cbfd0b701c446115a99' + || $id == '205fc79cba24f0f0018eb92c7c8b3ba4' + || $id == '170720e35f38150b811f68a937fb042d') + { + return false; + } + if ($id=='serialized') { + return serialize(array('foo')); + } + if ($id=='serialized2') { + return serialize(array('headers' => array(), 'data' => 'foo')); + } + if ( $id == '71769f39054f75894288e397df04e445' || $id == '615d222619fb20b527168340cebd0578' + || $id == '8a02d218a5165c467e7a5747cc6bd4b6' || $id == '648aca1366211d17cbf48e65dc570bee' + || $id == '4a923ef02d7f997ca14d56dfeae25ea7') { + return serialize(array('foo', 'bar')); + } + return 'foo'; + } + + /** + * Test if a cache is available or not (for the given id) + * + * For this test backend only, if $id == 'false', then the method will return false + * (123456 else) + * + * @param string $id Cache id + * @return mixed|false false (a cache is not available) or "last modified" timestamp (int) of the available cache record + */ + public function test($id) + { + $this->_addLog('test', array($id)); + if ($id=='false') { + return false; + } + if (($id=='3c439c922209e2cb0b54d6deffccd75a')) { + return false; + } + return 123456; + } + + /** + * Save some string datas into a cache record + * + * For this test backend only, if $id == 'false', then the method will return false + * (true else) + * + * @param string $data Datas to cache + * @param string $id Cache id + * @param array $tags Array of strings, the cache record will be tagged by each string entry + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @return boolean True if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + $this->_addLog('save', array($data, $id, $tags)); + if (substr($id,-5)=='false') { + return false; + } + return true; + } + + /** + * Remove a cache record + * + * For this test backend only, if $id == 'false', then the method will return false + * (true else) + * + * @param string $id Cache id + * @return boolean True if no problem + */ + public function remove($id) + { + $this->_addLog('remove', array($id)); + if (substr($id,-5)=='false') { + return false; + } + return true; + } + + /** + * Clean some cache records + * + * For this test backend only, if $mode == 'false', then the method will return false + * (true else) + * + * Available modes are : + * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} + * ($tags can be an array of strings or a single string) + * + * @param string $mode Clean mode + * @param array $tags Array of tags + * @return boolean True if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + $this->_addLog('clean', array($mode, $tags)); + if ($mode=='false') { + return false; + } + return true; + } + + /** + * Get the last log + * + * @return string The last log + */ + public function getLastLog() + { + return $this->_log[$this->_index - 1]; + } + + /** + * Get the log index + * + * @return int Log index + */ + public function getLogIndex() + { + return $this->_index; + } + + /** + * Get the complete log array + * + * @return array Complete log array + */ + public function getAllLogs() + { + return $this->_log; + } + + /** + * Return true if the automatic cleaning is available for the backend + * + * @return boolean + */ + public function isAutomaticCleaningAvailable() + { + return true; + } + + /** + * Return an array of stored cache ids + * + * @return array array of stored cache ids (string) + */ + public function getIds() + { + return array( + 'prefix_id1', 'prefix_id2' + ); + } + + /** + * Return an array of stored tags + * + * @return array array of stored tags (string) + */ + public function getTags() + { + return array( + 'tag1', 'tag2' + ); + } + + /** + * Return an array of stored cache ids which match given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of matching cache ids (string) + */ + public function getIdsMatchingTags($tags = array()) + { + if ($tags == array('tag1', 'tag2')) { + return array('prefix_id1', 'prefix_id2'); + } + + return array(); + } + + /** + * Return an array of stored cache ids which don't match given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of not matching cache ids (string) + */ + public function getIdsNotMatchingTags($tags = array()) + { + if ($tags == array('tag3', 'tag4')) { + return array('prefix_id3', 'prefix_id4'); + } + + return array(); + } + + /** + * Return an array of stored cache ids which match any given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of any matching cache ids (string) + */ + public function getIdsMatchingAnyTags($tags = array()) + { + if ($tags == array('tag5', 'tag6')) { + return array('prefix_id5', 'prefix_id6'); + } + + return array(); + } + + /** + * Return the filling percentage of the backend storage + * + * @return int integer between 0 and 100 + */ + public function getFillingPercentage() + { + return 50; + } + + /** + * Return an array of metadatas for the given cache id + * + * The array must include these keys : + * - expire : the expire timestamp + * - tags : a string array of tags + * - mtime : timestamp of last modification time + * + * @param string $id cache id + * @return array array of metadatas (false if the cache id is not found) + */ + public function getMetadatas($id) + { + return false; + } + + /** + * Give (if possible) an extra lifetime to the given cache id + * + * @param string $id cache id + * @param int $extraLifetime + * @return boolean true if ok + */ + public function touch($id, $extraLifetime) + { + return true; + } + + /** + * Return an associative array of capabilities (booleans) of the backend + * + * The array must include these keys : + * - automatic_cleaning (is automating cleaning necessary) + * - tags (are tags supported) + * - expired_read (is it possible to read expired cache records + * (for doNotTestCacheValidity option for example)) + * - priority does the backend deal with priority when saving + * - infinite_lifetime (is infinite lifetime can work with this backend) + * - get_list (is it possible to get the list of cache ids and the complete list of tags) + * + * @return array associative of with capabilities + */ + public function getCapabilities() + { + return array( + 'automatic_cleaning' => true, + 'tags' => true, + 'expired_read' => false, + 'priority' => true, + 'infinite_lifetime' => true, + 'get_list' => true + ); + } + + /** + * Add an event to the log array + * + * @param string $methodName MethodName + * @param array $args Arguments + * @return void + */ + private function _addLog($methodName, $args) + { + $this->_log[$this->_index] = array( + 'methodName' => $methodName, + 'args' => $args + ); + $this->_index = $this->_index + 1; + } + +} diff --git a/library/Zend/Cache/Backend/TwoLevels.php b/library/Zend/Cache/Backend/TwoLevels.php new file mode 100644 index 00000000..127f21d3 --- /dev/null +++ b/library/Zend/Cache/Backend/TwoLevels.php @@ -0,0 +1,536 @@ + (string) slow_backend : + * - Slow backend name + * - Must implement the Zend_Cache_Backend_ExtendedInterface + * - Should provide a big storage + * + * =====> (string) fast_backend : + * - Flow backend name + * - Must implement the Zend_Cache_Backend_ExtendedInterface + * - Must be much faster than slow_backend + * + * =====> (array) slow_backend_options : + * - Slow backend options (see corresponding backend) + * + * =====> (array) fast_backend_options : + * - Fast backend options (see corresponding backend) + * + * =====> (int) stats_update_factor : + * - Disable / Tune the computation of the fast backend filling percentage + * - When saving a record into cache : + * 1 => systematic computation of the fast backend filling percentage + * x (integer) > 1 => computation of the fast backend filling percentage randomly 1 times on x cache write + * + * =====> (boolean) slow_backend_custom_naming : + * =====> (boolean) fast_backend_custom_naming : + * =====> (boolean) slow_backend_autoload : + * =====> (boolean) fast_backend_autoload : + * - See Zend_Cache::factory() method + * + * =====> (boolean) auto_refresh_fast_cache + * - If true, auto refresh the fast cache when a cache record is hit + * + * @var array available options + */ + protected $_options = array( + 'slow_backend' => 'File', + 'fast_backend' => 'Apc', + 'slow_backend_options' => array(), + 'fast_backend_options' => array(), + 'stats_update_factor' => 10, + 'slow_backend_custom_naming' => false, + 'fast_backend_custom_naming' => false, + 'slow_backend_autoload' => false, + 'fast_backend_autoload' => false, + 'auto_refresh_fast_cache' => true + ); + + /** + * Slow Backend + * + * @var Zend_Cache_Backend_ExtendedInterface + */ + protected $_slowBackend; + + /** + * Fast Backend + * + * @var Zend_Cache_Backend_ExtendedInterface + */ + protected $_fastBackend; + + /** + * Cache for the fast backend filling percentage + * + * @var int + */ + protected $_fastBackendFillingPercentage = null; + + /** + * Constructor + * + * @param array $options Associative array of options + * @throws Zend_Cache_Exception + * @return void + */ + public function __construct(array $options = array()) + { + parent::__construct($options); + + if ($this->_options['slow_backend'] === null) { + Zend_Cache::throwException('slow_backend option has to set'); + } elseif ($this->_options['slow_backend'] instanceof Zend_Cache_Backend_ExtendedInterface) { + $this->_slowBackend = $this->_options['slow_backend']; + } else { + $this->_slowBackend = Zend_Cache::_makeBackend( + $this->_options['slow_backend'], + $this->_options['slow_backend_options'], + $this->_options['slow_backend_custom_naming'], + $this->_options['slow_backend_autoload'] + ); + if (!in_array('Zend_Cache_Backend_ExtendedInterface', class_implements($this->_slowBackend))) { + Zend_Cache::throwException('slow_backend must implement the Zend_Cache_Backend_ExtendedInterface interface'); + } + } + + if ($this->_options['fast_backend'] === null) { + Zend_Cache::throwException('fast_backend option has to set'); + } elseif ($this->_options['fast_backend'] instanceof Zend_Cache_Backend_ExtendedInterface) { + $this->_fastBackend = $this->_options['fast_backend']; + } else { + $this->_fastBackend = Zend_Cache::_makeBackend( + $this->_options['fast_backend'], + $this->_options['fast_backend_options'], + $this->_options['fast_backend_custom_naming'], + $this->_options['fast_backend_autoload'] + ); + if (!in_array('Zend_Cache_Backend_ExtendedInterface', class_implements($this->_fastBackend))) { + Zend_Cache::throwException('fast_backend must implement the Zend_Cache_Backend_ExtendedInterface interface'); + } + } + + $this->_slowBackend->setDirectives($this->_directives); + $this->_fastBackend->setDirectives($this->_directives); + } + + /** + * Test if a cache is available or not (for the given id) + * + * @param string $id cache id + * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record + */ + public function test($id) + { + $fastTest = $this->_fastBackend->test($id); + if ($fastTest) { + return $fastTest; + } else { + return $this->_slowBackend->test($id); + } + } + + /** + * Save some string datas into a cache record + * + * Note : $data is always "string" (serialization is done by the + * core not by the backend) + * + * @param string $data Datas to cache + * @param string $id Cache id + * @param array $tags Array of strings, the cache record will be tagged by each string entry + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @param int $priority integer between 0 (very low priority) and 10 (maximum priority) used by some particular backends + * @return boolean true if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false, $priority = 8) + { + $usage = $this->_getFastFillingPercentage('saving'); + $boolFast = true; + $lifetime = $this->getLifetime($specificLifetime); + $preparedData = $this->_prepareData($data, $lifetime, $priority); + if (($priority > 0) && (10 * $priority >= $usage)) { + $fastLifetime = $this->_getFastLifetime($lifetime, $priority); + $boolFast = $this->_fastBackend->save($preparedData, $id, array(), $fastLifetime); + $boolSlow = $this->_slowBackend->save($preparedData, $id, $tags, $lifetime); + } else { + $boolSlow = $this->_slowBackend->save($preparedData, $id, $tags, $lifetime); + if ($boolSlow === true) { + $boolFast = $this->_fastBackend->remove($id); + if (!$boolFast && !$this->_fastBackend->test($id)) { + // some backends return false on remove() even if the key never existed. (and it won't if fast is full) + // all we care about is that the key doesn't exist now + $boolFast = true; + } + } + } + + return ($boolFast && $boolSlow); + } + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * Note : return value is always "string" (unserialization is done by the core not by the backend) + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @return string|false cached datas + */ + public function load($id, $doNotTestCacheValidity = false) + { + $res = $this->_fastBackend->load($id, $doNotTestCacheValidity); + if ($res === false) { + $res = $this->_slowBackend->load($id, $doNotTestCacheValidity); + if ($res === false) { + // there is no cache at all for this id + return false; + } + } + $array = unserialize($res); + // maybe, we have to refresh the fast cache ? + if ($this->_options['auto_refresh_fast_cache']) { + if ($array['priority'] == 10) { + // no need to refresh the fast cache with priority = 10 + return $array['data']; + } + $newFastLifetime = $this->_getFastLifetime($array['lifetime'], $array['priority'], time() - $array['expire']); + // we have the time to refresh the fast cache + $usage = $this->_getFastFillingPercentage('loading'); + if (($array['priority'] > 0) && (10 * $array['priority'] >= $usage)) { + // we can refresh the fast cache + $preparedData = $this->_prepareData($array['data'], $array['lifetime'], $array['priority']); + $this->_fastBackend->save($preparedData, $id, array(), $newFastLifetime); + } + } + return $array['data']; + } + + /** + * Remove a cache record + * + * @param string $id Cache id + * @return boolean True if no problem + */ + public function remove($id) + { + $boolFast = $this->_fastBackend->remove($id); + $boolSlow = $this->_slowBackend->remove($id); + return $boolFast && $boolSlow; + } + + /** + * Clean some cache records + * + * Available modes are : + * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags} + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags + * ($tags can be an array of strings or a single string) + * + * @param string $mode Clean mode + * @param array $tags Array of tags + * @throws Zend_Cache_Exception + * @return boolean true if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + switch($mode) { + case Zend_Cache::CLEANING_MODE_ALL: + $boolFast = $this->_fastBackend->clean(Zend_Cache::CLEANING_MODE_ALL); + $boolSlow = $this->_slowBackend->clean(Zend_Cache::CLEANING_MODE_ALL); + return $boolFast && $boolSlow; + break; + case Zend_Cache::CLEANING_MODE_OLD: + return $this->_slowBackend->clean(Zend_Cache::CLEANING_MODE_OLD); + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + $ids = $this->_slowBackend->getIdsMatchingTags($tags); + $res = true; + foreach ($ids as $id) { + $bool = $this->remove($id); + $res = $res && $bool; + } + return $res; + break; + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + $ids = $this->_slowBackend->getIdsNotMatchingTags($tags); + $res = true; + foreach ($ids as $id) { + $bool = $this->remove($id); + $res = $res && $bool; + } + return $res; + break; + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + $ids = $this->_slowBackend->getIdsMatchingAnyTags($tags); + $res = true; + foreach ($ids as $id) { + $bool = $this->remove($id); + $res = $res && $bool; + } + return $res; + break; + default: + Zend_Cache::throwException('Invalid mode for clean() method'); + break; + } + } + + /** + * Return an array of stored cache ids + * + * @return array array of stored cache ids (string) + */ + public function getIds() + { + return $this->_slowBackend->getIds(); + } + + /** + * Return an array of stored tags + * + * @return array array of stored tags (string) + */ + public function getTags() + { + return $this->_slowBackend->getTags(); + } + + /** + * Return an array of stored cache ids which match given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of matching cache ids (string) + */ + public function getIdsMatchingTags($tags = array()) + { + return $this->_slowBackend->getIdsMatchingTags($tags); + } + + /** + * Return an array of stored cache ids which don't match given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of not matching cache ids (string) + */ + public function getIdsNotMatchingTags($tags = array()) + { + return $this->_slowBackend->getIdsNotMatchingTags($tags); + } + + /** + * Return an array of stored cache ids which match any given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of any matching cache ids (string) + */ + public function getIdsMatchingAnyTags($tags = array()) + { + return $this->_slowBackend->getIdsMatchingAnyTags($tags); + } + + /** + * Return the filling percentage of the backend storage + * + * @return int integer between 0 and 100 + */ + public function getFillingPercentage() + { + return $this->_slowBackend->getFillingPercentage(); + } + + /** + * Return an array of metadatas for the given cache id + * + * The array must include these keys : + * - expire : the expire timestamp + * - tags : a string array of tags + * - mtime : timestamp of last modification time + * + * @param string $id cache id + * @return array array of metadatas (false if the cache id is not found) + */ + public function getMetadatas($id) + { + return $this->_slowBackend->getMetadatas($id); + } + + /** + * Give (if possible) an extra lifetime to the given cache id + * + * @param string $id cache id + * @param int $extraLifetime + * @return boolean true if ok + */ + public function touch($id, $extraLifetime) + { + return $this->_slowBackend->touch($id, $extraLifetime); + } + + /** + * Return an associative array of capabilities (booleans) of the backend + * + * The array must include these keys : + * - automatic_cleaning (is automating cleaning necessary) + * - tags (are tags supported) + * - expired_read (is it possible to read expired cache records + * (for doNotTestCacheValidity option for example)) + * - priority does the backend deal with priority when saving + * - infinite_lifetime (is infinite lifetime can work with this backend) + * - get_list (is it possible to get the list of cache ids and the complete list of tags) + * + * @return array associative of with capabilities + */ + public function getCapabilities() + { + $slowBackendCapabilities = $this->_slowBackend->getCapabilities(); + return array( + 'automatic_cleaning' => $slowBackendCapabilities['automatic_cleaning'], + 'tags' => $slowBackendCapabilities['tags'], + 'expired_read' => $slowBackendCapabilities['expired_read'], + 'priority' => $slowBackendCapabilities['priority'], + 'infinite_lifetime' => $slowBackendCapabilities['infinite_lifetime'], + 'get_list' => $slowBackendCapabilities['get_list'] + ); + } + + /** + * Prepare a serialized array to store datas and metadatas informations + * + * @param string $data data to store + * @param int $lifetime original lifetime + * @param int $priority priority + * @return string serialize array to store into cache + */ + private function _prepareData($data, $lifetime, $priority) + { + $lt = $lifetime; + if ($lt === null) { + $lt = 9999999999; + } + return serialize(array( + 'data' => $data, + 'lifetime' => $lifetime, + 'expire' => time() + $lt, + 'priority' => $priority + )); + } + + /** + * Compute and return the lifetime for the fast backend + * + * @param int $lifetime original lifetime + * @param int $priority priority + * @param int $maxLifetime maximum lifetime + * @return int lifetime for the fast backend + */ + private function _getFastLifetime($lifetime, $priority, $maxLifetime = null) + { + if ($lifetime <= 0) { + // if no lifetime, we have an infinite lifetime + // we need to use arbitrary lifetimes + $fastLifetime = (int) (2592000 / (11 - $priority)); + } else { + // prevent computed infinite lifetime (0) by ceil + $fastLifetime = (int) ceil($lifetime / (11 - $priority)); + } + + if ($maxLifetime >= 0 && $fastLifetime > $maxLifetime) { + return $maxLifetime; + } + + return $fastLifetime; + } + + /** + * PUBLIC METHOD FOR UNIT TESTING ONLY ! + * + * Force a cache record to expire + * + * @param string $id cache id + */ + public function ___expire($id) + { + $this->_fastBackend->remove($id); + $this->_slowBackend->___expire($id); + } + + private function _getFastFillingPercentage($mode) + { + + if ($mode == 'saving') { + // mode saving + if ($this->_fastBackendFillingPercentage === null) { + $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage(); + } else { + $rand = rand(1, $this->_options['stats_update_factor']); + if ($rand == 1) { + // we force a refresh + $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage(); + } + } + } else { + // mode loading + // we compute the percentage only if it's not available in cache + if ($this->_fastBackendFillingPercentage === null) { + $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage(); + } + } + return $this->_fastBackendFillingPercentage; + } + +} diff --git a/library/Zend/Cache/Backend/WinCache.php b/library/Zend/Cache/Backend/WinCache.php new file mode 100644 index 00000000..452bac95 --- /dev/null +++ b/library/Zend/Cache/Backend/WinCache.php @@ -0,0 +1,349 @@ + infinite lifetime) + * @return boolean true if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + $lifetime = $this->getLifetime($specificLifetime); + $result = wincache_ucache_set($id, array($data, time(), $lifetime), $lifetime); + if (count($tags) > 0) { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_WINCACHE_BACKEND); + } + return $result; + } + + /** + * Remove a cache record + * + * @param string $id cache id + * @return boolean true if no problem + */ + public function remove($id) + { + return wincache_ucache_delete($id); + } + + /** + * Clean some cache records + * + * Available modes are : + * 'all' (default) => remove all cache entries ($tags is not used) + * 'old' => unsupported + * 'matchingTag' => unsupported + * 'notMatchingTag' => unsupported + * 'matchingAnyTag' => unsupported + * + * @param string $mode clean mode + * @param array $tags array of tags + * @throws Zend_Cache_Exception + * @return boolean true if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + switch ($mode) { + case Zend_Cache::CLEANING_MODE_ALL: + return wincache_ucache_clear(); + break; + case Zend_Cache::CLEANING_MODE_OLD: + $this->_log("Zend_Cache_Backend_WinCache::clean() : CLEANING_MODE_OLD is unsupported by the WinCache backend"); + break; + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + $this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_WINCACHE_BACKEND); + break; + default: + Zend_Cache::throwException('Invalid mode for clean() method'); + break; + } + } + + /** + * Return true if the automatic cleaning is available for the backend + * + * DEPRECATED : use getCapabilities() instead + * + * @deprecated + * @return boolean + */ + public function isAutomaticCleaningAvailable() + { + return false; + } + + /** + * Return the filling percentage of the backend storage + * + * @throws Zend_Cache_Exception + * @return int integer between 0 and 100 + */ + public function getFillingPercentage() + { + $mem = wincache_ucache_meminfo(); + $memSize = $mem['memory_total']; + $memUsed = $mem['memory_free']; + if ($memSize == 0) { + Zend_Cache::throwException('can\'t get WinCache memory size'); + } + if ($memUsed > $memSize) { + return 100; + } + return ((int) (100. * ($memUsed / $memSize))); + } + + /** + * Return an array of stored tags + * + * @return array array of stored tags (string) + */ + public function getTags() + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_WINCACHE_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which match given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of matching cache ids (string) + */ + public function getIdsMatchingTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_WINCACHE_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which don't match given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of not matching cache ids (string) + */ + public function getIdsNotMatchingTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_WINCACHE_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids which match any given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of any matching cache ids (string) + */ + public function getIdsMatchingAnyTags($tags = array()) + { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_WINCACHE_BACKEND); + return array(); + } + + /** + * Return an array of stored cache ids + * + * @return array array of stored cache ids (string) + */ + public function getIds() + { + $res = array(); + $array = wincache_ucache_info(); + $records = $array['ucache_entries']; + foreach ($records as $record) { + $res[] = $record['key_name']; + } + return $res; + } + + /** + * Return an array of metadatas for the given cache id + * + * The array must include these keys : + * - expire : the expire timestamp + * - tags : a string array of tags + * - mtime : timestamp of last modification time + * + * @param string $id cache id + * @return array array of metadatas (false if the cache id is not found) + */ + public function getMetadatas($id) + { + $tmp = wincache_ucache_get($id); + if (is_array($tmp)) { + $data = $tmp[0]; + $mtime = $tmp[1]; + if (!isset($tmp[2])) { + return false; + } + $lifetime = $tmp[2]; + return array( + 'expire' => $mtime + $lifetime, + 'tags' => array(), + 'mtime' => $mtime + ); + } + return false; + } + + /** + * Give (if possible) an extra lifetime to the given cache id + * + * @param string $id cache id + * @param int $extraLifetime + * @return boolean true if ok + */ + public function touch($id, $extraLifetime) + { + $tmp = wincache_ucache_get($id); + if (is_array($tmp)) { + $data = $tmp[0]; + $mtime = $tmp[1]; + if (!isset($tmp[2])) { + return false; + } + $lifetime = $tmp[2]; + $newLifetime = $lifetime - (time() - $mtime) + $extraLifetime; + if ($newLifetime <=0) { + return false; + } + return wincache_ucache_set($id, array($data, time(), $newLifetime), $newLifetime); + } + return false; + } + + /** + * Return an associative array of capabilities (booleans) of the backend + * + * The array must include these keys : + * - automatic_cleaning (is automating cleaning necessary) + * - tags (are tags supported) + * - expired_read (is it possible to read expired cache records + * (for doNotTestCacheValidity option for example)) + * - priority does the backend deal with priority when saving + * - infinite_lifetime (is infinite lifetime can work with this backend) + * - get_list (is it possible to get the list of cache ids and the complete list of tags) + * + * @return array associative of with capabilities + */ + public function getCapabilities() + { + return array( + 'automatic_cleaning' => false, + 'tags' => false, + 'expired_read' => false, + 'priority' => false, + 'infinite_lifetime' => false, + 'get_list' => true + ); + } + +} diff --git a/library/Zend/Cache/Backend/Xcache.php b/library/Zend/Cache/Backend/Xcache.php new file mode 100644 index 00000000..18c0d7df --- /dev/null +++ b/library/Zend/Cache/Backend/Xcache.php @@ -0,0 +1,221 @@ + (string) user : + * xcache.admin.user (necessary for the clean() method) + * + * =====> (string) password : + * xcache.admin.pass (clear, not MD5) (necessary for the clean() method) + * + * @var array available options + */ + protected $_options = array( + 'user' => null, + 'password' => null + ); + + /** + * Constructor + * + * @param array $options associative array of options + * @throws Zend_Cache_Exception + * @return void + */ + public function __construct(array $options = array()) + { + if (!extension_loaded('xcache')) { + Zend_Cache::throwException('The xcache extension must be loaded for using this backend !'); + } + parent::__construct($options); + } + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * WARNING $doNotTestCacheValidity=true is unsupported by the Xcache backend + * + * @param string $id cache id + * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested + * @return string cached datas (or false) + */ + public function load($id, $doNotTestCacheValidity = false) + { + if ($doNotTestCacheValidity) { + $this->_log("Zend_Cache_Backend_Xcache::load() : \$doNotTestCacheValidity=true is unsupported by the Xcache backend"); + } + $tmp = xcache_get($id); + if (is_array($tmp)) { + return $tmp[0]; + } + return false; + } + + /** + * Test if a cache is available or not (for the given id) + * + * @param string $id cache id + * @return mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record + */ + public function test($id) + { + if (xcache_isset($id)) { + $tmp = xcache_get($id); + if (is_array($tmp)) { + return $tmp[1]; + } + } + return false; + } + + /** + * Save some string datas into a cache record + * + * Note : $data is always "string" (serialization is done by the + * core not by the backend) + * + * @param string $data datas to cache + * @param string $id cache id + * @param array $tags array of strings, the cache record will be tagged by each string entry + * @param int $specificLifetime if != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @return boolean true if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + $lifetime = $this->getLifetime($specificLifetime); + $result = xcache_set($id, array($data, time()), $lifetime); + if (count($tags) > 0) { + $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_XCACHE_BACKEND); + } + return $result; + } + + /** + * Remove a cache record + * + * @param string $id cache id + * @return boolean true if no problem + */ + public function remove($id) + { + return xcache_unset($id); + } + + /** + * Clean some cache records + * + * Available modes are : + * 'all' (default) => remove all cache entries ($tags is not used) + * 'old' => unsupported + * 'matchingTag' => unsupported + * 'notMatchingTag' => unsupported + * 'matchingAnyTag' => unsupported + * + * @param string $mode clean mode + * @param array $tags array of tags + * @throws Zend_Cache_Exception + * @return boolean true if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + switch ($mode) { + case Zend_Cache::CLEANING_MODE_ALL: + // Necessary because xcache_clear_cache() need basic authentification + $backup = array(); + if (isset($_SERVER['PHP_AUTH_USER'])) { + $backup['PHP_AUTH_USER'] = $_SERVER['PHP_AUTH_USER']; + } + if (isset($_SERVER['PHP_AUTH_PW'])) { + $backup['PHP_AUTH_PW'] = $_SERVER['PHP_AUTH_PW']; + } + if ($this->_options['user']) { + $_SERVER['PHP_AUTH_USER'] = $this->_options['user']; + } + if ($this->_options['password']) { + $_SERVER['PHP_AUTH_PW'] = $this->_options['password']; + } + + $cnt = xcache_count(XC_TYPE_VAR); + for ($i=0; $i < $cnt; $i++) { + xcache_clear_cache(XC_TYPE_VAR, $i); + } + + if (isset($backup['PHP_AUTH_USER'])) { + $_SERVER['PHP_AUTH_USER'] = $backup['PHP_AUTH_USER']; + $_SERVER['PHP_AUTH_PW'] = $backup['PHP_AUTH_PW']; + } + return true; + break; + case Zend_Cache::CLEANING_MODE_OLD: + $this->_log("Zend_Cache_Backend_Xcache::clean() : CLEANING_MODE_OLD is unsupported by the Xcache backend"); + break; + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + $this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_XCACHE_BACKEND); + break; + default: + Zend_Cache::throwException('Invalid mode for clean() method'); + break; + } + } + + /** + * Return true if the automatic cleaning is available for the backend + * + * @return boolean + */ + public function isAutomaticCleaningAvailable() + { + return false; + } + +} diff --git a/library/Zend/Cache/Backend/ZendPlatform.php b/library/Zend/Cache/Backend/ZendPlatform.php new file mode 100644 index 00000000..fb9d36dd --- /dev/null +++ b/library/Zend/Cache/Backend/ZendPlatform.php @@ -0,0 +1,317 @@ +_directives['lifetime']; + } + $res = output_cache_get($id, $lifetime); + if($res) { + return $res[0]; + } else { + return false; + } + } + + + /** + * Test if a cache is available or not (for the given id) + * + * @param string $id Cache id + * @return mixed|false false (a cache is not available) or "last modified" timestamp (int) of the available cache record + */ + public function test($id) + { + $result = output_cache_get($id, $this->_directives['lifetime']); + if ($result) { + return $result[1]; + } + return false; + } + + /** + * Save some string datas into a cache record + * + * Note : $data is always "string" (serialization is done by the + * core not by the backend) + * + * @param string $data Data to cache + * @param string $id Cache id + * @param array $tags Array of strings, the cache record will be tagged by each string entry + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @return boolean true if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + if (!($specificLifetime === false)) { + $this->_log("Zend_Cache_Backend_ZendPlatform::save() : non false specifc lifetime is unsuported for this backend"); + } + + $lifetime = $this->_directives['lifetime']; + $result1 = output_cache_put($id, array($data, time())); + $result2 = (count($tags) == 0); + + foreach ($tags as $tag) { + $tagid = self::TAGS_PREFIX.$tag; + $old_tags = output_cache_get($tagid, $lifetime); + if ($old_tags === false) { + $old_tags = array(); + } + $old_tags[$id] = $id; + output_cache_remove_key($tagid); + $result2 = output_cache_put($tagid, $old_tags); + } + + return $result1 && $result2; + } + + + /** + * Remove a cache record + * + * @param string $id Cache id + * @return boolean True if no problem + */ + public function remove($id) + { + return output_cache_remove_key($id); + } + + + /** + * Clean some cache records + * + * Available modes are : + * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) + * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used) + * This mode is not supported in this backend + * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => unsupported + * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags + * ($tags can be an array of strings or a single string) + * + * @param string $mode Clean mode + * @param array $tags Array of tags + * @throws Zend_Cache_Exception + * @return boolean True if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + switch ($mode) { + case Zend_Cache::CLEANING_MODE_ALL: + case Zend_Cache::CLEANING_MODE_OLD: + $cache_dir = ini_get('zend_accelerator.output_cache_dir'); + if (!$cache_dir) { + return false; + } + $cache_dir .= '/.php_cache_api/'; + return $this->_clean($cache_dir, $mode); + break; + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + $idlist = null; + foreach ($tags as $tag) { + $next_idlist = output_cache_get(self::TAGS_PREFIX.$tag, $this->_directives['lifetime']); + if ($idlist) { + $idlist = array_intersect_assoc($idlist, $next_idlist); + } else { + $idlist = $next_idlist; + } + if (count($idlist) == 0) { + // if ID list is already empty - we may skip checking other IDs + $idlist = null; + break; + } + } + if ($idlist) { + foreach ($idlist as $id) { + output_cache_remove_key($id); + } + } + return true; + break; + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + $this->_log("Zend_Cache_Backend_ZendPlatform::clean() : CLEANING_MODE_NOT_MATCHING_TAG is not supported by the Zend Platform backend"); + return false; + break; + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + $idlist = null; + foreach ($tags as $tag) { + $next_idlist = output_cache_get(self::TAGS_PREFIX.$tag, $this->_directives['lifetime']); + if ($idlist) { + $idlist = array_merge_recursive($idlist, $next_idlist); + } else { + $idlist = $next_idlist; + } + if (count($idlist) == 0) { + // if ID list is already empty - we may skip checking other IDs + $idlist = null; + break; + } + } + if ($idlist) { + foreach ($idlist as $id) { + output_cache_remove_key($id); + } + } + return true; + break; + default: + Zend_Cache::throwException('Invalid mode for clean() method'); + break; + } + } + + /** + * Clean a directory and recursivly go over it's subdirectories + * + * Remove all the cached files that need to be cleaned (according to mode and files mtime) + * + * @param string $dir Path of directory ot clean + * @param string $mode The same parameter as in Zend_Cache_Backend_ZendPlatform::clean() + * @return boolean True if ok + */ + private function _clean($dir, $mode) + { + $d = @dir($dir); + if (!$d) { + return false; + } + $result = true; + while (false !== ($file = $d->read())) { + if ($file == '.' || $file == '..') { + continue; + } + $file = $d->path . $file; + if (is_dir($file)) { + $result = ($this->_clean($file .'/', $mode)) && ($result); + } else { + if ($mode == Zend_Cache::CLEANING_MODE_ALL) { + $result = ($this->_remove($file)) && ($result); + } else if ($mode == Zend_Cache::CLEANING_MODE_OLD) { + // Files older than lifetime get deleted from cache + if ($this->_directives['lifetime'] !== null) { + if ((time() - @filemtime($file)) > $this->_directives['lifetime']) { + $result = ($this->_remove($file)) && ($result); + } + } + } + } + } + $d->close(); + return $result; + } + + /** + * Remove a file + * + * If we can't remove the file (because of locks or any problem), we will touch + * the file to invalidate it + * + * @param string $file Complete file path + * @return boolean True if ok + */ + private function _remove($file) + { + if (!@unlink($file)) { + # If we can't remove the file (because of locks or any problem), we will touch + # the file to invalidate it + $this->_log("Zend_Cache_Backend_ZendPlatform::_remove() : we can't remove $file => we are going to try to invalidate it"); + if ($this->_directives['lifetime'] === null) { + return false; + } + if (!file_exists($file)) { + return false; + } + return @touch($file, time() - 2*abs($this->_directives['lifetime'])); + } + return true; + } + +} diff --git a/library/Zend/Cache/Backend/ZendServer.php b/library/Zend/Cache/Backend/ZendServer.php new file mode 100644 index 00000000..3a4a1da8 --- /dev/null +++ b/library/Zend/Cache/Backend/ZendServer.php @@ -0,0 +1,207 @@ + (string) namespace : + * Namespace to be used for chaching operations + * + * @var array available options + */ + protected $_options = array( + 'namespace' => 'zendframework' + ); + + /** + * Store data + * + * @param mixed $data Object to store + * @param string $id Cache id + * @param int $timeToLive Time to live in seconds + * @throws Zend_Cache_Exception + */ + abstract protected function _store($data, $id, $timeToLive); + + /** + * Fetch data + * + * @param string $id Cache id + * @throws Zend_Cache_Exception + */ + abstract protected function _fetch($id); + + /** + * Unset data + * + * @param string $id Cache id + */ + abstract protected function _unset($id); + + /** + * Clear cache + */ + abstract protected function _clear(); + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * @param string $id cache id + * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested + * @return string cached datas (or false) + */ + public function load($id, $doNotTestCacheValidity = false) + { + $tmp = $this->_fetch($id); + if ($tmp !== null) { + return $tmp; + } + return false; + } + + /** + * Test if a cache is available or not (for the given id) + * + * @param string $id cache id + * @return mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record + * @throws Zend_Cache_Exception + */ + public function test($id) + { + $tmp = $this->_fetch('internal-metadatas---' . $id); + if ($tmp !== false) { + if (!is_array($tmp) || !isset($tmp['mtime'])) { + Zend_Cache::throwException('Cache metadata for \'' . $id . '\' id is corrupted' ); + } + return $tmp['mtime']; + } + return false; + } + + /** + * Compute & return the expire time + * + * @return int expire time (unix timestamp) + */ + private function _expireTime($lifetime) + { + if ($lifetime === null) { + return 9999999999; + } + return time() + $lifetime; + } + + /** + * Save some string datas into a cache record + * + * Note : $data is always "string" (serialization is done by the + * core not by the backend) + * + * @param string $data datas to cache + * @param string $id cache id + * @param array $tags array of strings, the cache record will be tagged by each string entry + * @param int $specificLifetime if != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @return boolean true if no problem + */ + public function save($data, $id, $tags = array(), $specificLifetime = false) + { + $lifetime = $this->getLifetime($specificLifetime); + $metadatas = array( + 'mtime' => time(), + 'expire' => $this->_expireTime($lifetime), + ); + + if (count($tags) > 0) { + $this->_log('Zend_Cache_Backend_ZendServer::save() : tags are unsupported by the ZendServer backends'); + } + + return $this->_store($data, $id, $lifetime) && + $this->_store($metadatas, 'internal-metadatas---' . $id, $lifetime); + } + + /** + * Remove a cache record + * + * @param string $id cache id + * @return boolean true if no problem + */ + public function remove($id) + { + $result1 = $this->_unset($id); + $result2 = $this->_unset('internal-metadatas---' . $id); + + return $result1 && $result2; + } + + /** + * Clean some cache records + * + * Available modes are : + * 'all' (default) => remove all cache entries ($tags is not used) + * 'old' => unsupported + * 'matchingTag' => unsupported + * 'notMatchingTag' => unsupported + * 'matchingAnyTag' => unsupported + * + * @param string $mode clean mode + * @param array $tags array of tags + * @throws Zend_Cache_Exception + * @return boolean true if no problem + */ + public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) + { + switch ($mode) { + case Zend_Cache::CLEANING_MODE_ALL: + $this->_clear(); + return true; + break; + case Zend_Cache::CLEANING_MODE_OLD: + $this->_log("Zend_Cache_Backend_ZendServer::clean() : CLEANING_MODE_OLD is unsupported by the Zend Server backends."); + break; + case Zend_Cache::CLEANING_MODE_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG: + case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG: + $this->_clear(); + $this->_log('Zend_Cache_Backend_ZendServer::clean() : tags are unsupported by the Zend Server backends.'); + break; + default: + Zend_Cache::throwException('Invalid mode for clean() method'); + break; + } + } +} diff --git a/library/Zend/Cache/Backend/ZendServer/Disk.php b/library/Zend/Cache/Backend/ZendServer/Disk.php new file mode 100644 index 00000000..de806b17 --- /dev/null +++ b/library/Zend/Cache/Backend/ZendServer/Disk.php @@ -0,0 +1,100 @@ +_options['namespace'] . '::' . $id, + $data, + $timeToLive) === false) { + $this->_log('Store operation failed.'); + return false; + } + return true; + } + + /** + * Fetch data + * + * @param string $id Cache id + */ + protected function _fetch($id) + { + return zend_disk_cache_fetch($this->_options['namespace'] . '::' . $id); + } + + /** + * Unset data + * + * @param string $id Cache id + * @return boolean true if no problem + */ + protected function _unset($id) + { + return zend_disk_cache_delete($this->_options['namespace'] . '::' . $id); + } + + /** + * Clear cache + */ + protected function _clear() + { + zend_disk_cache_clear($this->_options['namespace']); + } +} diff --git a/library/Zend/Cache/Backend/ZendServer/ShMem.php b/library/Zend/Cache/Backend/ZendServer/ShMem.php new file mode 100644 index 00000000..d6f7bf03 --- /dev/null +++ b/library/Zend/Cache/Backend/ZendServer/ShMem.php @@ -0,0 +1,100 @@ +_options['namespace'] . '::' . $id, + $data, + $timeToLive) === false) { + $this->_log('Store operation failed.'); + return false; + } + return true; + } + + /** + * Fetch data + * + * @param string $id Cache id + */ + protected function _fetch($id) + { + return zend_shm_cache_fetch($this->_options['namespace'] . '::' . $id); + } + + /** + * Unset data + * + * @param string $id Cache id + * @return boolean true if no problem + */ + protected function _unset($id) + { + return zend_shm_cache_delete($this->_options['namespace'] . '::' . $id); + } + + /** + * Clear cache + */ + protected function _clear() + { + zend_shm_cache_clear($this->_options['namespace']); + } +} diff --git a/library/Zend/Cache/Core.php b/library/Zend/Cache/Core.php new file mode 100644 index 00000000..47fa687d --- /dev/null +++ b/library/Zend/Cache/Core.php @@ -0,0 +1,764 @@ + (boolean) write_control : + * - Enable / disable write control (the cache is read just after writing to detect corrupt entries) + * - Enable write control will lightly slow the cache writing but not the cache reading + * Write control can detect some corrupt cache files but maybe it's not a perfect control + * + * ====> (boolean) caching : + * - Enable / disable caching + * (can be very useful for the debug of cached scripts) + * + * =====> (string) cache_id_prefix : + * - prefix for cache ids (namespace) + * + * ====> (boolean) automatic_serialization : + * - Enable / disable automatic serialization + * - It can be used to save directly datas which aren't strings (but it's slower) + * + * ====> (int) automatic_cleaning_factor : + * - Disable / Tune the automatic cleaning process + * - The automatic cleaning process destroy too old (for the given life time) + * cache files when a new cache file is written : + * 0 => no automatic cache cleaning + * 1 => systematic cache cleaning + * x (integer) > 1 => automatic cleaning randomly 1 times on x cache write + * + * ====> (int) lifetime : + * - Cache lifetime (in seconds) + * - If null, the cache is valid forever. + * + * ====> (boolean) logging : + * - If set to true, logging is activated (but the system is slower) + * + * ====> (boolean) ignore_user_abort + * - If set to true, the core will set the ignore_user_abort PHP flag inside the + * save() method to avoid cache corruptions in some cases (default false) + * + * @var array $_options available options + */ + protected $_options = array( + 'write_control' => true, + 'caching' => true, + 'cache_id_prefix' => null, + 'automatic_serialization' => false, + 'automatic_cleaning_factor' => 10, + 'lifetime' => 3600, + 'logging' => false, + 'logger' => null, + 'ignore_user_abort' => false + ); + + /** + * Array of options which have to be transfered to backend + * + * @var array $_directivesList + */ + protected static $_directivesList = array('lifetime', 'logging', 'logger'); + + /** + * Not used for the core, just a sort a hint to get a common setOption() method (for the core and for frontends) + * + * @var array $_specificOptions + */ + protected $_specificOptions = array(); + + /** + * Last used cache id + * + * @var string $_lastId + */ + private $_lastId = null; + + /** + * True if the backend implements Zend_Cache_Backend_ExtendedInterface + * + * @var boolean $_extendedBackend + */ + protected $_extendedBackend = false; + + /** + * Array of capabilities of the backend (only if it implements Zend_Cache_Backend_ExtendedInterface) + * + * @var array + */ + protected $_backendCapabilities = array(); + + /** + * Constructor + * + * @param array|Zend_Config $options Associative array of options or Zend_Config instance + * @throws Zend_Cache_Exception + * @return void + */ + public function __construct($options = array()) + { + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } + if (!is_array($options)) { + Zend_Cache::throwException("Options passed were not an array" + . " or Zend_Config instance."); + } + while (list($name, $value) = each($options)) { + $this->setOption($name, $value); + } + $this->_loggerSanity(); + } + + /** + * Set options using an instance of type Zend_Config + * + * @param Zend_Config $config + * @return Zend_Cache_Core + */ + public function setConfig(Zend_Config $config) + { + $options = $config->toArray(); + while (list($name, $value) = each($options)) { + $this->setOption($name, $value); + } + return $this; + } + + /** + * Set the backend + * + * @param Zend_Cache_Backend $backendObject + * @throws Zend_Cache_Exception + * @return void + */ + public function setBackend(Zend_Cache_Backend $backendObject) + { + $this->_backend= $backendObject; + // some options (listed in $_directivesList) have to be given + // to the backend too (even if they are not "backend specific") + $directives = array(); + foreach (Zend_Cache_Core::$_directivesList as $directive) { + $directives[$directive] = $this->_options[$directive]; + } + $this->_backend->setDirectives($directives); + if (in_array('Zend_Cache_Backend_ExtendedInterface', class_implements($this->_backend))) { + $this->_extendedBackend = true; + $this->_backendCapabilities = $this->_backend->getCapabilities(); + } + + } + + /** + * Returns the backend + * + * @return Zend_Cache_Backend backend object + */ + public function getBackend() + { + return $this->_backend; + } + + /** + * Public frontend to set an option + * + * There is an additional validation (relatively to the protected _setOption method) + * + * @param string $name Name of the option + * @param mixed $value Value of the option + * @throws Zend_Cache_Exception + * @return void + */ + public function setOption($name, $value) + { + if (!is_string($name)) { + Zend_Cache::throwException("Incorrect option name : $name"); + } + $name = strtolower($name); + if (array_key_exists($name, $this->_options)) { + // This is a Core option + $this->_setOption($name, $value); + return; + } + if (array_key_exists($name, $this->_specificOptions)) { + // This a specic option of this frontend + $this->_specificOptions[$name] = $value; + return; + } + } + + /** + * Public frontend to get an option value + * + * @param string $name Name of the option + * @throws Zend_Cache_Exception + * @return mixed option value + */ + public function getOption($name) + { + if (is_string($name)) { + $name = strtolower($name); + if (array_key_exists($name, $this->_options)) { + // This is a Core option + return $this->_options[$name]; + } + if (array_key_exists($name, $this->_specificOptions)) { + // This a specic option of this frontend + return $this->_specificOptions[$name]; + } + } + Zend_Cache::throwException("Incorrect option name : $name"); + } + + /** + * Set an option + * + * @param string $name Name of the option + * @param mixed $value Value of the option + * @throws Zend_Cache_Exception + * @return void + */ + private function _setOption($name, $value) + { + if (!is_string($name) || !array_key_exists($name, $this->_options)) { + Zend_Cache::throwException("Incorrect option name : $name"); + } + if ($name == 'lifetime' && empty($value)) { + $value = null; + } + $this->_options[$name] = $value; + } + + /** + * Force a new lifetime + * + * The new value is set for the core/frontend but for the backend too (directive) + * + * @param int $newLifetime New lifetime (in seconds) + * @return void + */ + public function setLifetime($newLifetime) + { + $this->_options['lifetime'] = $newLifetime; + $this->_backend->setDirectives(array( + 'lifetime' => $newLifetime + )); + } + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @param boolean $doNotUnserialize Do not serialize (even if automatic_serialization is true) => for internal use + * @return mixed|false Cached datas + */ + public function load($id, $doNotTestCacheValidity = false, $doNotUnserialize = false) + { + if (!$this->_options['caching']) { + return false; + } + $id = $this->_id($id); // cache id may need prefix + $this->_lastId = $id; + self::_validateIdOrTag($id); + + $this->_log("Zend_Cache_Core: load item '{$id}'", 7); + $data = $this->_backend->load($id, $doNotTestCacheValidity); + if ($data===false) { + // no cache available + return false; + } + if ((!$doNotUnserialize) && $this->_options['automatic_serialization']) { + // we need to unserialize before sending the result + return unserialize($data); + } + return $data; + } + + /** + * Test if a cache is available for the given id + * + * @param string $id Cache id + * @return int|false Last modified time of cache entry if it is available, false otherwise + */ + public function test($id) + { + if (!$this->_options['caching']) { + return false; + } + $id = $this->_id($id); // cache id may need prefix + self::_validateIdOrTag($id); + $this->_lastId = $id; + + $this->_log("Zend_Cache_Core: test item '{$id}'", 7); + return $this->_backend->test($id); + } + + /** + * Save some data in a cache + * + * @param mixed $data Data to put in cache (can be another type than string if automatic_serialization is on) + * @param string $id Cache id (if not set, the last cache id will be used) + * @param array $tags Cache tags + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @param int $priority integer between 0 (very low priority) and 10 (maximum priority) used by some particular backends + * @throws Zend_Cache_Exception + * @return boolean True if no problem + */ + public function save($data, $id = null, $tags = array(), $specificLifetime = false, $priority = 8) + { + if (!$this->_options['caching']) { + return true; + } + if ($id === null) { + $id = $this->_lastId; + } else { + $id = $this->_id($id); + } + self::_validateIdOrTag($id); + self::_validateTagsArray($tags); + if ($this->_options['automatic_serialization']) { + // we need to serialize datas before storing them + $data = serialize($data); + } else { + if (!is_string($data)) { + Zend_Cache::throwException("Datas must be string or set automatic_serialization = true"); + } + } + + // automatic cleaning + if ($this->_options['automatic_cleaning_factor'] > 0) { + $rand = rand(1, $this->_options['automatic_cleaning_factor']); + if ($rand==1) { + // new way || deprecated way + if ($this->_extendedBackend || method_exists($this->_backend, 'isAutomaticCleaningAvailable')) { + $this->_log("Zend_Cache_Core::save(): automatic cleaning running", 7); + $this->clean(Zend_Cache::CLEANING_MODE_OLD); + } else { + $this->_log("Zend_Cache_Core::save(): automatic cleaning is not available/necessary with current backend", 4); + } + } + } + + $this->_log("Zend_Cache_Core: save item '{$id}'", 7); + if ($this->_options['ignore_user_abort']) { + $abort = ignore_user_abort(true); + } + if (($this->_extendedBackend) && ($this->_backendCapabilities['priority'])) { + $result = $this->_backend->save($data, $id, $tags, $specificLifetime, $priority); + } else { + $result = $this->_backend->save($data, $id, $tags, $specificLifetime); + } + if ($this->_options['ignore_user_abort']) { + ignore_user_abort($abort); + } + + if (!$result) { + // maybe the cache is corrupted, so we remove it ! + $this->_log("Zend_Cache_Core::save(): failed to save item '{$id}' -> removing it", 4); + $this->_backend->remove($id); + return false; + } + + if ($this->_options['write_control']) { + $data2 = $this->_backend->load($id, true); + if ($data!=$data2) { + $this->_log("Zend_Cache_Core::save(): write control of item '{$id}' failed -> removing it", 4); + $this->_backend->remove($id); + return false; + } + } + + return true; + } + + /** + * Remove a cache + * + * @param string $id Cache id to remove + * @return boolean True if ok + */ + public function remove($id) + { + if (!$this->_options['caching']) { + return true; + } + $id = $this->_id($id); // cache id may need prefix + self::_validateIdOrTag($id); + + $this->_log("Zend_Cache_Core: remove item '{$id}'", 7); + return $this->_backend->remove($id); + } + + /** + * Clean cache entries + * + * Available modes are : + * 'all' (default) => remove all cache entries ($tags is not used) + * 'old' => remove too old cache entries ($tags is not used) + * 'matchingTag' => remove cache entries matching all given tags + * ($tags can be an array of strings or a single string) + * 'notMatchingTag' => remove cache entries not matching one of the given tags + * ($tags can be an array of strings or a single string) + * 'matchingAnyTag' => remove cache entries matching any given tags + * ($tags can be an array of strings or a single string) + * + * @param string $mode + * @param array|string $tags + * @throws Zend_Cache_Exception + * @return boolean True if ok + */ + public function clean($mode = 'all', $tags = array()) + { + if (!$this->_options['caching']) { + return true; + } + if (!in_array($mode, array(Zend_Cache::CLEANING_MODE_ALL, + Zend_Cache::CLEANING_MODE_OLD, + Zend_Cache::CLEANING_MODE_MATCHING_TAG, + Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG, + Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG))) { + Zend_Cache::throwException('Invalid cleaning mode'); + } + self::_validateTagsArray($tags); + + return $this->_backend->clean($mode, $tags); + } + + /** + * Return an array of stored cache ids which match given tags + * + * In case of multiple tags, a logical AND is made between tags + * + * @param array $tags array of tags + * @return array array of matching cache ids (string) + */ + public function getIdsMatchingTags($tags = array()) + { + if (!$this->_extendedBackend) { + Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); + } + if (!($this->_backendCapabilities['tags'])) { + Zend_Cache::throwException(self::BACKEND_NOT_SUPPORTS_TAG); + } + + $ids = $this->_backend->getIdsMatchingTags($tags); + + // we need to remove cache_id_prefix from ids (see #ZF-6178, #ZF-7600) + if (isset($this->_options['cache_id_prefix']) && $this->_options['cache_id_prefix'] !== '') { + $prefix = & $this->_options['cache_id_prefix']; + $prefixLen = strlen($prefix); + foreach ($ids as &$id) { + if (strpos($id, $prefix) === 0) { + $id = substr($id, $prefixLen); + } + } + } + + return $ids; + } + + /** + * Return an array of stored cache ids which don't match given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of not matching cache ids (string) + */ + public function getIdsNotMatchingTags($tags = array()) + { + if (!$this->_extendedBackend) { + Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); + } + if (!($this->_backendCapabilities['tags'])) { + Zend_Cache::throwException(self::BACKEND_NOT_SUPPORTS_TAG); + } + + $ids = $this->_backend->getIdsNotMatchingTags($tags); + + // we need to remove cache_id_prefix from ids (see #ZF-6178, #ZF-7600) + if (isset($this->_options['cache_id_prefix']) && $this->_options['cache_id_prefix'] !== '') { + $prefix = & $this->_options['cache_id_prefix']; + $prefixLen = strlen($prefix); + foreach ($ids as &$id) { + if (strpos($id, $prefix) === 0) { + $id = substr($id, $prefixLen); + } + } + } + + return $ids; + } + + /** + * Return an array of stored cache ids which match any given tags + * + * In case of multiple tags, a logical OR is made between tags + * + * @param array $tags array of tags + * @return array array of matching any cache ids (string) + */ + public function getIdsMatchingAnyTags($tags = array()) + { + if (!$this->_extendedBackend) { + Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); + } + if (!($this->_backendCapabilities['tags'])) { + Zend_Cache::throwException(self::BACKEND_NOT_SUPPORTS_TAG); + } + + $ids = $this->_backend->getIdsMatchingAnyTags($tags); + + // we need to remove cache_id_prefix from ids (see #ZF-6178, #ZF-7600) + if (isset($this->_options['cache_id_prefix']) && $this->_options['cache_id_prefix'] !== '') { + $prefix = & $this->_options['cache_id_prefix']; + $prefixLen = strlen($prefix); + foreach ($ids as &$id) { + if (strpos($id, $prefix) === 0) { + $id = substr($id, $prefixLen); + } + } + } + + return $ids; + } + + /** + * Return an array of stored cache ids + * + * @return array array of stored cache ids (string) + */ + public function getIds() + { + if (!$this->_extendedBackend) { + Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); + } + + $ids = $this->_backend->getIds(); + + // we need to remove cache_id_prefix from ids (see #ZF-6178, #ZF-7600) + if (isset($this->_options['cache_id_prefix']) && $this->_options['cache_id_prefix'] !== '') { + $prefix = & $this->_options['cache_id_prefix']; + $prefixLen = strlen($prefix); + foreach ($ids as &$id) { + if (strpos($id, $prefix) === 0) { + $id = substr($id, $prefixLen); + } + } + } + + return $ids; + } + + /** + * Return an array of stored tags + * + * @return array array of stored tags (string) + */ + public function getTags() + { + if (!$this->_extendedBackend) { + Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); + } + if (!($this->_backendCapabilities['tags'])) { + Zend_Cache::throwException(self::BACKEND_NOT_SUPPORTS_TAG); + } + return $this->_backend->getTags(); + } + + /** + * Return the filling percentage of the backend storage + * + * @return int integer between 0 and 100 + */ + public function getFillingPercentage() + { + if (!$this->_extendedBackend) { + Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); + } + return $this->_backend->getFillingPercentage(); + } + + /** + * Return an array of metadatas for the given cache id + * + * The array will include these keys : + * - expire : the expire timestamp + * - tags : a string array of tags + * - mtime : timestamp of last modification time + * + * @param string $id cache id + * @return array array of metadatas (false if the cache id is not found) + */ + public function getMetadatas($id) + { + if (!$this->_extendedBackend) { + Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); + } + $id = $this->_id($id); // cache id may need prefix + return $this->_backend->getMetadatas($id); + } + + /** + * Give (if possible) an extra lifetime to the given cache id + * + * @param string $id cache id + * @param int $extraLifetime + * @return boolean true if ok + */ + public function touch($id, $extraLifetime) + { + if (!$this->_extendedBackend) { + Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF); + } + $id = $this->_id($id); // cache id may need prefix + + $this->_log("Zend_Cache_Core: touch item '{$id}'", 7); + return $this->_backend->touch($id, $extraLifetime); + } + + /** + * Validate a cache id or a tag (security, reliable filenames, reserved prefixes...) + * + * Throw an exception if a problem is found + * + * @param string $string Cache id or tag + * @throws Zend_Cache_Exception + * @return void + */ + protected static function _validateIdOrTag($string) + { + if (!is_string($string)) { + Zend_Cache::throwException('Invalid id or tag : must be a string'); + } + if (substr($string, 0, 9) == 'internal-') { + Zend_Cache::throwException('"internal-*" ids or tags are reserved'); + } + if (!preg_match('~^[a-zA-Z0-9_]+$~D', $string)) { + Zend_Cache::throwException("Invalid id or tag '$string' : must use only [a-zA-Z0-9_]"); + } + } + + /** + * Validate a tags array (security, reliable filenames, reserved prefixes...) + * + * Throw an exception if a problem is found + * + * @param array $tags Array of tags + * @throws Zend_Cache_Exception + * @return void + */ + protected static function _validateTagsArray($tags) + { + if (!is_array($tags)) { + Zend_Cache::throwException('Invalid tags array : must be an array'); + } + foreach($tags as $tag) { + self::_validateIdOrTag($tag); + } + reset($tags); + } + + /** + * Make sure if we enable logging that the Zend_Log class + * is available. + * Create a default log object if none is set. + * + * @throws Zend_Cache_Exception + * @return void + */ + protected function _loggerSanity() + { + if (!isset($this->_options['logging']) || !$this->_options['logging']) { + return; + } + + if (isset($this->_options['logger']) && $this->_options['logger'] instanceof Zend_Log) { + return; + } + + // Create a default logger to the standard output stream + require_once 'Zend/Log.php'; + require_once 'Zend/Log/Writer/Stream.php'; + require_once 'Zend/Log/Filter/Priority.php'; + $logger = new Zend_Log(new Zend_Log_Writer_Stream('php://output')); + $logger->addFilter(new Zend_Log_Filter_Priority(Zend_Log::WARN, '<=')); + $this->_options['logger'] = $logger; + } + + /** + * Log a message at the WARN (4) priority. + * + * @param string $message + * @throws Zend_Cache_Exception + * @return void + */ + protected function _log($message, $priority = 4) + { + if (!$this->_options['logging']) { + return; + } + if (!(isset($this->_options['logger']) || $this->_options['logger'] instanceof Zend_Log)) { + Zend_Cache::throwException('Logging is enabled but logger is not set'); + } + $logger = $this->_options['logger']; + $logger->log($message, $priority); + } + + /** + * Make and return a cache id + * + * Checks 'cache_id_prefix' and returns new id with prefix or simply the id if null + * + * @param string $id Cache id + * @return string Cache id (with or without prefix) + */ + protected function _id($id) + { + if (($id !== null) && isset($this->_options['cache_id_prefix'])) { + return $this->_options['cache_id_prefix'] . $id; // return with prefix + } + return $id; // no prefix, just return the $id passed + } + +} diff --git a/library/Zend/Cache/Exception.php b/library/Zend/Cache/Exception.php new file mode 100644 index 00000000..a3b519ce --- /dev/null +++ b/library/Zend/Cache/Exception.php @@ -0,0 +1,32 @@ +_tags = $tags; + $this->_extension = $extension; + ob_start(array($this, '_flush')); + ob_implicit_flush(false); + $this->_idStack[] = $id; + return false; + } + + /** + * callback for output buffering + * (shouldn't really be called manually) + * + * @param string $data Buffered output + * @return string Data to send to browser + */ + public function _flush($data) + { + $id = array_pop($this->_idStack); + if ($id === null) { + Zend_Cache::throwException('use of _flush() without a start()'); + } + if ($this->_extension) { + $this->save(serialize(array($data, $this->_extension)), $id, $this->_tags); + } else { + $this->save($data, $id, $this->_tags); + } + return $data; + } +} diff --git a/library/Zend/Cache/Frontend/Class.php b/library/Zend/Cache/Frontend/Class.php new file mode 100644 index 00000000..91f26ab1 --- /dev/null +++ b/library/Zend/Cache/Frontend/Class.php @@ -0,0 +1,265 @@ + (mixed) cached_entity : + * - if set to a class name, we will cache an abstract class and will use only static calls + * - if set to an object, we will cache this object methods + * + * ====> (boolean) cache_by_default : + * - if true, method calls will be cached by default + * + * ====> (array) cached_methods : + * - an array of method names which will be cached (even if cache_by_default = false) + * + * ====> (array) non_cached_methods : + * - an array of method names which won't be cached (even if cache_by_default = true) + * + * @var array available options + */ + protected $_specificOptions = array( + 'cached_entity' => null, + 'cache_by_default' => true, + 'cached_methods' => array(), + 'non_cached_methods' => array() + ); + + /** + * Tags array + * + * @var array + */ + private $_tags = array(); + + /** + * SpecificLifetime value + * + * false => no specific life time + * + * @var int + */ + private $_specificLifetime = false; + + /** + * The cached object or the name of the cached abstract class + * + * @var mixed + */ + private $_cachedEntity = null; + + /** + * The class name of the cached object or cached abstract class + * + * Used to differentiate between different classes with the same method calls. + * + * @var string + */ + private $_cachedEntityLabel = ''; + + /** + * Priority (used by some particular backends) + * + * @var int + */ + private $_priority = 8; + + /** + * Constructor + * + * @param array $options Associative array of options + * @throws Zend_Cache_Exception + * @return void + */ + public function __construct(array $options = array()) + { + while (list($name, $value) = each($options)) { + $this->setOption($name, $value); + } + if ($this->_specificOptions['cached_entity'] === null) { + Zend_Cache::throwException('cached_entity must be set !'); + } + $this->setCachedEntity($this->_specificOptions['cached_entity']); + $this->setOption('automatic_serialization', true); + } + + /** + * Set a specific life time + * + * @param int $specificLifetime + * @return void + */ + public function setSpecificLifetime($specificLifetime = false) + { + $this->_specificLifetime = $specificLifetime; + } + + /** + * Set the priority (used by some particular backends) + * + * @param int $priority integer between 0 (very low priority) and 10 (maximum priority) + */ + public function setPriority($priority) + { + $this->_priority = $priority; + } + + /** + * Public frontend to set an option + * + * Just a wrapper to get a specific behaviour for cached_entity + * + * @param string $name Name of the option + * @param mixed $value Value of the option + * @throws Zend_Cache_Exception + * @return void + */ + public function setOption($name, $value) + { + if ($name == 'cached_entity') { + $this->setCachedEntity($value); + } else { + parent::setOption($name, $value); + } + } + + /** + * Specific method to set the cachedEntity + * + * if set to a class name, we will cache an abstract class and will use only static calls + * if set to an object, we will cache this object methods + * + * @param mixed $cachedEntity + */ + public function setCachedEntity($cachedEntity) + { + if (!is_string($cachedEntity) && !is_object($cachedEntity)) { + Zend_Cache::throwException('cached_entity must be an object or a class name'); + } + $this->_cachedEntity = $cachedEntity; + $this->_specificOptions['cached_entity'] = $cachedEntity; + if (is_string($this->_cachedEntity)){ + $this->_cachedEntityLabel = $this->_cachedEntity; + } else { + $ro = new ReflectionObject($this->_cachedEntity); + $this->_cachedEntityLabel = $ro->getName(); + } + } + + /** + * Set the cache array + * + * @param array $tags + * @return void + */ + public function setTagsArray($tags = array()) + { + $this->_tags = $tags; + } + + /** + * Main method : call the specified method or get the result from cache + * + * @param string $name Method name + * @param array $parameters Method parameters + * @return mixed Result + */ + public function __call($name, $parameters) + { + $callback = array($this->_cachedEntity, $name); + + if (!is_callable($callback, false)) { + Zend_Cache::throwException('Invalid callback'); + } + + $cacheBool1 = $this->_specificOptions['cache_by_default']; + $cacheBool2 = in_array($name, $this->_specificOptions['cached_methods']); + $cacheBool3 = in_array($name, $this->_specificOptions['non_cached_methods']); + $cache = (($cacheBool1 || $cacheBool2) && (!$cacheBool3)); + if (!$cache) { + // We do not have not cache + return call_user_func_array($callback, $parameters); + } + + $id = $this->_makeId($name, $parameters); + if ( ($rs = $this->load($id)) && isset($rs[0], $rs[1]) ) { + // A cache is available + $output = $rs[0]; + $return = $rs[1]; + } else { + // A cache is not available (or not valid for this frontend) + ob_start(); + ob_implicit_flush(false); + + try { + $return = call_user_func_array($callback, $parameters); + $output = ob_get_clean(); + $data = array($output, $return); + $this->save($data, $id, $this->_tags, $this->_specificLifetime, $this->_priority); + } catch (Exception $e) { + ob_end_clean(); + throw $e; + } + } + + echo $output; + return $return; + } + + /** + * ZF-9970 + * + * @deprecated + */ + private function _makeId($name, $args) + { + return $this->makeId($name, $args); + } + + /** + * Make a cache id from the method name and parameters + * + * @param string $name Method name + * @param array $args Method parameters + * @return string Cache id + */ + public function makeId($name, array $args = array()) + { + return md5($this->_cachedEntityLabel . '__' . $name . '__' . serialize($args)); + } + +} diff --git a/library/Zend/Cache/Frontend/File.php b/library/Zend/Cache/Frontend/File.php new file mode 100644 index 00000000..5bc1f35c --- /dev/null +++ b/library/Zend/Cache/Frontend/File.php @@ -0,0 +1,222 @@ + (string) master_file : + * - a complete path of the master file + * - deprecated (see master_files) + * + * ====> (array) master_files : + * - an array of complete path of master files + * - this option has to be set ! + * + * ====> (string) master_files_mode : + * - Zend_Cache_Frontend_File::MODE_AND or Zend_Cache_Frontend_File::MODE_OR + * - if MODE_AND, then all master files have to be touched to get a cache invalidation + * - if MODE_OR (default), then a single touched master file is enough to get a cache invalidation + * + * ====> (boolean) ignore_missing_master_files + * - if set to true, missing master files are ignored silently + * - if set to false (default), an exception is thrown if there is a missing master file + * @var array available options + */ + protected $_specificOptions = array( + 'master_file' => null, + 'master_files' => null, + 'master_files_mode' => 'OR', + 'ignore_missing_master_files' => false + ); + + /** + * Master file mtimes + * + * Array of int + * + * @var array + */ + private $_masterFile_mtimes = null; + + /** + * Constructor + * + * @param array $options Associative array of options + * @throws Zend_Cache_Exception + * @return void + */ + public function __construct(array $options = array()) + { + while (list($name, $value) = each($options)) { + $this->setOption($name, $value); + } + if (!isset($this->_specificOptions['master_files'])) { + Zend_Cache::throwException('master_files option must be set'); + } + } + + /** + * Change the master_files option + * + * @param array $masterFiles the complete paths and name of the master files + */ + public function setMasterFiles(array $masterFiles) + { + $this->_specificOptions['master_file'] = null; // to keep a compatibility + $this->_specificOptions['master_files'] = null; + $this->_masterFile_mtimes = array(); + + clearstatcache(); + $i = 0; + foreach ($masterFiles as $masterFile) { + if (file_exists($masterFile)) { + $mtime = filemtime($masterFile); + } else { + $mtime = false; + } + + if (!$this->_specificOptions['ignore_missing_master_files'] && !$mtime) { + Zend_Cache::throwException('Unable to read master_file : ' . $masterFile); + } + + $this->_masterFile_mtimes[$i] = $mtime; + $this->_specificOptions['master_files'][$i] = $masterFile; + if ($i === 0) { // to keep a compatibility + $this->_specificOptions['master_file'] = $masterFile; + } + + $i++; + } + } + + /** + * Change the master_file option + * + * To keep the compatibility + * + * @deprecated + * @param string $masterFile the complete path and name of the master file + */ + public function setMasterFile($masterFile) + { + $this->setMasterFiles(array($masterFile)); + } + + /** + * Public frontend to set an option + * + * Just a wrapper to get a specific behaviour for master_file + * + * @param string $name Name of the option + * @param mixed $value Value of the option + * @throws Zend_Cache_Exception + * @return void + */ + public function setOption($name, $value) + { + if ($name == 'master_file') { + $this->setMasterFile($value); + } else if ($name == 'master_files') { + $this->setMasterFiles($value); + } else { + parent::setOption($name, $value); + } + } + + /** + * Test if a cache is available for the given id and (if yes) return it (false else) + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @param boolean $doNotUnserialize Do not serialize (even if automatic_serialization is true) => for internal use + * @return mixed|false Cached datas + */ + public function load($id, $doNotTestCacheValidity = false, $doNotUnserialize = false) + { + if (!$doNotTestCacheValidity) { + if ($this->test($id)) { + return parent::load($id, true, $doNotUnserialize); + } + return false; + } + return parent::load($id, true, $doNotUnserialize); + } + + /** + * Test if a cache is available for the given id + * + * @param string $id Cache id + * @return int|false Last modified time of cache entry if it is available, false otherwise + */ + public function test($id) + { + $lastModified = parent::test($id); + if ($lastModified) { + if ($this->_specificOptions['master_files_mode'] == self::MODE_AND) { + // MODE_AND + foreach($this->_masterFile_mtimes as $masterFileMTime) { + if ($masterFileMTime) { + if ($lastModified > $masterFileMTime) { + return $lastModified; + } + } + } + } else { + // MODE_OR + $res = true; + foreach($this->_masterFile_mtimes as $masterFileMTime) { + if ($masterFileMTime) { + if ($lastModified <= $masterFileMTime) { + return false; + } + } + } + return $lastModified; + } + } + return false; + } + +} + diff --git a/library/Zend/Cache/Frontend/Function.php b/library/Zend/Cache/Frontend/Function.php new file mode 100644 index 00000000..dfbcf71e --- /dev/null +++ b/library/Zend/Cache/Frontend/Function.php @@ -0,0 +1,179 @@ + (boolean) cache_by_default : + * - if true, function calls will be cached by default + * + * ====> (array) cached_functions : + * - an array of function names which will be cached (even if cache_by_default = false) + * + * ====> (array) non_cached_functions : + * - an array of function names which won't be cached (even if cache_by_default = true) + * + * @var array options + */ + protected $_specificOptions = array( + 'cache_by_default' => true, + 'cached_functions' => array(), + 'non_cached_functions' => array() + ); + + /** + * Constructor + * + * @param array $options Associative array of options + * @return void + */ + public function __construct(array $options = array()) + { + while (list($name, $value) = each($options)) { + $this->setOption($name, $value); + } + $this->setOption('automatic_serialization', true); + } + + /** + * Main method : call the specified function or get the result from cache + * + * @param callback $callback A valid callback + * @param array $parameters Function parameters + * @param array $tags Cache tags + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @param int $priority integer between 0 (very low priority) and 10 (maximum priority) used by some particular backends + * @return mixed Result + */ + public function call($callback, array $parameters = array(), $tags = array(), $specificLifetime = false, $priority = 8) + { + if (!is_callable($callback, true, $name)) { + Zend_Cache::throwException('Invalid callback'); + } + + $cacheBool1 = $this->_specificOptions['cache_by_default']; + $cacheBool2 = in_array($name, $this->_specificOptions['cached_functions']); + $cacheBool3 = in_array($name, $this->_specificOptions['non_cached_functions']); + $cache = (($cacheBool1 || $cacheBool2) && (!$cacheBool3)); + if (!$cache) { + // Caching of this callback is disabled + return call_user_func_array($callback, $parameters); + } + + $id = $this->_makeId($callback, $parameters); + if ( ($rs = $this->load($id)) && isset($rs[0], $rs[1])) { + // A cache is available + $output = $rs[0]; + $return = $rs[1]; + } else { + // A cache is not available (or not valid for this frontend) + ob_start(); + ob_implicit_flush(false); + $return = call_user_func_array($callback, $parameters); + $output = ob_get_clean(); + $data = array($output, $return); + $this->save($data, $id, $tags, $specificLifetime, $priority); + } + + echo $output; + return $return; + } + + /** + * ZF-9970 + * + * @deprecated + */ + private function _makeId($callback, array $args) + { + return $this->makeId($callback, $args); + } + + /** + * Make a cache id from the function name and parameters + * + * @param callback $callback A valid callback + * @param array $args Function parameters + * @throws Zend_Cache_Exception + * @return string Cache id + */ + public function makeId($callback, array $args = array()) + { + if (!is_callable($callback, true, $name)) { + Zend_Cache::throwException('Invalid callback'); + } + + // functions, methods and classnames are case-insensitive + $name = strtolower($name); + + // generate a unique id for object callbacks + if (is_object($callback)) { // Closures & __invoke + $object = $callback; + } elseif (isset($callback[0])) { // array($object, 'method') + $object = $callback[0]; + } + if (isset($object)) { + try { + $tmp = @serialize($callback); + } catch (Exception $e) { + Zend_Cache::throwException($e->getMessage()); + } + if (!$tmp) { + $lastErr = error_get_last(); + Zend_Cache::throwException("Can't serialize callback object to generate id: {$lastErr['message']}"); + } + $name.= '__' . $tmp; + } + + // generate a unique id for arguments + $argsStr = ''; + if ($args) { + try { + $argsStr = @serialize(array_values($args)); + } catch (Exception $e) { + Zend_Cache::throwException($e->getMessage()); + } + if (!$argsStr) { + $lastErr = error_get_last(); + throw Zend_Cache::throwException("Can't serialize arguments to generate id: {$lastErr['message']}"); + } + } + + return md5($name . $argsStr); + } + +} diff --git a/library/Zend/Cache/Frontend/Output.php b/library/Zend/Cache/Frontend/Output.php new file mode 100644 index 00000000..290731b2 --- /dev/null +++ b/library/Zend/Cache/Frontend/Output.php @@ -0,0 +1,105 @@ +_idStack = array(); + } + + /** + * Start the cache + * + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @param boolean $echoData If set to true, datas are sent to the browser if the cache is hit (simpy returned else) + * @return mixed True if the cache is hit (false else) with $echoData=true (default) ; string else (datas) + */ + public function start($id, $doNotTestCacheValidity = false, $echoData = true) + { + $data = $this->load($id, $doNotTestCacheValidity); + if ($data !== false) { + if ( $echoData ) { + echo($data); + return true; + } else { + return $data; + } + } + ob_start(); + ob_implicit_flush(false); + $this->_idStack[] = $id; + return false; + } + + /** + * Stop the cache + * + * @param array $tags Tags array + * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime) + * @param string $forcedDatas If not null, force written datas with this + * @param boolean $echoData If set to true, datas are sent to the browser + * @param int $priority integer between 0 (very low priority) and 10 (maximum priority) used by some particular backends + * @return void + */ + public function end($tags = array(), $specificLifetime = false, $forcedDatas = null, $echoData = true, $priority = 8) + { + if ($forcedDatas === null) { + $data = ob_get_clean(); + } else { + $data =& $forcedDatas; + } + $id = array_pop($this->_idStack); + if ($id === null) { + Zend_Cache::throwException('use of end() without a start()'); + } + $this->save($data, $id, $tags, $specificLifetime, $priority); + if ($echoData) { + echo($data); + } + } + +} diff --git a/library/Zend/Cache/Frontend/Page.php b/library/Zend/Cache/Frontend/Page.php new file mode 100644 index 00000000..b9bd29fd --- /dev/null +++ b/library/Zend/Cache/Frontend/Page.php @@ -0,0 +1,404 @@ + (boolean) http_conditional : + * - if true, http conditional mode is on + * WARNING : http_conditional OPTION IS NOT IMPLEMENTED FOR THE MOMENT (TODO) + * + * ====> (boolean) debug_header : + * - if true, a debug text is added before each cached pages + * + * ====> (boolean) content_type_memorization : + * - deprecated => use memorize_headers instead + * - if the Content-Type header is sent after the cache was started, the + * corresponding value can be memorized and replayed when the cache is hit + * (if false (default), the frontend doesn't take care of Content-Type header) + * + * ====> (array) memorize_headers : + * - an array of strings corresponding to some HTTP headers name. Listed headers + * will be stored with cache datas and "replayed" when the cache is hit + * + * ====> (array) default_options : + * - an associative array of default options : + * - (boolean) cache : cache is on by default if true + * - (boolean) cacheWithXXXVariables (XXXX = 'Get', 'Post', 'Session', 'Files' or 'Cookie') : + * if true, cache is still on even if there are some variables in this superglobal array + * if false, cache is off if there are some variables in this superglobal array + * - (boolean) makeIdWithXXXVariables (XXXX = 'Get', 'Post', 'Session', 'Files' or 'Cookie') : + * if true, we have to use the content of this superglobal array to make a cache id + * if false, the cache id won't be dependent of the content of this superglobal array + * - (int) specific_lifetime : cache specific lifetime + * (false => global lifetime is used, null => infinite lifetime, + * integer => this lifetime is used), this "lifetime" is probably only + * usefull when used with "regexps" array + * - (array) tags : array of tags (strings) + * - (int) priority : integer between 0 (very low priority) and 10 (maximum priority) used by + * some particular backends + * + * ====> (array) regexps : + * - an associative array to set options only for some REQUEST_URI + * - keys are (pcre) regexps + * - values are associative array with specific options to set if the regexp matchs on $_SERVER['REQUEST_URI'] + * (see default_options for the list of available options) + * - if several regexps match the $_SERVER['REQUEST_URI'], only the last one will be used + * + * @var array options + */ + protected $_specificOptions = array( + 'http_conditional' => false, + 'debug_header' => false, + 'content_type_memorization' => false, + 'memorize_headers' => array(), + 'default_options' => array( + 'cache_with_get_variables' => false, + 'cache_with_post_variables' => false, + 'cache_with_session_variables' => false, + 'cache_with_files_variables' => false, + 'cache_with_cookie_variables' => false, + 'make_id_with_get_variables' => true, + 'make_id_with_post_variables' => true, + 'make_id_with_session_variables' => true, + 'make_id_with_files_variables' => true, + 'make_id_with_cookie_variables' => true, + 'cache' => true, + 'specific_lifetime' => false, + 'tags' => array(), + 'priority' => null + ), + 'regexps' => array() + ); + + /** + * Internal array to store some options + * + * @var array associative array of options + */ + protected $_activeOptions = array(); + + /** + * If true, the page won't be cached + * + * @var boolean + */ + protected $_cancel = false; + + /** + * Constructor + * + * @param array $options Associative array of options + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @throws Zend_Cache_Exception + * @return void + */ + public function __construct(array $options = array()) + { + while (list($name, $value) = each($options)) { + $name = strtolower($name); + switch ($name) { + case 'regexps': + $this->_setRegexps($value); + break; + case 'default_options': + $this->_setDefaultOptions($value); + break; + case 'content_type_memorization': + $this->_setContentTypeMemorization($value); + break; + default: + $this->setOption($name, $value); + } + } + if (isset($this->_specificOptions['http_conditional'])) { + if ($this->_specificOptions['http_conditional']) { + Zend_Cache::throwException('http_conditional is not implemented for the moment !'); + } + } + $this->setOption('automatic_serialization', true); + } + + /** + * Specific setter for the 'default_options' option (with some additional tests) + * + * @param array $options Associative array + * @throws Zend_Cache_Exception + * @return void + */ + protected function _setDefaultOptions($options) + { + if (!is_array($options)) { + Zend_Cache::throwException('default_options must be an array !'); + } + foreach ($options as $key=>$value) { + if (!is_string($key)) { + Zend_Cache::throwException("invalid option [$key] !"); + } + $key = strtolower($key); + if (isset($this->_specificOptions['default_options'][$key])) { + $this->_specificOptions['default_options'][$key] = $value; + } + } + } + + /** + * Set the deprecated contentTypeMemorization option + * + * @param boolean $value value + * @return void + * @deprecated + */ + protected function _setContentTypeMemorization($value) + { + $found = null; + foreach ($this->_specificOptions['memorize_headers'] as $key => $value) { + if (strtolower($value) == 'content-type') { + $found = $key; + } + } + if ($value) { + if (!$found) { + $this->_specificOptions['memorize_headers'][] = 'Content-Type'; + } + } else { + if ($found) { + unset($this->_specificOptions['memorize_headers'][$found]); + } + } + } + + /** + * Specific setter for the 'regexps' option (with some additional tests) + * + * @param array $options Associative array + * @throws Zend_Cache_Exception + * @return void + */ + protected function _setRegexps($regexps) + { + if (!is_array($regexps)) { + Zend_Cache::throwException('regexps option must be an array !'); + } + foreach ($regexps as $regexp=>$conf) { + if (!is_array($conf)) { + Zend_Cache::throwException('regexps option must be an array of arrays !'); + } + $validKeys = array_keys($this->_specificOptions['default_options']); + foreach ($conf as $key=>$value) { + if (!is_string($key)) { + Zend_Cache::throwException("unknown option [$key] !"); + } + $key = strtolower($key); + if (!in_array($key, $validKeys)) { + unset($regexps[$regexp][$key]); + } + } + } + $this->setOption('regexps', $regexps); + } + + /** + * Start the cache + * + * @param string $id (optional) A cache id (if you set a value here, maybe you have to use Output frontend instead) + * @param boolean $doNotDie For unit testing only ! + * @return boolean True if the cache is hit (false else) + */ + public function start($id = false, $doNotDie = false) + { + $this->_cancel = false; + $lastMatchingRegexp = null; + if (isset($_SERVER['REQUEST_URI'])) { + foreach ($this->_specificOptions['regexps'] as $regexp => $conf) { + if (preg_match("`$regexp`", $_SERVER['REQUEST_URI'])) { + $lastMatchingRegexp = $regexp; + } + } + } + $this->_activeOptions = $this->_specificOptions['default_options']; + if ($lastMatchingRegexp !== null) { + $conf = $this->_specificOptions['regexps'][$lastMatchingRegexp]; + foreach ($conf as $key=>$value) { + $this->_activeOptions[$key] = $value; + } + } + if (!($this->_activeOptions['cache'])) { + return false; + } + if (!$id) { + $id = $this->_makeId(); + if (!$id) { + return false; + } + } + $array = $this->load($id); + if ($array !== false) { + $data = $array['data']; + $headers = $array['headers']; + if (!headers_sent()) { + foreach ($headers as $key=>$headerCouple) { + $name = $headerCouple[0]; + $value = $headerCouple[1]; + header("$name: $value"); + } + } + if ($this->_specificOptions['debug_header']) { + echo 'DEBUG HEADER : This is a cached page !'; + } + echo $data; + if ($doNotDie) { + return true; + } + die(); + } + ob_start(array($this, '_flush')); + ob_implicit_flush(false); + return false; + } + + /** + * Cancel the current caching process + */ + public function cancel() + { + $this->_cancel = true; + } + + /** + * callback for output buffering + * (shouldn't really be called manually) + * + * @param string $data Buffered output + * @return string Data to send to browser + */ + public function _flush($data) + { + if ($this->_cancel) { + return $data; + } + $contentType = null; + $storedHeaders = array(); + $headersList = headers_list(); + foreach($this->_specificOptions['memorize_headers'] as $key=>$headerName) { + foreach ($headersList as $headerSent) { + $tmp = explode(':', $headerSent); + $headerSentName = trim(array_shift($tmp)); + if (strtolower($headerName) == strtolower($headerSentName)) { + $headerSentValue = trim(implode(':', $tmp)); + $storedHeaders[] = array($headerSentName, $headerSentValue); + } + } + } + $array = array( + 'data' => $data, + 'headers' => $storedHeaders + ); + $this->save($array, null, $this->_activeOptions['tags'], $this->_activeOptions['specific_lifetime'], $this->_activeOptions['priority']); + return $data; + } + + /** + * Make an id depending on REQUEST_URI and superglobal arrays (depending on options) + * + * @return mixed|false a cache id (string), false if the cache should have not to be used + */ + protected function _makeId() + { + $tmp = $_SERVER['REQUEST_URI']; + $array = explode('?', $tmp, 2); + $tmp = $array[0]; + foreach (array('Get', 'Post', 'Session', 'Files', 'Cookie') as $arrayName) { + $tmp2 = $this->_makePartialId($arrayName, $this->_activeOptions['cache_with_' . strtolower($arrayName) . '_variables'], $this->_activeOptions['make_id_with_' . strtolower($arrayName) . '_variables']); + if ($tmp2===false) { + return false; + } + $tmp = $tmp . $tmp2; + } + return md5($tmp); + } + + /** + * Make a partial id depending on options + * + * @param string $arrayName Superglobal array name + * @param bool $bool1 If true, cache is still on even if there are some variables in the superglobal array + * @param bool $bool2 If true, we have to use the content of the superglobal array to make a partial id + * @return mixed|false Partial id (string) or false if the cache should have not to be used + */ + protected function _makePartialId($arrayName, $bool1, $bool2) + { + switch ($arrayName) { + case 'Get': + $var = $_GET; + break; + case 'Post': + $var = $_POST; + break; + case 'Session': + if (isset($_SESSION)) { + $var = $_SESSION; + } else { + $var = null; + } + break; + case 'Cookie': + if (isset($_COOKIE)) { + $var = $_COOKIE; + } else { + $var = null; + } + break; + case 'Files': + $var = $_FILES; + break; + default: + return false; + } + if ($bool1) { + if ($bool2) { + return serialize($var); + } + return ''; + } + if (count($var) > 0) { + return false; + } + return ''; + } + +} diff --git a/library/Zend/Cache/Manager.php b/library/Zend/Cache/Manager.php new file mode 100644 index 00000000..5d73f7e4 --- /dev/null +++ b/library/Zend/Cache/Manager.php @@ -0,0 +1,298 @@ + array( + 'frontend' => array( + 'name' => 'Core', + 'options' => array( + 'automatic_serialization' => true, + ), + ), + 'backend' => array( + 'name' => 'File', + 'options' => array( + // use system temp dir by default of file backend + // 'cache_dir' => '../cache', + ), + ), + ), + + // Static Page HTML Cache + 'page' => array( + 'frontend' => array( + 'name' => 'Capture', + 'options' => array( + 'ignore_user_abort' => true, + ), + ), + 'backend' => array( + 'name' => 'Static', + 'options' => array( + 'public_dir' => '../public', + ), + ), + ), + + // Tag Cache + 'pagetag' => array( + 'frontend' => array( + 'name' => 'Core', + 'options' => array( + 'automatic_serialization' => true, + 'lifetime' => null + ), + ), + 'backend' => array( + 'name' => 'File', + 'options' => array( + // use system temp dir by default of file backend + // 'cache_dir' => '../cache', + // use default umask of file backend + // 'cache_file_umask' => 0644 + ), + ), + ), + ); + + /** + * Set a new cache for the Cache Manager to contain + * + * @param string $name + * @param Zend_Cache_Core $cache + * @return Zend_Cache_Manager + */ + public function setCache($name, Zend_Cache_Core $cache) + { + $this->_caches[$name] = $cache; + return $this; + } + + /** + * Check if the Cache Manager contains the named cache object, or a named + * configuration template to lazy load the cache object + * + * @param string $name + * @return bool + */ + public function hasCache($name) + { + if (isset($this->_caches[$name]) + || $this->hasCacheTemplate($name) + ) { + return true; + } + return false; + } + + /** + * Fetch the named cache object, or instantiate and return a cache object + * using a named configuration template + * + * @param string $name + * @return Zend_Cache_Core + */ + public function getCache($name) + { + if (isset($this->_caches[$name])) { + return $this->_caches[$name]; + } + if (isset($this->_optionTemplates[$name])) { + if ($name == self::PAGECACHE + && (!isset($this->_optionTemplates[$name]['backend']['options']['tag_cache']) + || !$this->_optionTemplates[$name]['backend']['options']['tag_cache'] instanceof Zend_Cache_Core) + ) { + $this->_optionTemplates[$name]['backend']['options']['tag_cache'] + = $this->getCache(self::PAGETAGCACHE); + } + + $this->_caches[$name] = Zend_Cache::factory( + $this->_optionTemplates[$name]['frontend']['name'], + $this->_optionTemplates[$name]['backend']['name'], + isset($this->_optionTemplates[$name]['frontend']['options']) ? $this->_optionTemplates[$name]['frontend']['options'] : array(), + isset($this->_optionTemplates[$name]['backend']['options']) ? $this->_optionTemplates[$name]['backend']['options'] : array(), + isset($this->_optionTemplates[$name]['frontend']['customFrontendNaming']) ? $this->_optionTemplates[$name]['frontend']['customFrontendNaming'] : false, + isset($this->_optionTemplates[$name]['backend']['customBackendNaming']) ? $this->_optionTemplates[$name]['backend']['customBackendNaming'] : false, + isset($this->_optionTemplates[$name]['frontendBackendAutoload']) ? $this->_optionTemplates[$name]['frontendBackendAutoload'] : false + ); + + return $this->_caches[$name]; + } + } + + /** + * Fetch all available caches + * + * @return array An array of all available caches with it's names as key + */ + public function getCaches() + { + $caches = $this->_caches; + foreach ($this->_optionTemplates as $name => $tmp) { + if (!isset($caches[$name])) { + $caches[$name] = $this->getCache($name); + } + } + return $caches; + } + + /** + * Set a named configuration template from which a cache object can later + * be lazy loaded + * + * @param string $name + * @param array $options + * @return Zend_Cache_Manager + */ + public function setCacheTemplate($name, $options) + { + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } elseif (!is_array($options)) { + require_once 'Zend/Cache/Exception.php'; + throw new Zend_Cache_Exception('Options passed must be in' + . ' an associative array or instance of Zend_Config'); + } + $this->_optionTemplates[$name] = $options; + return $this; + } + + /** + * Check if the named configuration template + * + * @param string $name + * @return bool + */ + public function hasCacheTemplate($name) + { + if (isset($this->_optionTemplates[$name])) { + return true; + } + return false; + } + + /** + * Get the named configuration template + * + * @param string $name + * @return array + */ + public function getCacheTemplate($name) + { + if (isset($this->_optionTemplates[$name])) { + return $this->_optionTemplates[$name]; + } + } + + /** + * Pass an array containing changes to be applied to a named + * configuration + * template + * + * @param string $name + * @param array $options + * @return Zend_Cache_Manager + * @throws Zend_Cache_Exception for invalid options format or if option templates do not have $name + */ + public function setTemplateOptions($name, $options) + { + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } elseif (!is_array($options)) { + require_once 'Zend/Cache/Exception.php'; + throw new Zend_Cache_Exception('Options passed must be in' + . ' an associative array or instance of Zend_Config'); + } + if (!isset($this->_optionTemplates[$name])) { + throw new Zend_Cache_Exception('A cache configuration template' + . 'does not exist with the name "' . $name . '"'); + } + $this->_optionTemplates[$name] + = $this->_mergeOptions($this->_optionTemplates[$name], $options); + return $this; + } + + /** + * Simple method to merge two configuration arrays + * + * @param array $current + * @param array $options + * @return array + */ + protected function _mergeOptions(array $current, array $options) + { + if (isset($options['frontend']['name'])) { + $current['frontend']['name'] = $options['frontend']['name']; + } + if (isset($options['backend']['name'])) { + $current['backend']['name'] = $options['backend']['name']; + } + if (isset($options['frontend']['options'])) { + foreach ($options['frontend']['options'] as $key=>$value) { + $current['frontend']['options'][$key] = $value; + } + } + if (isset($options['backend']['options'])) { + foreach ($options['backend']['options'] as $key=>$value) { + $current['backend']['options'][$key] = $value; + } + } + return $current; + } +} diff --git a/library/Zend/Captcha/Adapter.php b/library/Zend/Captcha/Adapter.php new file mode 100644 index 00000000..c0e7d978 --- /dev/null +++ b/library/Zend/Captcha/Adapter.php @@ -0,0 +1,76 @@ +_name; + } + + /** + * Set name + * + * @param string $name + */ + public function setName($name) + { + $this->_name = $name; + return $this; + } + + /** + * Constructor + * + * @param array|Zend_Config $options + * @return void + */ + public function __construct($options = null) + { + // Set options + if (is_array($options)) { + $this->setOptions($options); + } else if ($options instanceof Zend_Config) { + $this->setConfig($options); + } + } + + /** + * Set single option for the object + * + * @param string $key + * @param string $value + * @return Zend_Form_Element + */ + public function setOption($key, $value) + { + if (in_array(strtolower($key), $this->_skipOptions)) { + return $this; + } + + $method = 'set' . ucfirst ($key); + if (method_exists ($this, $method)) { + // Setter exists; use it + $this->$method ($value); + $this->_options[$key] = $value; + } elseif (property_exists($this, $key)) { + // Assume it's metadata + $this->$key = $value; + $this->_options[$key] = $value; + } + return $this; + } + + /** + * Set object state from options array + * + * @param array $options + * @return Zend_Form_Element + */ + public function setOptions($options = null) + { + foreach ($options as $key => $value) { + $this->setOption($key, $value); + } + return $this; + } + + /** + * Retrieve options representing object state + * + * @return array + */ + public function getOptions() + { + return $this->_options; + } + + /** + * Set object state from config object + * + * @param Zend_Config $config + * @return Zend_Captcha_Base + */ + public function setConfig(Zend_Config $config) + { + return $this->setOptions($config->toArray()); + } + + /** + * Get optional decorator + * + * By default, return null, indicating no extra decorator needed. + * + * @return null + */ + public function getDecorator() + { + return null; + } +} diff --git a/library/Zend/Captcha/Dumb.php b/library/Zend/Captcha/Dumb.php new file mode 100644 index 00000000..46b8d059 --- /dev/null +++ b/library/Zend/Captcha/Dumb.php @@ -0,0 +1,52 @@ +' + . strrev($this->getWord()) + . ''; + } +} diff --git a/library/Zend/Captcha/Exception.php b/library/Zend/Captcha/Exception.php new file mode 100644 index 00000000..1b4f27f5 --- /dev/null +++ b/library/Zend/Captcha/Exception.php @@ -0,0 +1,37 @@ +_figlet = new Zend_Text_Figlet($options); + } + + /** + * Generate new captcha + * + * @return string + */ + public function generate() + { + $this->_useNumbers = false; + return parent::generate(); + } + + /** + * Display the captcha + * + * @param Zend_View_Interface $view + * @param mixed $element + * @return string + */ + public function render(Zend_View_Interface $view = null, $element = null) + { + return '
'
+             . $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 00000000..de644ea4 --- /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->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 '' . $this->getImgAlt()
+             . ' '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 00000000..b5ab2f98 --- /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 00000000..f8ea6a6b --- /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 00000000..b40db7d2 --- /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 00000000..6d5c34b9 --- /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 00000000..f1683a9f --- /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 00000000..ee278092 --- /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 00000000..1d2c3581 --- /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 00000000..7749360b --- /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 00000000..3942c58c --- /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 00000000..71d1e271 --- /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 00000000..cf4f1ab6 --- /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/OperationNotAvailableException.php b/library/Zend/Cloud/OperationNotAvailableException.php new file mode 100644 index 00000000..07f71ee1 --- /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 00000000..253b2a15 --- /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 00000000..61a170e2 --- /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 00000000..35acb4d8 --- /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 00000000..c782aab7 --- /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 00000000..834939e4 --- /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 00000000..bcb02252 --- /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) { + 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 00000000..3d58344c --- /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/S3.php b/library/Zend/Cloud/StorageService/Adapter/S3.php new file mode 100644 index 00000000..21672e6b --- /dev/null +++ b/library/Zend/Cloud/StorageService/Adapter/S3.php @@ -0,0 +1,327 @@ +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 { + // TODO We *really* need to add support for object copying in the S3 adapter + $item = $this->fetch($_getFullPath(sourcePath), $options); + $this->storeItem($item, $destinationPath, $options); + } 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 00000000..d883aa71 --- /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 00000000..3cab170b --- /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 00000000..df40dbdf --- /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 00000000..4061d505 --- /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 00000000..1c943c18 --- /dev/null +++ b/library/Zend/CodeGenerator/Php/Class.php @@ -0,0 +1,513 @@ +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 (!$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; + } + + /** + * 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 (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; + } + + /** + * getProperties() + * + * @return array + */ + public function getProperties() + { + return $this->_properties; + } + + /** + * 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; + } + + /** + * hasProperty() + * + * @param string $propertyName + * @return bool + */ + public function hasProperty($propertyName) + { + return isset($this->_properties[$propertyName]); + } + + /** + * 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->_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; + + $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->_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 00000000..58b378c3 --- /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 00000000..459f616d --- /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 00000000..f6a30565 --- /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 00000000..dd0179cc --- /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 00000000..43f7b4f0 --- /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 00000000..883d8234 --- /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) { + $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 00000000..6d96d48c --- /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 00000000..24474165 --- /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 00000000..6e38cd4d --- /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 00000000..869d4fee --- /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 00000000..c678fa3d --- /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 00000000..ae07ab1a --- /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 00000000..8044c19e --- /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 00000000..d9edfea2 --- /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 00000000..1a26a2b4 --- /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 00000000..0575cb71 --- /dev/null +++ b/library/Zend/Config/Json.php @@ -0,0 +1,240 @@ + $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)) { + $value = str_replace($constant, constant($constant), $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 00000000..689ec43c --- /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 00000000..9a359893 --- /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 00000000..d618ccf4 --- /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 00000000..1bcbddc2 --- /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 00000000..be137522 --- /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 00000000..434d2fb8 --- /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 00000000..4e417847 --- /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 00000000..5f5ba841 --- /dev/null +++ b/library/Zend/Config/Yaml.php @@ -0,0 +1,381 @@ +_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', $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', $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 = rtrim(preg_replace("/#.*$/", "", $m[2])); + // 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 (!self::$_ignoreConstants) { + // test for constants + $value = self::_replaceConstants($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) { + $config[] = substr($line, 2); + } 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; + } + + /** + * 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 00000000..d6035665 --- /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 00000000..cabb4065 --- /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 00000000..a6c17855 --- /dev/null +++ b/library/Zend/Controller/Action.php @@ -0,0 +1,692 @@ +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 + * /.phtml. You may change the script suffix by + * resetting {@link $viewSuffix}. You may omit the controller directory + * prefix by specifying boolean true for $noController. + * + * By default, the rendered contents are appended to the response. You may + * specify the named body content segment to set by specifying a $name. + * + * @see Zend_Controller_Response_Abstract::appendBody() + * @param string|null $action Defaults to action registered in request object + * @param string|null $name Response object named path segment to use; defaults to null + * @param bool $noController Defaults to false; i.e. use controller name as subdir in which to search for view script + * @return void + */ + public function render($action = null, $name = null, $noController = false) + { + if (!$this->getInvokeArg('noViewRenderer') && $this->_helper->hasHelper('viewRenderer')) { + return $this->_helper->viewRenderer->render($action, $name, $noController); + } + + $view = $this->initView(); + $script = $this->getViewScript($action, $noController); + + $this->getResponse()->appendBody( + $view->render($script), + $name + ); + } + + /** + * Render a given view script + * + * Similar to {@link render()}, this method renders a view script. Unlike render(), + * however, it does not autodetermine the view script via {@link getViewScript()}, + * but instead renders the script passed to it. Use this if you know the + * exact view script name and path you wish to use, or if using paths that do not + * conform to the spec defined with getViewScript(). + * + * By default, the rendered contents are appended to the response. You may + * specify the named body content segment to set by specifying a $name. + * + * @param string $script + * @param string $name + * @return void + */ + public function renderScript($script, $name = null) + { + if (!$this->getInvokeArg('noViewRenderer') && $this->_helper->hasHelper('viewRenderer')) { + return $this->_helper->viewRenderer->renderScript($script, $name); + } + + $view = $this->initView(); + $this->getResponse()->appendBody( + $view->render($script), + $name + ); + } + + /** + * Construct view script path + * + * Used by render() to determine the path to the view script. + * + * @param string $action Defaults to action registered in request object + * @param bool $noController Defaults to false; i.e. use controller name as subdir in which to search for view script + * @return string + * @throws Zend_Controller_Exception with bad $action + */ + public function getViewScript($action = null, $noController = null) + { + if (!$this->getInvokeArg('noViewRenderer') && $this->_helper->hasHelper('viewRenderer')) { + $viewRenderer = $this->_helper->getHelper('viewRenderer'); + if (null !== $noController) { + $viewRenderer->setNoController($noController); + } + return $viewRenderer->getViewScript($action); + } + + $request = $this->getRequest(); + if (null === $action) { + $action = $request->getActionName(); + } elseif (!is_string($action)) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Invalid action specifier for view render'); + } + + if (null === $this->_delimiters) { + $dispatcher = Zend_Controller_Front::getInstance()->getDispatcher(); + $wordDelimiters = $dispatcher->getWordDelimiter(); + $pathDelimiters = $dispatcher->getPathDelimiter(); + $this->_delimiters = array_unique(array_merge($wordDelimiters, (array) $pathDelimiters)); + } + + $action = str_replace($this->_delimiters, '-', $action); + $script = $action . '.' . $this->viewSuffix; + + if (!$noController) { + $controller = $request->getControllerName(); + $controller = str_replace($this->_delimiters, '-', $controller); + $script = $controller . DIRECTORY_SEPARATOR . $script; + } + + return $script; + } + + /** + * Return the Request object + * + * @return Zend_Controller_Request_Abstract + */ + public function getRequest() + { + return $this->_request; + } + + /** + * Set the Request object + * + * @param Zend_Controller_Request_Abstract $request + * @return Zend_Controller_Action + */ + public function setRequest(Zend_Controller_Request_Abstract $request) + { + $this->_request = $request; + return $this; + } + + /** + * Return the Response object + * + * @return Zend_Controller_Response_Abstract + */ + public function getResponse() + { + return $this->_response; + } + + /** + * Set the Response object + * + * @param Zend_Controller_Response_Abstract $response + * @return Zend_Controller_Action + */ + public function setResponse(Zend_Controller_Response_Abstract $response) + { + $this->_response = $response; + return $this; + } + + /** + * Set invocation arguments + * + * @param array $args + * @return Zend_Controller_Action + */ + protected function _setInvokeArgs(array $args = array()) + { + $this->_invokeArgs = $args; + return $this; + } + + /** + * Return the array of constructor arguments (minus the Request object) + * + * @return array + */ + public function getInvokeArgs() + { + return $this->_invokeArgs; + } + + /** + * Return a single invocation argument + * + * @param string $key + * @return mixed + */ + public function getInvokeArg($key) + { + if (isset($this->_invokeArgs[$key])) { + return $this->_invokeArgs[$key]; + } + + return null; + } + + /** + * Get a helper by name + * + * @param string $helperName + * @return Zend_Controller_Action_Helper_Abstract + */ + public function getHelper($helperName) + { + return $this->_helper->{$helperName}; + } + + /** + * Get a clone of a helper by name + * + * @param string $helperName + * @return Zend_Controller_Action_Helper_Abstract + */ + public function getHelperCopy($helperName) + { + return clone $this->_helper->{$helperName}; + } + + /** + * Set the front controller instance + * + * @param Zend_Controller_Front $front + * @return Zend_Controller_Action + */ + public function setFrontController(Zend_Controller_Front $front) + { + $this->_frontController = $front; + return $this; + } + + /** + * Retrieve Front Controller + * + * @return Zend_Controller_Front + */ + public function getFrontController() + { + // Used cache version if found + if (null !== $this->_frontController) { + return $this->_frontController; + } + + // Grab singleton instance, if class has been loaded + if (class_exists('Zend_Controller_Front')) { + $this->_frontController = Zend_Controller_Front::getInstance(); + return $this->_frontController; + } + + // Throw exception in all other cases + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Front controller class has not been loaded'); + } + + /** + * Pre-dispatch routines + * + * Called before action method. If using class with + * {@link Zend_Controller_Front}, it may modify the + * {@link $_request Request object} and reset its dispatched flag in order + * to skip processing the current action. + * + * @return void + */ + public function preDispatch() + { + } + + /** + * Post-dispatch routines + * + * Called after action method execution. If using class with + * {@link Zend_Controller_Front}, it may modify the + * {@link $_request Request object} and reset its dispatched flag in order + * to process an additional action. + * + * Common usages for postDispatch() include rendering content in a sitewide + * template, link url correction, setting headers, etc. + * + * @return void + */ + public function postDispatch() + { + } + + /** + * Proxy for undefined methods. Default behavior is to throw an + * exception on undefined methods, however this function can be + * overridden to implement magic (dynamic) actions, or provide run-time + * dispatching. + * + * @param string $methodName + * @param array $args + * @return void + * @throws Zend_Controller_Action_Exception + */ + public function __call($methodName, $args) + { + require_once 'Zend/Controller/Action/Exception.php'; + if ('Action' == substr($methodName, -6)) { + $action = substr($methodName, 0, strlen($methodName) - 6); + throw new Zend_Controller_Action_Exception(sprintf('Action "%s" does not exist and was not trapped in __call()', $action), 404); + } + + throw new Zend_Controller_Action_Exception(sprintf('Method "%s" does not exist and was not trapped in __call()', $methodName), 500); + } + + /** + * Dispatch the requested action + * + * @param string $action Method name of action + * @return void + */ + public function dispatch($action) + { + // Notify helpers of action preDispatch state + $this->_helper->notifyPreDispatch(); + + $this->preDispatch(); + if ($this->getRequest()->isDispatched()) { + if (null === $this->_classMethods) { + $this->_classMethods = get_class_methods($this); + } + + // If pre-dispatch hooks introduced a redirect then stop dispatch + // @see ZF-7496 + if (!($this->getResponse()->isRedirect())) { + // preDispatch() didn't change the action, so we can continue + if ($this->getInvokeArg('useCaseSensitiveActions') || in_array($action, $this->_classMethods)) { + if ($this->getInvokeArg('useCaseSensitiveActions')) { + trigger_error('Using case sensitive actions without word separators is deprecated; please do not rely on this "feature"'); + } + $this->$action(); + } else { + $this->__call($action, array()); + } + } + $this->postDispatch(); + } + + // whats actually important here is that this action controller is + // shutting down, regardless of dispatching; notify the helpers of this + // state + $this->_helper->notifyPostDispatch(); + } + + /** + * Call the action specified in the request object, and return a response + * + * Not used in the Action Controller implementation, but left for usage in + * Page Controller implementations. Dispatches a method based on the + * request. + * + * Returns a Zend_Controller_Response_Abstract object, instantiating one + * prior to execution if none exists in the controller. + * + * {@link preDispatch()} is called prior to the action, + * {@link postDispatch()} is called following it. + * + * @param null|Zend_Controller_Request_Abstract $request Optional request + * object to use + * @param null|Zend_Controller_Response_Abstract $response Optional response + * object to use + * @return Zend_Controller_Response_Abstract + */ + public function run(Zend_Controller_Request_Abstract $request = null, Zend_Controller_Response_Abstract $response = null) + { + if (null !== $request) { + $this->setRequest($request); + } else { + $request = $this->getRequest(); + } + + if (null !== $response) { + $this->setResponse($response); + } + + $action = $request->getActionName(); + if (empty($action)) { + $action = 'index'; + } + $action = $action . 'Action'; + + $request->setDispatched(true); + $this->dispatch($action); + + return $this->getResponse(); + } + + /** + * Gets a parameter from the {@link $_request Request object}. If the + * parameter does not exist, NULL will be returned. + * + * If the parameter does not exist and $default is set, then + * $default will be returned instead of NULL. + * + * @param string $paramName + * @param mixed $default + * @return mixed + */ + protected function _getParam($paramName, $default = null) + { + $value = $this->getRequest()->getParam($paramName); + if ((null === $value || '' === $value) && (null !== $default)) { + $value = $default; + } + + return $value; + } + + /** + * Set a parameter in the {@link $_request Request object}. + * + * @param string $paramName + * @param mixed $value + * @return Zend_Controller_Action + */ + protected function _setParam($paramName, $value) + { + $this->getRequest()->setParam($paramName, $value); + + return $this; + } + + /** + * Determine whether a given parameter exists in the + * {@link $_request Request object}. + * + * @param string $paramName + * @return boolean + */ + protected function _hasParam($paramName) + { + return null !== $this->getRequest()->getParam($paramName); + } + + /** + * Return all parameters in the {@link $_request Request object} + * as an associative array. + * + * @return array + */ + protected function _getAllParams() + { + return $this->getRequest()->getParams(); + } + + + /** + * Forward to another controller/action. + * + * It is important to supply the unformatted names, i.e. "article" + * rather than "ArticleController". The dispatcher will do the + * appropriate formatting when the request is received. + * + * If only an action name is provided, forwards to that action in this + * controller. + * + * If an action and controller are specified, forwards to that action and + * controller in this module. + * + * Specifying an action, controller, and module is the most specific way to + * forward. + * + * A fourth argument, $params, will be used to set the request parameters. + * If either the controller or module are unnecessary for forwarding, + * simply pass null values for them before specifying the parameters. + * + * @param string $action + * @param string $controller + * @param string $module + * @param array $params + * @return void + */ + final protected function _forward($action, $controller = null, $module = null, array $params = null) + { + $request = $this->getRequest(); + + if (null !== $params) { + $request->setParams($params); + } + + if (null !== $controller) { + $request->setControllerName($controller); + + // Module should only be reset if controller has been specified + if (null !== $module) { + $request->setModuleName($module); + } + } + + $request->setActionName($action) + ->setDispatched(false); + } + + /** + * Redirect to another URL + * + * Proxies to {@link Zend_Controller_Action_Helper_Redirector::gotoUrl()}. + * + * @param string $url + * @param array $options Options to be used when redirecting + * @return void + */ + protected function _redirect($url, array $options = array()) + { + $this->_helper->redirector->gotoUrl($url, $options); + } +} diff --git a/library/Zend/Controller/Action/Exception.php b/library/Zend/Controller/Action/Exception.php new file mode 100644 index 00000000..c444b23f --- /dev/null +++ b/library/Zend/Controller/Action/Exception.php @@ -0,0 +1,38 @@ +_actionController = $actionController; + return $this; + } + + /** + * Retrieve current action controller + * + * @return Zend_Controller_Action + */ + public function getActionController() + { + return $this->_actionController; + } + + /** + * Retrieve front controller instance + * + * @return Zend_Controller_Front + */ + public function getFrontController() + { + return Zend_Controller_Front::getInstance(); + } + + /** + * Hook into action controller initialization + * + * @return void + */ + public function init() + { + } + + /** + * Hook into action controller preDispatch() workflow + * + * @return void + */ + public function preDispatch() + { + } + + /** + * Hook into action controller postDispatch() workflow + * + * @return void + */ + public function postDispatch() + { + } + + /** + * getRequest() - + * + * @return Zend_Controller_Request_Abstract $request + */ + public function getRequest() + { + $controller = $this->getActionController(); + if (null === $controller) { + $controller = $this->getFrontController(); + } + + return $controller->getRequest(); + } + + /** + * getResponse() - + * + * @return Zend_Controller_Response_Abstract $response + */ + public function getResponse() + { + $controller = $this->getActionController(); + if (null === $controller) { + $controller = $this->getFrontController(); + } + + return $controller->getResponse(); + } + + /** + * getName() + * + * @return string + */ + public function getName() + { + $fullClassName = get_class($this); + if (strpos($fullClassName, '_') !== false) { + $helperName = strrchr($fullClassName, '_'); + return ltrim($helperName, '_'); + } elseif (strpos($fullClassName, '\\') !== false) { + $helperName = strrchr($fullClassName, '\\'); + return ltrim($helperName, '\\'); + } else { + return $fullClassName; + } + } +} diff --git a/library/Zend/Controller/Action/Helper/ActionStack.php b/library/Zend/Controller/Action/Helper/ActionStack.php new file mode 100644 index 00000000..eceb6f27 --- /dev/null +++ b/library/Zend/Controller/Action/Helper/ActionStack.php @@ -0,0 +1,138 @@ +hasPlugin('Zend_Controller_Plugin_ActionStack')) { + /** + * @see Zend_Controller_Plugin_ActionStack + */ + require_once 'Zend/Controller/Plugin/ActionStack.php'; + $this->_actionStack = new Zend_Controller_Plugin_ActionStack(); + $front->registerPlugin($this->_actionStack, 97); + } else { + $this->_actionStack = $front->getPlugin('Zend_Controller_Plugin_ActionStack'); + } + } + + /** + * Push onto the stack + * + * @param Zend_Controller_Request_Abstract $next + * @return Zend_Controller_Action_Helper_ActionStack Provides a fluent interface + */ + public function pushStack(Zend_Controller_Request_Abstract $next) + { + $this->_actionStack->pushStack($next); + return $this; + } + + /** + * Push a new action onto the stack + * + * @param string $action + * @param string $controller + * @param string $module + * @param array $params + * @throws Zend_Controller_Action_Exception + * @return Zend_Controller_Action_Helper_ActionStack + */ + public function actionToStack($action, $controller = null, $module = null, array $params = array()) + { + if ($action instanceof Zend_Controller_Request_Abstract) { + return $this->pushStack($action); + } elseif (!is_string($action)) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('ActionStack requires either a request object or minimally a string action'); + } + + $request = $this->getRequest(); + + if ($request instanceof Zend_Controller_Request_Abstract === false){ + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('Request object not set yet'); + } + + $controller = (null === $controller) ? $request->getControllerName() : $controller; + $module = (null === $module) ? $request->getModuleName() : $module; + + /** + * @see Zend_Controller_Request_Simple + */ + require_once 'Zend/Controller/Request/Simple.php'; + $newRequest = new Zend_Controller_Request_Simple($action, $controller, $module, $params); + + return $this->pushStack($newRequest); + } + + /** + * Perform helper when called as $this->_helper->actionStack() from an action controller + * + * Proxies to {@link simple()} + * + * @param string $action + * @param string $controller + * @param string $module + * @param array $params + * @return boolean + */ + public function direct($action, $controller = null, $module = null, array $params = array()) + { + return $this->actionToStack($action, $controller, $module, $params); + } +} diff --git a/library/Zend/Controller/Action/Helper/AjaxContext.php b/library/Zend/Controller/Action/Helper/AjaxContext.php new file mode 100644 index 00000000..33606d57 --- /dev/null +++ b/library/Zend/Controller/Action/Helper/AjaxContext.php @@ -0,0 +1,80 @@ +addContext('html', array('suffix' => 'ajax')); + } + + /** + * Initialize AJAX context switching + * + * Checks for XHR requests; if detected, attempts to perform context switch. + * + * @param string $format + * @return void + */ + public function initContext($format = null) + { + $this->_currentContext = null; + + $request = $this->getRequest(); + if (!method_exists($request, 'isXmlHttpRequest') || + !$this->getRequest()->isXmlHttpRequest()) + { + return; + } + + return parent::initContext($format); + } +} diff --git a/library/Zend/Controller/Action/Helper/AutoComplete/Abstract.php b/library/Zend/Controller/Action/Helper/AutoComplete/Abstract.php new file mode 100644 index 00000000..09510f8b --- /dev/null +++ b/library/Zend/Controller/Action/Helper/AutoComplete/Abstract.php @@ -0,0 +1,149 @@ +disableLayout(); + } + + Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer')->setNoRender(true); + + return $this; + } + + /** + * Encode data to JSON + * + * @param mixed $data + * @param bool $keepLayouts + * @throws Zend_Controller_Action_Exception + * @return string + */ + public function encodeJson($data, $keepLayouts = false) + { + if ($this->validateData($data)) { + return Zend_Controller_Action_HelperBroker::getStaticHelper('Json')->encodeJson($data, $keepLayouts); + } + + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('Invalid data passed for autocompletion'); + } + + /** + * Send autocompletion data + * + * Calls prepareAutoCompletion, populates response body with this + * information, and sends response. + * + * @param mixed $data + * @param bool $keepLayouts + * @return string|void + */ + public function sendAutoCompletion($data, $keepLayouts = false) + { + $data = $this->prepareAutoCompletion($data, $keepLayouts); + + $response = $this->getResponse(); + $response->setBody($data); + + if (!$this->suppressExit) { + $response->sendResponse(); + exit; + } + + return $data; + } + + /** + * Strategy pattern: allow calling helper as broker method + * + * Prepares autocompletion data and, if $sendNow is true, immediately sends + * response. + * + * @param mixed $data + * @param bool $sendNow + * @param bool $keepLayouts + * @return string|void + */ + public function direct($data, $sendNow = true, $keepLayouts = false) + { + if ($sendNow) { + return $this->sendAutoCompletion($data, $keepLayouts); + } + + return $this->prepareAutoCompletion($data, $keepLayouts); + } +} diff --git a/library/Zend/Controller/Action/Helper/AutoCompleteDojo.php b/library/Zend/Controller/Action/Helper/AutoCompleteDojo.php new file mode 100644 index 00000000..bda6ed97 --- /dev/null +++ b/library/Zend/Controller/Action/Helper/AutoCompleteDojo.php @@ -0,0 +1,87 @@ + $value) { + $items[] = array('label' => $value, 'name' => $value); + } + $data = new Zend_Dojo_Data('name', $items); + } + + if (!$keepLayouts) { + require_once 'Zend/Controller/Action/HelperBroker.php'; + Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer')->setNoRender(true); + + require_once 'Zend/Layout.php'; + $layout = Zend_Layout::getMvcInstance(); + if ($layout instanceof Zend_Layout) { + $layout->disableLayout(); + } + } + + $response = Zend_Controller_Front::getInstance()->getResponse(); + $response->setHeader('Content-Type', 'application/json'); + + return $data->toJson(); + } +} diff --git a/library/Zend/Controller/Action/Helper/AutoCompleteScriptaculous.php b/library/Zend/Controller/Action/Helper/AutoCompleteScriptaculous.php new file mode 100644 index 00000000..1fb78bd8 --- /dev/null +++ b/library/Zend/Controller/Action/Helper/AutoCompleteScriptaculous.php @@ -0,0 +1,82 @@ +validateData($data)) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('Invalid data passed for autocompletion'); + } + + $data = (array) $data; + $data = '
  • ' . implode('
  • ', $data) . '
'; + + if (!$keepLayouts) { + $this->disableLayouts(); + } + + return $data; + } +} diff --git a/library/Zend/Controller/Action/Helper/Cache.php b/library/Zend/Controller/Action/Helper/Cache.php new file mode 100644 index 00000000..e0a5d473 --- /dev/null +++ b/library/Zend/Controller/Action/Helper/Cache.php @@ -0,0 +1,279 @@ +getRequest()->getControllerName(); + $actions = array_unique($actions); + if (!isset($this->_caching[$controller])) { + $this->_caching[$controller] = array(); + } + if (!empty($tags)) { + $tags = array_unique($tags); + if (!isset($this->_tags[$controller])) { + $this->_tags[$controller] = array(); + } + } + foreach ($actions as $action) { + $this->_caching[$controller][] = $action; + if (!empty($tags)) { + $this->_tags[$controller][$action] = array(); + foreach ($tags as $tag) { + $this->_tags[$controller][$action][] = $tag; + } + } + } + if ($extension) { + if (!isset($this->_extensions[$controller])) { + $this->_extensions[$controller] = array(); + } + foreach ($actions as $action) { + $this->_extensions[$controller][$action] = $extension; + } + } + } + + /** + * Remove a specific page cache static file based on its + * relative URL from the application's public directory. + * The file extension is not required here; usually matches + * the original REQUEST_URI that was cached. + * + * @param string $relativeUrl + * @param bool $recursive + * @return mixed + */ + public function removePage($relativeUrl, $recursive = false) + { + $cache = $this->getCache(Zend_Cache_Manager::PAGECACHE); + if ($recursive) { + $backend = $cache->getBackend(); + if (($backend instanceof Zend_Cache_Backend) + && method_exists($backend, 'removeRecursively') + ) { + return $backend->removeRecursively($relativeUrl); + } + } + + return $cache->remove($relativeUrl); + } + + /** + * Remove a specific page cache static file based on its + * relative URL from the application's public directory. + * The file extension is not required here; usually matches + * the original REQUEST_URI that was cached. + * + * @param array $tags + * @return mixed + */ + public function removePagesTagged(array $tags) + { + return $this->getCache(Zend_Cache_Manager::PAGECACHE) + ->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, $tags); + } + + /** + * Commence page caching for any cacheable actions + * + * @return void + */ + public function preDispatch() + { + $controller = $this->getRequest()->getControllerName(); + $action = $this->getRequest()->getActionName(); + $stats = ob_get_status(true); + foreach ($stats as $status) { + if ($status['name'] == 'Zend_Cache_Frontend_Page::_flush' + || $status['name'] == 'Zend_Cache_Frontend_Capture::_flush') { + $obStarted = true; + } + } + if (!isset($obStarted) && isset($this->_caching[$controller]) && + in_array($action, $this->_caching[$controller])) { + $reqUri = $this->getRequest()->getRequestUri(); + $tags = array(); + if (isset($this->_tags[$controller][$action]) + && !empty($this->_tags[$controller][$action])) { + $tags = array_unique($this->_tags[$controller][$action]); + } + $extension = null; + if (isset($this->_extensions[$controller][$action])) { + $extension = $this->_extensions[$controller][$action]; + } + $this->getCache(Zend_Cache_Manager::PAGECACHE) + ->start($this->_encodeCacheId($reqUri), $tags, $extension); + } + } + + /** + * Encode a Cache ID as hexadecimal. This is a workaround because Backend ID validation + * is trapped in the Frontend classes. Will try to get this reversed for ZF 2.0 + * because it's a major annoyance to have IDs so restricted! + * + * @return string + * @param string $requestUri + */ + protected function _encodeCacheId($requestUri) + { + return bin2hex($requestUri); + } + + /** + * Set an instance of the Cache Manager for this helper + * + * @param Zend_Cache_Manager $manager + * @return void + */ + public function setManager(Zend_Cache_Manager $manager) + { + $this->_manager = $manager; + return $this; + } + + /** + * Get the Cache Manager instance or instantiate the object if not + * exists. Attempts to load from bootstrap if available. + * + * @return Zend_Cache_Manager + */ + public function getManager() + { + if ($this->_manager !== null) { + return $this->_manager; + } + $front = Zend_Controller_Front::getInstance(); + if ($front->getParam('bootstrap') + && $front->getParam('bootstrap')->getResource('CacheManager')) { + return $front->getParam('bootstrap') + ->getResource('CacheManager'); + } + $this->_manager = new Zend_Cache_Manager; + return $this->_manager; + } + + /** + * Return a list of actions for the current Controller marked for + * caching + * + * @return array + */ + public function getCacheableActions() + { + return $this->_caching; + } + + /** + * Return a list of tags set for all cacheable actions + * + * @return array + */ + public function getCacheableTags() + { + return $this->_tags; + } + + /** + * Proxy non-matched methods back to Zend_Cache_Manager where + * appropriate + * + * @param string $method + * @param array $args + * @return mixed + */ + public function __call($method, $args) + { + if (method_exists($this->getManager(), $method)) { + return call_user_func_array( + array($this->getManager(), $method), $args + ); + } + throw new Zend_Controller_Action_Exception('Method does not exist:' + . $method); + } + +} diff --git a/library/Zend/Controller/Action/Helper/ContextSwitch.php b/library/Zend/Controller/Action/Helper/ContextSwitch.php new file mode 100644 index 00000000..f618b29f --- /dev/null +++ b/library/Zend/Controller/Action/Helper/ContextSwitch.php @@ -0,0 +1,1394 @@ +setConfig($options); + } elseif (is_array($options)) { + $this->setOptions($options); + } + + if (empty($this->_contexts)) { + $this->addContexts(array( + 'json' => array( + 'suffix' => 'json', + 'headers' => array('Content-Type' => 'application/json'), + 'callbacks' => array( + 'init' => 'initJsonContext', + 'post' => 'postJsonContext' + ) + ), + 'xml' => array( + 'suffix' => 'xml', + 'headers' => array('Content-Type' => 'application/xml'), + ) + )); + } + + $this->init(); + } + + /** + * Initialize at start of action controller + * + * Reset the view script suffix to the original state, or store the + * original state. + * + * @return void + */ + public function init() + { + if (null === $this->_viewSuffixOrig) { + $this->_viewSuffixOrig = $this->_getViewRenderer()->getViewSuffix(); + } else { + $this->_getViewRenderer()->setViewSuffix($this->_viewSuffixOrig); + } + } + + /** + * Configure object from array of options + * + * @param array $options + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setOptions(array $options) + { + if (isset($options['contexts'])) { + $this->setContexts($options['contexts']); + unset($options['contexts']); + } + + foreach ($options as $key => $value) { + $method = 'set' . ucfirst($key); + if (in_array($method, $this->_unconfigurable)) { + continue; + } + + if (in_array($method, $this->_specialConfig)) { + $method = '_' . $method; + } + + if (method_exists($this, $method)) { + $this->$method($value); + } + } + return $this; + } + + /** + * Set object state from config object + * + * @param Zend_Config $config + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setConfig(Zend_Config $config) + { + return $this->setOptions($config->toArray()); + } + + /** + * Strategy pattern: return object + * + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function direct() + { + return $this; + } + + /** + * Initialize context detection and switching + * + * @param mixed $format + * @throws Zend_Controller_Action_Exception + * @return void + */ + public function initContext($format = null) + { + $this->_currentContext = null; + + $controller = $this->getActionController(); + $request = $this->getRequest(); + $action = $request->getActionName(); + + // Return if no context switching enabled, or no context switching + // enabled for this action + $contexts = $this->getActionContexts($action); + if (empty($contexts)) { + return; + } + + // Return if no context parameter provided + if (!$context = $request->getParam($this->getContextParam())) { + if ($format === null) { + return; + } + $context = $format; + $format = null; + } + + // Check if context allowed by action controller + if (!$this->hasActionContext($action, $context)) { + return; + } + + // Return if invalid context parameter provided and no format or invalid + // format provided + if (!$this->hasContext($context)) { + if (empty($format) || !$this->hasContext($format)) { + + return; + } + } + + // Use provided format if passed + if (!empty($format) && $this->hasContext($format)) { + $context = $format; + } + + $suffix = $this->getSuffix($context); + + $this->_getViewRenderer()->setViewSuffix($suffix); + + $headers = $this->getHeaders($context); + if (!empty($headers)) { + $response = $this->getResponse(); + foreach ($headers as $header => $content) { + $response->setHeader($header, $content); + } + } + + if ($this->getAutoDisableLayout()) { + /** + * @see Zend_Layout + */ + require_once 'Zend/Layout.php'; + $layout = Zend_Layout::getMvcInstance(); + if (null !== $layout) { + $layout->disableLayout(); + } + } + + if (null !== ($callback = $this->getCallback($context, self::TRIGGER_INIT))) { + if (is_string($callback) && method_exists($this, $callback)) { + $this->$callback(); + } elseif (is_string($callback) && function_exists($callback)) { + $callback(); + } elseif (is_array($callback)) { + call_user_func($callback); + } else { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf('Invalid context callback registered for context "%s"', $context)); + } + } + + $this->_currentContext = $context; + } + + /** + * JSON context extra initialization + * + * Turns off viewRenderer auto-rendering + * + * @return void + */ + public function initJsonContext() + { + if (!$this->getAutoJsonSerialization()) { + return; + } + + $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer'); + $view = $viewRenderer->view; + if ($view instanceof Zend_View_Interface) { + $viewRenderer->setNoRender(true); + } + } + + /** + * Should JSON contexts auto-serialize? + * + * @param boolean $flag + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setAutoJsonSerialization($flag) + { + $this->_autoJsonSerialization = (bool) $flag; + return $this; + } + + /** + * Get JSON context auto-serialization flag + * + * @return boolean + */ + public function getAutoJsonSerialization() + { + return $this->_autoJsonSerialization; + } + + /** + * Set suffix from array + * + * @param array $spec + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + protected function _setSuffix(array $spec) + { + foreach ($spec as $context => $suffixInfo) { + if (!is_string($context)) { + $context = null; + } + + if (is_string($suffixInfo)) { + $this->setSuffix($context, $suffixInfo); + continue; + } elseif (is_array($suffixInfo)) { + if (isset($suffixInfo['suffix'])) { + $suffix = $suffixInfo['suffix']; + $prependViewRendererSuffix = true; + + if ((null === $context) && isset($suffixInfo['context'])) { + $context = $suffixInfo['context']; + } + + if (isset($suffixInfo['prependViewRendererSuffix'])) { + $prependViewRendererSuffix = $suffixInfo['prependViewRendererSuffix']; + } + + $this->setSuffix($context, $suffix, $prependViewRendererSuffix); + continue; + } + + $count = count($suffixInfo); + switch (true) { + case (($count < 2) && (null === $context)): + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('Invalid suffix information provided in config'); + case ($count < 2): + $suffix = array_shift($suffixInfo); + $this->setSuffix($context, $suffix); + break; + case (($count < 3) && (null === $context)): + $context = array_shift($suffixInfo); + $suffix = array_shift($suffixInfo); + $this->setSuffix($context, $suffix); + break; + case (($count == 3) && (null === $context)): + $context = array_shift($suffixInfo); + $suffix = array_shift($suffixInfo); + $prependViewRendererSuffix = array_shift($suffixInfo); + $this->setSuffix($context, $suffix, $prependViewRendererSuffix); + break; + case ($count >= 2): + $suffix = array_shift($suffixInfo); + $prependViewRendererSuffix = array_shift($suffixInfo); + $this->setSuffix($context, $suffix, $prependViewRendererSuffix); + break; + } + } + } + return $this; + } + + /** + * Customize view script suffix to use when switching context. + * + * Passing an empty suffix value to the setters disables the view script + * suffix change. + * + * @param string $context Context type for which to set suffix + * @param string $suffix Suffix to use + * @param boolean $prependViewRendererSuffix Whether or not to prepend the new suffix to the viewrenderer suffix + * @throws Zend_Controller_Action_Exception + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setSuffix($context, $suffix, $prependViewRendererSuffix = true) + { + if (!isset($this->_contexts[$context])) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf('Cannot set suffix; invalid context type "%s"', $context)); + } + + if (empty($suffix)) { + $suffix = ''; + } + + if (is_array($suffix)) { + if (isset($suffix['prependViewRendererSuffix'])) { + $prependViewRendererSuffix = $suffix['prependViewRendererSuffix']; + } + if (isset($suffix['suffix'])) { + $suffix = $suffix['suffix']; + } else { + $suffix = ''; + } + } + + $suffix = (string) $suffix; + + if ($prependViewRendererSuffix) { + if (empty($suffix)) { + $suffix = $this->_getViewRenderer()->getViewSuffix(); + } else { + $suffix .= '.' . $this->_getViewRenderer()->getViewSuffix(); + } + } + + $this->_contexts[$context]['suffix'] = $suffix; + return $this; + } + + /** + * Retrieve suffix for given context type + * + * @param string $type Context type + * @throws Zend_Controller_Action_Exception + * @return string + */ + public function getSuffix($type) + { + if (!isset($this->_contexts[$type])) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf('Cannot retrieve suffix; invalid context type "%s"', $type)); + } + + return $this->_contexts[$type]['suffix']; + } + + /** + * Does the given context exist? + * + * @param string $context + * @param boolean $throwException + * @throws Zend_Controller_Action_Exception if context does not exist and throwException is true + * @return bool + */ + public function hasContext($context, $throwException = false) + { + if (is_string($context)) { + if (isset($this->_contexts[$context])) { + return true; + } + } elseif (is_array($context)) { + $error = false; + foreach ($context as $test) { + if (!isset($this->_contexts[$test])) { + $error = (string) $test; + break; + } + } + if (false === $error) { + return true; + } + $context = $error; + } elseif (true === $context) { + return true; + } + + if ($throwException) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf('Context "%s" does not exist', $context)); + } + + return false; + } + + /** + * Add header to context + * + * @param string $context + * @param string $header + * @param string $content + * @throws Zend_Controller_Action_Exception + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function addHeader($context, $header, $content) + { + $context = (string) $context; + $this->hasContext($context, true); + + $header = (string) $header; + $content = (string) $content; + + if (isset($this->_contexts[$context]['headers'][$header])) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf('Cannot add "%s" header to context "%s": already exists', $header, $context)); + } + + $this->_contexts[$context]['headers'][$header] = $content; + return $this; + } + + /** + * Customize response header to use when switching context + * + * Passing an empty header value to the setters disables the response + * header. + * + * @param string $type Context type for which to set suffix + * @param string $header Header to set + * @param string $content Header content + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setHeader($context, $header, $content) + { + $this->hasContext($context, true); + $context = (string) $context; + $header = (string) $header; + $content = (string) $content; + + $this->_contexts[$context]['headers'][$header] = $content; + return $this; + } + + /** + * Add multiple headers at once for a given context + * + * @param string $context + * @param array $headers + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function addHeaders($context, array $headers) + { + foreach ($headers as $header => $content) { + $this->addHeader($context, $header, $content); + } + + return $this; + } + + /** + * Set headers from context => headers pairs + * + * @param array $options + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + protected function _setHeaders(array $options) + { + foreach ($options as $context => $headers) { + if (!is_array($headers)) { + continue; + } + $this->setHeaders($context, $headers); + } + + return $this; + } + + /** + * Set multiple headers at once for a given context + * + * @param string $context + * @param array $headers + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setHeaders($context, array $headers) + { + $this->clearHeaders($context); + foreach ($headers as $header => $content) { + $this->setHeader($context, $header, $content); + } + + return $this; + } + + /** + * Retrieve context header + * + * Returns the value of a given header for a given context type + * + * @param string $context + * @param string $header + * @return string|null + */ + public function getHeader($context, $header) + { + $this->hasContext($context, true); + $context = (string) $context; + $header = (string) $header; + if (isset($this->_contexts[$context]['headers'][$header])) { + return $this->_contexts[$context]['headers'][$header]; + } + + return null; + } + + /** + * Retrieve context headers + * + * Returns all headers for a context as key/value pairs + * + * @param string $context + * @return array + */ + public function getHeaders($context) + { + $this->hasContext($context, true); + $context = (string) $context; + return $this->_contexts[$context]['headers']; + } + + /** + * Remove a single header from a context + * + * @param string $context + * @param string $header + * @return boolean + */ + public function removeHeader($context, $header) + { + $this->hasContext($context, true); + $context = (string) $context; + $header = (string) $header; + if (isset($this->_contexts[$context]['headers'][$header])) { + unset($this->_contexts[$context]['headers'][$header]); + return true; + } + + return false; + } + + /** + * Clear all headers for a given context + * + * @param string $context + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function clearHeaders($context) + { + $this->hasContext($context, true); + $context = (string) $context; + $this->_contexts[$context]['headers'] = array(); + return $this; + } + + /** + * Validate trigger and return in normalized form + * + * @param string $trigger + * @throws Zend_Controller_Action_Exception + * @return string + */ + protected function _validateTrigger($trigger) + { + $trigger = strtoupper($trigger); + if ('TRIGGER_' !== substr($trigger, 0, 8)) { + $trigger = 'TRIGGER_' . $trigger; + } + + if (!in_array($trigger, array(self::TRIGGER_INIT, self::TRIGGER_POST))) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf('Invalid trigger "%s"', $trigger)); + } + + return $trigger; + } + + /** + * Set a callback for a given context and trigger + * + * @param string $context + * @param string $trigger + * @param string|array $callback + * @throws Zend_Controller_Action_Exception + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setCallback($context, $trigger, $callback) + { + $this->hasContext($context, true); + $trigger = $this->_validateTrigger($trigger); + + if (!is_string($callback)) { + if (!is_array($callback) || (2 != count($callback))) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('Invalid callback specified'); + } + } + + $this->_contexts[$context]['callbacks'][$trigger] = $callback; + return $this; + } + + /** + * Set callbacks from array of context => callbacks pairs + * + * @param array $options + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + protected function _setCallbacks(array $options) + { + foreach ($options as $context => $callbacks) { + if (!is_array($callbacks)) { + continue; + } + + $this->setCallbacks($context, $callbacks); + } + return $this; + } + + /** + * Set callbacks for a given context + * + * Callbacks should be in trigger/callback pairs. + * + * @param string $context + * @param array $callbacks + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setCallbacks($context, array $callbacks) + { + $this->hasContext($context, true); + $context = (string) $context; + if (!isset($this->_contexts[$context]['callbacks'])) { + $this->_contexts[$context]['callbacks'] = array(); + } + + foreach ($callbacks as $trigger => $callback) { + $this->setCallback($context, $trigger, $callback); + } + return $this; + } + + /** + * Get a single callback for a given context and trigger + * + * @param string $context + * @param string $trigger + * @return string|array|null + */ + public function getCallback($context, $trigger) + { + $this->hasContext($context, true); + $trigger = $this->_validateTrigger($trigger); + if (isset($this->_contexts[$context]['callbacks'][$trigger])) { + return $this->_contexts[$context]['callbacks'][$trigger]; + } + + return null; + } + + /** + * Get all callbacks for a given context + * + * @param string $context + * @return array + */ + public function getCallbacks($context) + { + $this->hasContext($context, true); + return $this->_contexts[$context]['callbacks']; + } + + /** + * Clear a callback for a given context and trigger + * + * @param string $context + * @param string $trigger + * @return boolean + */ + public function removeCallback($context, $trigger) + { + $this->hasContext($context, true); + $trigger = $this->_validateTrigger($trigger); + if (isset($this->_contexts[$context]['callbacks'][$trigger])) { + unset($this->_contexts[$context]['callbacks'][$trigger]); + return true; + } + + return false; + } + + /** + * Clear all callbacks for a given context + * + * @param string $context + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function clearCallbacks($context) + { + $this->hasContext($context, true); + $this->_contexts[$context]['callbacks'] = array(); + return $this; + } + + /** + * Set name of parameter to use when determining context format + * + * @param string $name + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setContextParam($name) + { + $this->_contextParam = (string) $name; + return $this; + } + + /** + * Return context format request parameter name + * + * @return string + */ + public function getContextParam() + { + return $this->_contextParam; + } + + /** + * Indicate default context to use when no context format provided + * + * @param string $type + * @throws Zend_Controller_Action_Exception + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setDefaultContext($type) + { + if (!isset($this->_contexts[$type])) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf('Cannot set default context; invalid context type "%s"', $type)); + } + + $this->_defaultContext = $type; + return $this; + } + + /** + * Return default context + * + * @return string + */ + public function getDefaultContext() + { + return $this->_defaultContext; + } + + /** + * Set flag indicating if layout should be disabled + * + * @param boolean $flag + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setAutoDisableLayout($flag) + { + $this->_disableLayout = ($flag) ? true : false; + return $this; + } + + /** + * Retrieve auto layout disable flag + * + * @return boolean + */ + public function getAutoDisableLayout() + { + return $this->_disableLayout; + } + + /** + * Add new context + * + * @param string $context Context type + * @param array $spec Context specification + * @throws Zend_Controller_Action_Exception + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function addContext($context, array $spec) + { + if ($this->hasContext($context)) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf('Cannot add context "%s"; already exists', $context)); + } + $context = (string) $context; + + $this->_contexts[$context] = array(); + + $this->setSuffix($context, (isset($spec['suffix']) ? $spec['suffix'] : '')) + ->setHeaders($context, (isset($spec['headers']) ? $spec['headers'] : array())) + ->setCallbacks($context, (isset($spec['callbacks']) ? $spec['callbacks'] : array())); + return $this; + } + + /** + * Overwrite existing context + * + * @param string $context Context type + * @param array $spec Context specification + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setContext($context, array $spec) + { + $this->removeContext($context); + return $this->addContext($context, $spec); + } + + /** + * Add multiple contexts + * + * @param array $contexts + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function addContexts(array $contexts) + { + foreach ($contexts as $context => $spec) { + $this->addContext($context, $spec); + } + return $this; + } + + /** + * Set multiple contexts, after first removing all + * + * @param array $contexts + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setContexts(array $contexts) + { + $this->clearContexts(); + foreach ($contexts as $context => $spec) { + $this->addContext($context, $spec); + } + return $this; + } + + /** + * Retrieve context specification + * + * @param string $context + * @return array|null + */ + public function getContext($context) + { + if ($this->hasContext($context)) { + return $this->_contexts[(string) $context]; + } + return null; + } + + /** + * Retrieve context definitions + * + * @return array + */ + public function getContexts() + { + return $this->_contexts; + } + + /** + * Remove a context + * + * @param string $context + * @return boolean + */ + public function removeContext($context) + { + if ($this->hasContext($context)) { + unset($this->_contexts[(string) $context]); + return true; + } + return false; + } + + /** + * Remove all contexts + * + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function clearContexts() + { + $this->_contexts = array(); + return $this; + } + + /** + * Return current context, if any + * + * @return null|string + */ + public function getCurrentContext() + { + return $this->_currentContext; + } + + /** + * Post dispatch processing + * + * Execute postDispatch callback for current context, if available + * + * @throws Zend_Controller_Action_Exception + * @return void + */ + public function postDispatch() + { + $context = $this->getCurrentContext(); + if (null !== $context) { + if (null !== ($callback = $this->getCallback($context, self::TRIGGER_POST))) { + if (is_string($callback) && method_exists($this, $callback)) { + $this->$callback(); + } elseif (is_string($callback) && function_exists($callback)) { + $callback(); + } elseif (is_array($callback)) { + call_user_func($callback); + } else { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf('Invalid postDispatch context callback registered for context "%s"', $context)); + } + } + } + } + + /** + * JSON post processing + * + * JSON serialize view variables to response body + * + * @return void + */ + public function postJsonContext() + { + if (!$this->getAutoJsonSerialization()) { + return; + } + + $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer'); + $view = $viewRenderer->view; + if ($view instanceof Zend_View_Interface) { + /** + * @see Zend_Json + */ + if(method_exists($view, 'getVars')) { + require_once 'Zend/Json.php'; + $vars = Zend_Json::encode($view->getVars()); + $this->getResponse()->setBody($vars); + } else { + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('View does not implement the getVars() method needed to encode the view into JSON'); + } + } + } + + /** + * Add one or more contexts to an action + * + * @param string $action + * @param string|array $context + * @return Zend_Controller_Action_Helper_ContextSwitch|void Provides a fluent interface + */ + public function addActionContext($action, $context) + { + $this->hasContext($context, true); + $controller = $this->getActionController(); + if (null === $controller) { + return; + } + $action = (string) $action; + $contextKey = $this->_contextKey; + + if (!isset($controller->$contextKey)) { + $controller->$contextKey = array(); + } + + if (true === $context) { + $contexts = $this->getContexts(); + $controller->{$contextKey}[$action] = array_keys($contexts); + return $this; + } + + $context = (array) $context; + if (!isset($controller->{$contextKey}[$action])) { + $controller->{$contextKey}[$action] = $context; + } else { + $controller->{$contextKey}[$action] = array_merge( + $controller->{$contextKey}[$action], + $context + ); + } + + return $this; + } + + /** + * Set a context as available for a given controller action + * + * @param string $action + * @param string|array $context + * @return Zend_Controller_Action_Helper_ContextSwitch|void Provides a fluent interface + */ + public function setActionContext($action, $context) + { + $this->hasContext($context, true); + $controller = $this->getActionController(); + if (null === $controller) { + return; + } + $action = (string) $action; + $contextKey = $this->_contextKey; + + if (!isset($controller->$contextKey)) { + $controller->$contextKey = array(); + } + + if (true === $context) { + $contexts = $this->getContexts(); + $controller->{$contextKey}[$action] = array_keys($contexts); + } else { + $controller->{$contextKey}[$action] = (array) $context; + } + + return $this; + } + + /** + * Add multiple action/context pairs at once + * + * @param array $contexts + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function addActionContexts(array $contexts) + { + foreach ($contexts as $action => $context) { + $this->addActionContext($action, $context); + } + return $this; + } + + /** + * Overwrite and set multiple action contexts at once + * + * @param array $contexts + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function setActionContexts(array $contexts) + { + foreach ($contexts as $action => $context) { + $this->setActionContext($action, $context); + } + return $this; + } + + /** + * Does a particular controller action have the given context(s)? + * + * @param string $action + * @param string|array $context + * @throws Zend_Controller_Action_Exception + * @return boolean + */ + public function hasActionContext($action, $context) + { + $this->hasContext($context, true); + $controller = $this->getActionController(); + if (null === $controller) { + return false; + } + $action = (string) $action; + $contextKey = $this->_contextKey; + + if (!isset($controller->{$contextKey})) { + return false; + } + + $allContexts = $controller->{$contextKey}; + + if (!is_array($allContexts)) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception("Invalid contexts found for controller"); + } + + if (!isset($allContexts[$action])) { + return false; + } + + if (true === $allContexts[$action]) { + return true; + } + + $contexts = $allContexts[$action]; + + if (!is_array($contexts)) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf("Invalid contexts found for action '%s'", $action)); + } + + if (is_string($context) && in_array($context, $contexts)) { + return true; + } elseif (is_array($context)) { + $found = true; + foreach ($context as $test) { + if (!in_array($test, $contexts)) { + $found = false; + break; + } + } + return $found; + } + + return false; + } + + /** + * Get contexts for a given action or all actions in the controller + * + * @param string $action + * @return array + */ + public function getActionContexts($action = null) + { + $controller = $this->getActionController(); + if (null === $controller) { + return array(); + } + $action = (string) $action; + $contextKey = $this->_contextKey; + + if (!isset($controller->$contextKey)) { + return array(); + } + + if (null !== $action) { + if (isset($controller->{$contextKey}[$action])) { + return $controller->{$contextKey}[$action]; + } else { + return array(); + } + } + + return $controller->$contextKey; + } + + /** + * Remove one or more contexts for a given controller action + * + * @param string $action + * @param string|array $context + * @return boolean + */ + public function removeActionContext($action, $context) + { + if ($this->hasActionContext($action, $context)) { + $controller = $this->getActionController(); + $contextKey = $this->_contextKey; + $action = (string) $action; + $contexts = $controller->$contextKey; + $actionContexts = $contexts[$action]; + $contexts = (array) $context; + foreach ($contexts as $context) { + $index = array_search($context, $actionContexts); + if (false !== $index) { + unset($controller->{$contextKey}[$action][$index]); + } + } + return true; + } + return false; + } + + /** + * Clear all contexts for a given controller action or all actions + * + * @param string $action + * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface + */ + public function clearActionContexts($action = null) + { + $controller = $this->getActionController(); + $contextKey = $this->_contextKey; + + if (!isset($controller->$contextKey) || empty($controller->$contextKey)) { + return $this; + } + + if (null === $action) { + $controller->$contextKey = array(); + return $this; + } + + $action = (string) $action; + if (isset($controller->{$contextKey}[$action])) { + unset($controller->{$contextKey}[$action]); + } + + return $this; + } + + /** + * Retrieve ViewRenderer + * + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + protected function _getViewRenderer() + { + if (null === $this->_viewRenderer) { + $this->_viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer'); + } + + return $this->_viewRenderer; + } +} + diff --git a/library/Zend/Controller/Action/Helper/FlashMessenger.php b/library/Zend/Controller/Action/Helper/FlashMessenger.php new file mode 100644 index 00000000..6b601897 --- /dev/null +++ b/library/Zend/Controller/Action/Helper/FlashMessenger.php @@ -0,0 +1,266 @@ +getName()); + foreach (self::$_session as $namespace => $messages) { + self::$_messages[$namespace] = $messages; + unset(self::$_session->{$namespace}); + } + } + } + + /** + * postDispatch() - runs after action is dispatched, in this + * case, it is resetting the namespace in case we have forwarded to a different + * action, Flashmessage will be 'clean' (default namespace) + * + * @return Zend_Controller_Action_Helper_FlashMessenger Provides a fluent interface + */ + public function postDispatch() + { + $this->resetNamespace(); + return $this; + } + + /** + * setNamespace() - change the namespace messages are added to, useful for + * per action controller messaging between requests + * + * @param string $namespace + * @return Zend_Controller_Action_Helper_FlashMessenger Provides a fluent interface + */ + public function setNamespace($namespace = 'default') + { + $this->_namespace = $namespace; + return $this; + } + + /** + * resetNamespace() - reset the namespace to the default + * + * @return Zend_Controller_Action_Helper_FlashMessenger Provides a fluent interface + */ + public function resetNamespace() + { + $this->setNamespace(); + return $this; + } + + /** + * addMessage() - Add a message to flash message + * + * @param string $message + * @return Zend_Controller_Action_Helper_FlashMessenger Provides a fluent interface + */ + public function addMessage($message) + { + if (self::$_messageAdded === false) { + self::$_session->setExpirationHops(1, null, true); + } + + if (!is_array(self::$_session->{$this->_namespace})) { + self::$_session->{$this->_namespace} = array(); + } + + self::$_session->{$this->_namespace}[] = $message; + + return $this; + } + + /** + * hasMessages() - Wether a specific namespace has messages + * + * @return boolean + */ + public function hasMessages() + { + return isset(self::$_messages[$this->_namespace]); + } + + /** + * getMessages() - Get messages from a specific namespace + * + * @return array + */ + public function getMessages() + { + if ($this->hasMessages()) { + return self::$_messages[$this->_namespace]; + } + + return array(); + } + + /** + * Clear all messages from the previous request & current namespace + * + * @return boolean True if messages were cleared, false if none existed + */ + public function clearMessages() + { + if ($this->hasMessages()) { + unset(self::$_messages[$this->_namespace]); + return true; + } + + return false; + } + + /** + * hasCurrentMessages() - check to see if messages have been added to current + * namespace within this request + * + * @return boolean + */ + public function hasCurrentMessages() + { + return isset(self::$_session->{$this->_namespace}); + } + + /** + * getCurrentMessages() - get messages that have been added to the current + * namespace within this request + * + * @return array + */ + public function getCurrentMessages() + { + if ($this->hasCurrentMessages()) { + return self::$_session->{$this->_namespace}; + } + + return array(); + } + + /** + * clear messages from the current request & current namespace + * + * @return boolean + */ + public function clearCurrentMessages() + { + if ($this->hasCurrentMessages()) { + unset(self::$_session->{$this->_namespace}); + return true; + } + + return false; + } + + /** + * getIterator() - complete the IteratorAggregate interface, for iterating + * + * @return ArrayObject + */ + public function getIterator() + { + if ($this->hasMessages()) { + return new ArrayObject($this->getMessages()); + } + + return new ArrayObject(); + } + + /** + * count() - Complete the countable interface + * + * @return int + */ + public function count() + { + if ($this->hasMessages()) { + return count($this->getMessages()); + } + + return 0; + } + + /** + * Strategy pattern: proxy to addMessage() + * + * @param string $message + * @return void + */ + public function direct($message) + { + return $this->addMessage($message); + } +} diff --git a/library/Zend/Controller/Action/Helper/Json.php b/library/Zend/Controller/Action/Helper/Json.php new file mode 100644 index 00000000..d2f32c52 --- /dev/null +++ b/library/Zend/Controller/Action/Helper/Json.php @@ -0,0 +1,130 @@ +true|false + * if $keepLayouts and parmas for Zend_Json::encode are required + * then, the array can contains a 'keepLayout'=>true|false + * that will not be passed to Zend_Json::encode method but will be passed + * to Zend_View_Helper_Json + * @throws Zend_Controller_Action_Helper_Json + * @return string + */ + public function encodeJson($data, $keepLayouts = false) + { + /** + * @see Zend_View_Helper_Json + */ + require_once 'Zend/View/Helper/Json.php'; + $jsonHelper = new Zend_View_Helper_Json(); + $data = $jsonHelper->json($data, $keepLayouts); + + if (!$keepLayouts) { + /** + * @see Zend_Controller_Action_HelperBroker + */ + require_once 'Zend/Controller/Action/HelperBroker.php'; + Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer')->setNoRender(true); + } + + return $data; + } + + /** + * Encode JSON response and immediately send + * + * @param mixed $data + * @param boolean|array $keepLayouts + * NOTE: if boolean, establish $keepLayouts to true|false + * if array, admit params for Zend_Json::encode as enableJsonExprFinder=>true|false + * if $keepLayouts and parmas for Zend_Json::encode are required + * then, the array can contains a 'keepLayout'=>true|false + * that will not be passed to Zend_Json::encode method but will be passed + * to Zend_View_Helper_Json + * @return string|void + */ + public function sendJson($data, $keepLayouts = false) + { + $data = $this->encodeJson($data, $keepLayouts); + $response = $this->getResponse(); + $response->setBody($data); + + if (!$this->suppressExit) { + $response->sendResponse(); + exit; + } + + return $data; + } + + /** + * Strategy pattern: call helper as helper broker method + * + * Allows encoding JSON. If $sendNow is true, immediately sends JSON + * response. + * + * @param mixed $data + * @param boolean $sendNow + * @param boolean $keepLayouts + * @return string|void + */ + public function direct($data, $sendNow = true, $keepLayouts = false) + { + if ($sendNow) { + return $this->sendJson($data, $keepLayouts); + } + return $this->encodeJson($data, $keepLayouts); + } +} diff --git a/library/Zend/Controller/Action/Helper/Redirector.php b/library/Zend/Controller/Action/Helper/Redirector.php new file mode 100644 index 00000000..efa3572c --- /dev/null +++ b/library/Zend/Controller/Action/Helper/Redirector.php @@ -0,0 +1,534 @@ +_code; + } + + /** + * Validate HTTP status redirect code + * + * @param int $code + * @throws Zend_Controller_Action_Exception on invalid HTTP status code + * @return true + */ + protected function _checkCode($code) + { + $code = (int)$code; + if ((300 > $code) || (307 < $code) || (304 == $code) || (306 == $code)) { + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('Invalid redirect HTTP status code (' . $code . ')'); + } + + return true; + } + + /** + * Retrieve HTTP status code for {@link _redirect()} behaviour + * + * @param int $code + * @return Zend_Controller_Action_Helper_Redirector Provides a fluent interface + */ + public function setCode($code) + { + $this->_checkCode($code); + $this->_code = $code; + return $this; + } + + /** + * Retrieve flag for whether or not {@link _redirect()} will exit when finished. + * + * @return boolean + */ + public function getExit() + { + return $this->_exit; + } + + /** + * Retrieve exit flag for {@link _redirect()} behaviour + * + * @param boolean $flag + * @return Zend_Controller_Action_Helper_Redirector Provides a fluent interface + */ + public function setExit($flag) + { + $this->_exit = ($flag) ? true : false; + return $this; + } + + /** + * Retrieve flag for whether or not {@link _redirect()} will prepend the + * base URL on relative URLs + * + * @return boolean + */ + public function getPrependBase() + { + return $this->_prependBase; + } + + /** + * Retrieve 'prepend base' flag for {@link _redirect()} behaviour + * + * @param boolean $flag + * @return Zend_Controller_Action_Helper_Redirector Provides a fluent interface + */ + public function setPrependBase($flag) + { + $this->_prependBase = ($flag) ? true : false; + return $this; + } + + /** + * Retrieve flag for whether or not {@link redirectAndExit()} shall close the session before + * exiting. + * + * @return boolean + */ + public function getCloseSessionOnExit() + { + return $this->_closeSessionOnExit; + } + + /** + * Set flag for whether or not {@link redirectAndExit()} shall close the session before exiting. + * + * @param boolean $flag + * @return Zend_Controller_Action_Helper_Redirector Provides a fluent interface + */ + public function setCloseSessionOnExit($flag) + { + $this->_closeSessionOnExit = ($flag) ? true : false; + return $this; + } + + /** + * Return use absolute URI flag + * + * @return boolean + */ + public function getUseAbsoluteUri() + { + return $this->_useAbsoluteUri; + } + + /** + * Set use absolute URI flag + * + * @param boolean $flag + * @return Zend_Controller_Action_Helper_Redirector Provides a fluent interface + */ + public function setUseAbsoluteUri($flag = true) + { + $this->_useAbsoluteUri = ($flag) ? true : false; + return $this; + } + + /** + * Set redirect in response object + * + * @return void + */ + protected function _redirect($url) + { + if ($this->getUseAbsoluteUri() && !preg_match('#^(https?|ftp)://#', $url)) { + $host = (isset($_SERVER['HTTP_HOST'])?$_SERVER['HTTP_HOST']:''); + $proto = (isset($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=="off") ? 'https' : 'http'; + $port = (isset($_SERVER['SERVER_PORT'])?$_SERVER['SERVER_PORT']:80); + $uri = $proto . '://' . $host; + if ((('http' == $proto) && (80 != $port)) || (('https' == $proto) && (443 != $port))) { + // do not append if HTTP_HOST already contains port + if (strrchr($host, ':') === false) { + $uri .= ':' . $port; + } + } + $url = $uri . '/' . ltrim($url, '/'); + } + $this->_redirectUrl = $url; + $this->getResponse()->setRedirect($url, $this->getCode()); + } + + /** + * Retrieve currently set URL for redirect + * + * @return string + */ + public function getRedirectUrl() + { + return $this->_redirectUrl; + } + + /** + * Determine if the baseUrl should be prepended, and prepend if necessary + * + * @param string $url + * @return string + */ + protected function _prependBase($url) + { + if ($this->getPrependBase()) { + $request = $this->getRequest(); + if ($request instanceof Zend_Controller_Request_Http) { + $base = rtrim($request->getBaseUrl(), '/'); + if (!empty($base) && ('/' != $base)) { + $url = $base . '/' . ltrim($url, '/'); + } else { + $url = '/' . ltrim($url, '/'); + } + } + } + + return $url; + } + + /** + * Set a redirect URL of the form /module/controller/action/params + * + * @param string $action + * @param string $controller + * @param string $module + * @param array $params + * @return void + */ + public function setGotoSimple($action, $controller = null, $module = null, array $params = array()) + { + $dispatcher = $this->getFrontController()->getDispatcher(); + $request = $this->getRequest(); + $curModule = $request->getModuleName(); + $useDefaultController = false; + + if (null === $controller && null !== $module) { + $useDefaultController = true; + } + + if (null === $module) { + $module = $curModule; + } + + if ($module == $dispatcher->getDefaultModule()) { + $module = ''; + } + + if (null === $controller && !$useDefaultController) { + $controller = $request->getControllerName(); + if (empty($controller)) { + $controller = $dispatcher->getDefaultControllerName(); + } + } + + $params[$request->getModuleKey()] = $module; + $params[$request->getControllerKey()] = $controller; + $params[$request->getActionKey()] = $action; + + $router = $this->getFrontController()->getRouter(); + $url = $router->assemble($params, 'default', true); + + $this->_redirect($url); + } + + /** + * Build a URL based on a route + * + * @param array $urlOptions + * @param string $name Route name + * @param boolean $reset + * @param boolean $encode + * @return void + */ + public function setGotoRoute(array $urlOptions = array(), $name = null, $reset = false, $encode = true) + { + $router = $this->getFrontController()->getRouter(); + $url = $router->assemble($urlOptions, $name, $reset, $encode); + + $this->_redirect($url); + } + + /** + * Set a redirect URL string + * + * By default, emits a 302 HTTP status header, prepends base URL as defined + * in request object if url is relative, and halts script execution by + * calling exit(). + * + * $options is an optional associative array that can be used to control + * redirect behaviour. The available option keys are: + * - exit: boolean flag indicating whether or not to halt script execution when done + * - prependBase: boolean flag indicating whether or not to prepend the base URL when a relative URL is provided + * - code: integer HTTP status code to use with redirect. Should be between 300 and 307. + * + * _redirect() sets the Location header in the response object. If you set + * the exit flag to false, you can override this header later in code + * execution. + * + * If the exit flag is true (true by default), _redirect() will write and + * close the current session, if any. + * + * @param string $url + * @param array $options + * @return void + */ + public function setGotoUrl($url, array $options = array()) + { + // prevent header injections + $url = str_replace(array("\n", "\r"), '', $url); + + if (null !== $options) { + if (isset($options['exit'])) { + $this->setExit(($options['exit']) ? true : false); + } + if (isset($options['prependBase'])) { + $this->setPrependBase(($options['prependBase']) ? true : false); + } + if (isset($options['code'])) { + $this->setCode($options['code']); + } + } + + // If relative URL, decide if we should prepend base URL + if (!preg_match('|^[a-z]+://|', $url)) { + $url = $this->_prependBase($url); + } + + $this->_redirect($url); + } + + /** + * Perform a redirect to an action/controller/module with params + * + * @param string $action + * @param string $controller + * @param string $module + * @param array $params + * @return void + */ + public function gotoSimple($action, $controller = null, $module = null, array $params = array()) + { + $this->setGotoSimple($action, $controller, $module, $params); + + if ($this->getExit()) { + $this->redirectAndExit(); + } + } + + /** + * Perform a redirect to an action/controller/module with params, forcing an immdiate exit + * + * @param mixed $action + * @param mixed $controller + * @param mixed $module + * @param array $params + * @return void + */ + public function gotoSimpleAndExit($action, $controller = null, $module = null, array $params = array()) + { + $this->setGotoSimple($action, $controller, $module, $params); + $this->redirectAndExit(); + } + + /** + * Redirect to a route-based URL + * + * Uses route's assemble method tobuild the URL; route is specified by $name; + * default route is used if none provided. + * + * @param array $urlOptions Array of key/value pairs used to assemble URL + * @param string $name + * @param boolean $reset + * @param boolean $encode + * @return void + */ + public function gotoRoute(array $urlOptions = array(), $name = null, $reset = false, $encode = true) + { + $this->setGotoRoute($urlOptions, $name, $reset, $encode); + + if ($this->getExit()) { + $this->redirectAndExit(); + } + } + + /** + * Redirect to a route-based URL, and immediately exit + * + * Uses route's assemble method tobuild the URL; route is specified by $name; + * default route is used if none provided. + * + * @param array $urlOptions Array of key/value pairs used to assemble URL + * @param string $name + * @param boolean $reset + * @return void + */ + public function gotoRouteAndExit(array $urlOptions = array(), $name = null, $reset = false) + { + $this->setGotoRoute($urlOptions, $name, $reset); + $this->redirectAndExit(); + } + + /** + * Perform a redirect to a url + * + * @param string $url + * @param array $options + * @return void + */ + public function gotoUrl($url, array $options = array()) + { + $this->setGotoUrl($url, $options); + + if ($this->getExit()) { + $this->redirectAndExit(); + } + } + + /** + * Set a URL string for a redirect, perform redirect, and immediately exit + * + * @param string $url + * @param array $options + * @return void + */ + public function gotoUrlAndExit($url, array $options = array()) + { + $this->setGotoUrl($url, $options); + $this->redirectAndExit(); + } + + /** + * exit(): Perform exit for redirector + * + * @return void + */ + public function redirectAndExit() + { + if ($this->getCloseSessionOnExit()) { + // Close session, if started + if (class_exists('Zend_Session', false) && Zend_Session::isStarted()) { + Zend_Session::writeClose(); + } elseif (isset($_SESSION)) { + session_write_close(); + } + } + + $this->getResponse()->sendHeaders(); + exit(); + } + + /** + * direct(): Perform helper when called as + * $this->_helper->redirector($action, $controller, $module, $params) + * + * @param string $action + * @param string $controller + * @param string $module + * @param array $params + * @return void + */ + public function direct($action, $controller = null, $module = null, array $params = array()) + { + $this->gotoSimple($action, $controller, $module, $params); + } + + /** + * Overloading + * + * Overloading for old 'goto', 'setGoto', and 'gotoAndExit' methods + * + * @param string $method + * @param array $args + * @return mixed + * @throws Zend_Controller_Action_Exception for invalid methods + */ + public function __call($method, $args) + { + $method = strtolower($method); + if ('goto' == $method) { + return call_user_func_array(array($this, 'gotoSimple'), $args); + } + if ('setgoto' == $method) { + return call_user_func_array(array($this, 'setGotoSimple'), $args); + } + if ('gotoandexit' == $method) { + return call_user_func_array(array($this, 'gotoSimpleAndExit'), $args); + } + + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception(sprintf('Invalid method "%s" called on redirector', $method)); + } +} diff --git a/library/Zend/Controller/Action/Helper/Url.php b/library/Zend/Controller/Action/Helper/Url.php new file mode 100644 index 00000000..ab0908ab --- /dev/null +++ b/library/Zend/Controller/Action/Helper/Url.php @@ -0,0 +1,117 @@ +getRequest(); + + if (null === $controller) { + $controller = $request->getControllerName(); + } + + if (null === $module) { + $module = $request->getModuleName(); + } + + $url = $controller . '/' . $action; + if ($module != $this->getFrontController()->getDispatcher()->getDefaultModule()) { + $url = $module . '/' . $url; + } + + if ('' !== ($baseUrl = $this->getFrontController()->getBaseUrl())) { + $url = $baseUrl . '/' . $url; + } + + if (null !== $params) { + $paramPairs = array(); + foreach ($params as $key => $value) { + $paramPairs[] = urlencode($key) . '/' . urlencode($value); + } + $paramString = implode('/', $paramPairs); + $url .= '/' . $paramString; + } + + $url = '/' . ltrim($url, '/'); + + return $url; + } + + /** + * Assembles a URL based on a given route + * + * This method will typically be used for more complex operations, as it + * ties into the route objects registered with the router. + * + * @param array $urlOptions Options passed to the assemble method of the Route object. + * @param mixed $name The name of a Route to use. If null it will use the current Route + * @param boolean $reset + * @param boolean $encode + * @return string Url for the link href attribute. + */ + public function url($urlOptions = array(), $name = null, $reset = false, $encode = true) + { + $router = $this->getFrontController()->getRouter(); + return $router->assemble($urlOptions, $name, $reset, $encode); + } + + /** + * Perform helper when called as $this->_helper->url() from an action controller + * + * Proxies to {@link simple()} + * + * @param string $action + * @param string $controller + * @param string $module + * @param array $params + * @return string + */ + public function direct($action, $controller = null, $module = null, array $params = null) + { + return $this->simple($action, $controller, $module, $params); + } +} diff --git a/library/Zend/Controller/Action/Helper/ViewRenderer.php b/library/Zend/Controller/Action/Helper/ViewRenderer.php new file mode 100644 index 00000000..51059be5 --- /dev/null +++ b/library/Zend/Controller/Action/Helper/ViewRenderer.php @@ -0,0 +1,992 @@ + + * // In your bootstrap: + * Zend_Controller_Action_HelperBroker::addHelper(new Zend_Controller_Action_Helper_ViewRenderer()); + * + * // In your action controller methods: + * $viewHelper = $this->_helper->getHelper('view'); + * + * // Don't use controller subdirectories + * $viewHelper->setNoController(true); + * + * // Specify a different script to render: + * $this->_helper->viewRenderer('form'); + * + * + * + * @uses Zend_Controller_Action_Helper_Abstract + * @package Zend_Controller + * @subpackage Zend_Controller_Action_Helper + * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Controller_Action_Helper_ViewRenderer extends Zend_Controller_Action_Helper_Abstract +{ + /** + * @var Zend_View_Interface + */ + public $view; + + /** + * Word delimiters + * @var array + */ + protected $_delimiters; + + /** + * @var Zend_Filter_Inflector + */ + protected $_inflector; + + /** + * Inflector target + * @var string + */ + protected $_inflectorTarget = ''; + + /** + * Current module directory + * @var string + */ + protected $_moduleDir = ''; + + /** + * Whether or not to autorender using controller name as subdirectory; + * global setting (not reset at next invocation) + * @var boolean + */ + protected $_neverController = false; + + /** + * Whether or not to autorender postDispatch; global setting (not reset at + * next invocation) + * @var boolean + */ + protected $_neverRender = false; + + /** + * Whether or not to use a controller name as a subdirectory when rendering + * @var boolean + */ + protected $_noController = false; + + /** + * Whether or not to autorender postDispatch; per controller/action setting (reset + * at next invocation) + * @var boolean + */ + protected $_noRender = false; + + /** + * Characters representing path delimiters in the controller + * @var string|array + */ + protected $_pathDelimiters; + + /** + * Which named segment of the response to utilize + * @var string + */ + protected $_responseSegment = null; + + /** + * Which action view script to render + * @var string + */ + protected $_scriptAction = null; + + /** + * View object basePath + * @var string + */ + protected $_viewBasePathSpec = ':moduleDir/views'; + + /** + * View script path specification string + * @var string + */ + protected $_viewScriptPathSpec = ':controller/:action.:suffix'; + + /** + * View script path specification string, minus controller segment + * @var string + */ + protected $_viewScriptPathNoControllerSpec = ':action.:suffix'; + + /** + * View script suffix + * @var string + */ + protected $_viewSuffix = 'phtml'; + + /** + * Constructor + * + * Optionally set view object and options. + * + * @param Zend_View_Interface $view + * @param array $options + * @return void + */ + public function __construct(Zend_View_Interface $view = null, array $options = array()) + { + if (null !== $view) { + $this->setView($view); + } + + if (!empty($options)) { + $this->_setOptions($options); + } + } + + /** + * Clone - also make sure the view is cloned. + * + * @return void + */ + public function __clone() + { + if (isset($this->view) && $this->view instanceof Zend_View_Interface) { + $this->view = clone $this->view; + + } + } + + /** + * Set the view object + * + * @param Zend_View_Interface $view + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setView(Zend_View_Interface $view) + { + $this->view = $view; + return $this; + } + + /** + * Get current module name + * + * @return string + */ + public function getModule() + { + $request = $this->getRequest(); + $module = $request->getModuleName(); + if (null === $module) { + $module = $this->getFrontController()->getDispatcher()->getDefaultModule(); + } + + return $module; + } + + /** + * Get module directory + * + * @throws Zend_Controller_Action_Exception + * @return string + */ + public function getModuleDirectory() + { + $module = $this->getModule(); + $moduleDir = $this->getFrontController()->getControllerDirectory($module); + if ((null === $moduleDir) || is_array($moduleDir)) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('ViewRenderer cannot locate module directory for module "' . $module . '"'); + } + $this->_moduleDir = dirname($moduleDir); + return $this->_moduleDir; + } + + /** + * Get inflector + * + * @return Zend_Filter_Inflector + */ + public function getInflector() + { + if (null === $this->_inflector) { + /** + * @see Zend_Filter_Inflector + */ + require_once 'Zend/Filter/Inflector.php'; + /** + * @see Zend_Filter_PregReplace + */ + require_once 'Zend/Filter/PregReplace.php'; + /** + * @see Zend_Filter_Word_UnderscoreToSeparator + */ + require_once 'Zend/Filter/Word/UnderscoreToSeparator.php'; + $this->_inflector = new Zend_Filter_Inflector(); + $this->_inflector->setStaticRuleReference('moduleDir', $this->_moduleDir) // moduleDir must be specified before the less specific 'module' + ->addRules(array( + ':module' => array('Word_CamelCaseToDash', 'StringToLower'), + ':controller' => array('Word_CamelCaseToDash', new Zend_Filter_Word_UnderscoreToSeparator('/'), 'StringToLower', new Zend_Filter_PregReplace('/\./', '-')), + ':action' => array('Word_CamelCaseToDash', new Zend_Filter_PregReplace('#[^a-z0-9' . preg_quote('/', '#') . ']+#i', '-'), 'StringToLower'), + )) + ->setStaticRuleReference('suffix', $this->_viewSuffix) + ->setTargetReference($this->_inflectorTarget); + } + + // Ensure that module directory is current + $this->getModuleDirectory(); + + return $this->_inflector; + } + + /** + * Set inflector + * + * @param Zend_Filter_Inflector $inflector + * @param boolean $reference Whether the moduleDir, target, and suffix should be set as references to ViewRenderer properties + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setInflector(Zend_Filter_Inflector $inflector, $reference = false) + { + $this->_inflector = $inflector; + if ($reference) { + $this->_inflector->setStaticRuleReference('suffix', $this->_viewSuffix) + ->setStaticRuleReference('moduleDir', $this->_moduleDir) + ->setTargetReference($this->_inflectorTarget); + } + return $this; + } + + /** + * Set inflector target + * + * @param string $target + * @return void + */ + protected function _setInflectorTarget($target) + { + $this->_inflectorTarget = (string) $target; + } + + /** + * Set internal module directory representation + * + * @param string $dir + * @return void + */ + protected function _setModuleDir($dir) + { + $this->_moduleDir = (string) $dir; + } + + /** + * Get internal module directory representation + * + * @return string + */ + protected function _getModuleDir() + { + return $this->_moduleDir; + } + + /** + * Generate a class prefix for helper and filter classes + * + * @return string + */ + protected function _generateDefaultPrefix() + { + $default = 'Zend_View'; + if (null === $this->_actionController) { + return $default; + } + + $class = get_class($this->_actionController); + + if (!strstr($class, '_')) { + return $default; + } + + $module = $this->getModule(); + if ('default' == $module) { + return $default; + } + + $prefix = substr($class, 0, strpos($class, '_')) . '_View'; + + return $prefix; + } + + /** + * Retrieve base path based on location of current action controller + * + * @return string + */ + protected function _getBasePath() + { + if (null === $this->_actionController) { + return './views'; + } + + $inflector = $this->getInflector(); + $this->_setInflectorTarget($this->getViewBasePathSpec()); + + $dispatcher = $this->getFrontController()->getDispatcher(); + $request = $this->getRequest(); + + $parts = array( + 'module' => (($moduleName = $request->getModuleName()) != '') ? $dispatcher->formatModuleName($moduleName) : $moduleName, + 'controller' => $request->getControllerName(), + 'action' => $dispatcher->formatActionName($request->getActionName()) + ); + + $path = $inflector->filter($parts); + return $path; + } + + /** + * Set options + * + * @param array $options + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + protected function _setOptions(array $options) + { + foreach ($options as $key => $value) + { + switch ($key) { + case 'neverRender': + case 'neverController': + case 'noController': + case 'noRender': + $property = '_' . $key; + $this->{$property} = ($value) ? true : false; + break; + case 'responseSegment': + case 'scriptAction': + case 'viewBasePathSpec': + case 'viewScriptPathSpec': + case 'viewScriptPathNoControllerSpec': + case 'viewSuffix': + $property = '_' . $key; + $this->{$property} = (string) $value; + break; + default: + break; + } + } + + return $this; + } + + /** + * Initialize the view object + * + * $options may contain the following keys: + * - neverRender - flag dis/enabling postDispatch() autorender (affects all subsequent calls) + * - noController - flag indicating whether or not to look for view scripts in subdirectories named after the controller + * - noRender - flag indicating whether or not to autorender postDispatch() + * - responseSegment - which named response segment to render a view script to + * - scriptAction - what action script to render + * - viewBasePathSpec - specification to use for determining view base path + * - viewScriptPathSpec - specification to use for determining view script paths + * - viewScriptPathNoControllerSpec - specification to use for determining view script paths when noController flag is set + * - viewSuffix - what view script filename suffix to use + * + * @param string $path + * @param string $prefix + * @param array $options + * @throws Zend_Controller_Action_Exception + * @return void + */ + public function initView($path = null, $prefix = null, array $options = array()) + { + if (null === $this->view) { + $this->setView(new Zend_View()); + } + + // Reset some flags every time + $options['noController'] = (isset($options['noController'])) ? $options['noController'] : false; + $options['noRender'] = (isset($options['noRender'])) ? $options['noRender'] : false; + $this->_scriptAction = null; + $this->_responseSegment = null; + + // Set options first; may be used to determine other initializations + $this->_setOptions($options); + + // Get base view path + if (empty($path)) { + $path = $this->_getBasePath(); + if (empty($path)) { + /** + * @see Zend_Controller_Action_Exception + */ + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('ViewRenderer initialization failed: retrieved view base path is empty'); + } + } + + if (null === $prefix) { + $prefix = $this->_generateDefaultPrefix(); + } + + // Determine if this path has already been registered + $currentPaths = $this->view->getScriptPaths(); + $path = str_replace(array('/', '\\'), '/', $path); + $pathExists = false; + foreach ($currentPaths as $tmpPath) { + $tmpPath = str_replace(array('/', '\\'), '/', $tmpPath); + if (strstr($tmpPath, $path)) { + $pathExists = true; + break; + } + } + if (!$pathExists) { + $this->view->addBasePath($path, $prefix); + } + + // Register view with action controller (unless already registered) + if ((null !== $this->_actionController) && (null === $this->_actionController->view)) { + $this->_actionController->view = $this->view; + $this->_actionController->viewSuffix = $this->_viewSuffix; + } + } + + /** + * init - initialize view + * + * @return void + */ + public function init() + { + if ($this->getFrontController()->getParam('noViewRenderer')) { + return; + } + + $this->initView(); + } + + /** + * Set view basePath specification + * + * Specification can contain one or more of the following: + * - :moduleDir - current module directory + * - :controller - name of current controller in the request + * - :action - name of current action in the request + * - :module - name of current module in the request + * + * @param string $path + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setViewBasePathSpec($path) + { + $this->_viewBasePathSpec = (string) $path; + return $this; + } + + /** + * Retrieve the current view basePath specification string + * + * @return string + */ + public function getViewBasePathSpec() + { + return $this->_viewBasePathSpec; + } + + /** + * Set view script path specification + * + * Specification can contain one or more of the following: + * - :moduleDir - current module directory + * - :controller - name of current controller in the request + * - :action - name of current action in the request + * - :module - name of current module in the request + * + * @param string $path + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setViewScriptPathSpec($path) + { + $this->_viewScriptPathSpec = (string) $path; + return $this; + } + + /** + * Retrieve the current view script path specification string + * + * @return string + */ + public function getViewScriptPathSpec() + { + return $this->_viewScriptPathSpec; + } + + /** + * Set view script path specification (no controller variant) + * + * Specification can contain one or more of the following: + * - :moduleDir - current module directory + * - :controller - name of current controller in the request + * - :action - name of current action in the request + * - :module - name of current module in the request + * + * :controller will likely be ignored in this variant. + * + * @param string $path + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setViewScriptPathNoControllerSpec($path) + { + $this->_viewScriptPathNoControllerSpec = (string) $path; + return $this; + } + + /** + * Retrieve the current view script path specification string (no controller variant) + * + * @return string + */ + public function getViewScriptPathNoControllerSpec() + { + return $this->_viewScriptPathNoControllerSpec; + } + + /** + * Get a view script based on an action and/or other variables + * + * Uses values found in current request if no values passed in $vars. + * + * If {@link $_noController} is set, uses {@link $_viewScriptPathNoControllerSpec}; + * otherwise, uses {@link $_viewScriptPathSpec}. + * + * @param string $action + * @param array $vars + * @return string + */ + public function getViewScript($action = null, array $vars = array()) + { + $request = $this->getRequest(); + if ((null === $action) && (!isset($vars['action']))) { + $action = $this->getScriptAction(); + if (null === $action) { + $action = $request->getActionName(); + } + $vars['action'] = $action; + } elseif (null !== $action) { + $vars['action'] = $action; + } + + $replacePattern = array('/[^a-z0-9]+$/i', '/^[^a-z0-9]+/i'); + $vars['action'] = preg_replace($replacePattern, '', $vars['action']); + + $inflector = $this->getInflector(); + if ($this->getNoController() || $this->getNeverController()) { + $this->_setInflectorTarget($this->getViewScriptPathNoControllerSpec()); + } else { + $this->_setInflectorTarget($this->getViewScriptPathSpec()); + } + return $this->_translateSpec($vars); + } + + /** + * Set the neverRender flag (i.e., globally dis/enable autorendering) + * + * @param boolean $flag + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setNeverRender($flag = true) + { + $this->_neverRender = ($flag) ? true : false; + return $this; + } + + /** + * Retrieve neverRender flag value + * + * @return boolean + */ + public function getNeverRender() + { + return $this->_neverRender; + } + + /** + * Set the noRender flag (i.e., whether or not to autorender) + * + * @param boolean $flag + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setNoRender($flag = true) + { + $this->_noRender = ($flag) ? true : false; + return $this; + } + + /** + * Retrieve noRender flag value + * + * @return boolean + */ + public function getNoRender() + { + return $this->_noRender; + } + + /** + * Set the view script to use + * + * @param string $name + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setScriptAction($name) + { + $this->_scriptAction = (string) $name; + return $this; + } + + /** + * Retrieve view script name + * + * @return string + */ + public function getScriptAction() + { + return $this->_scriptAction; + } + + /** + * Set the response segment name + * + * @param string $name + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setResponseSegment($name) + { + if (null === $name) { + $this->_responseSegment = null; + } else { + $this->_responseSegment = (string) $name; + } + + return $this; + } + + /** + * Retrieve named response segment name + * + * @return string + */ + public function getResponseSegment() + { + return $this->_responseSegment; + } + + /** + * Set the noController flag (i.e., whether or not to render into controller subdirectories) + * + * @param boolean $flag + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setNoController($flag = true) + { + $this->_noController = ($flag) ? true : false; + return $this; + } + + /** + * Retrieve noController flag value + * + * @return boolean + */ + public function getNoController() + { + return $this->_noController; + } + + /** + * Set the neverController flag (i.e., whether or not to render into controller subdirectories) + * + * @param boolean $flag + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setNeverController($flag = true) + { + $this->_neverController = ($flag) ? true : false; + return $this; + } + + /** + * Retrieve neverController flag value + * + * @return boolean + */ + public function getNeverController() + { + return $this->_neverController; + } + + /** + * Set view script suffix + * + * @param string $suffix + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setViewSuffix($suffix) + { + $this->_viewSuffix = (string) $suffix; + return $this; + } + + /** + * Get view script suffix + * + * @return string + */ + public function getViewSuffix() + { + return $this->_viewSuffix; + } + + /** + * Set options for rendering a view script + * + * @param string $action View script to render + * @param string $name Response named segment to render to + * @param boolean $noController Whether or not to render within a subdirectory named after the controller + * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface + */ + public function setRender($action = null, $name = null, $noController = null) + { + if (null !== $action) { + $this->setScriptAction($action); + } + + if (null !== $name) { + $this->setResponseSegment($name); + } + + if (null !== $noController) { + $this->setNoController($noController); + } + + return $this; + } + + /** + * Inflect based on provided vars + * + * Allowed variables are: + * - :moduleDir - current module directory + * - :module - current module name + * - :controller - current controller name + * - :action - current action name + * - :suffix - view script file suffix + * + * @param array $vars + * @return string + */ + protected function _translateSpec(array $vars = array()) + { + $inflector = $this->getInflector(); + $request = $this->getRequest(); + $dispatcher = $this->getFrontController()->getDispatcher(); + $module = $dispatcher->formatModuleName($request->getModuleName()); + $controller = $request->getControllerName(); + $action = $dispatcher->formatActionName($request->getActionName()); + + $params = compact('module', 'controller', 'action'); + foreach ($vars as $key => $value) { + switch ($key) { + case 'module': + case 'controller': + case 'action': + case 'moduleDir': + case 'suffix': + $params[$key] = (string) $value; + break; + default: + break; + } + } + + if (isset($params['suffix'])) { + $origSuffix = $this->getViewSuffix(); + $this->setViewSuffix($params['suffix']); + } + if (isset($params['moduleDir'])) { + $origModuleDir = $this->_getModuleDir(); + $this->_setModuleDir($params['moduleDir']); + } + + $filtered = $inflector->filter($params); + + if (isset($params['suffix'])) { + $this->setViewSuffix($origSuffix); + } + if (isset($params['moduleDir'])) { + $this->_setModuleDir($origModuleDir); + } + + return $filtered; + } + + /** + * Render a view script (optionally to a named response segment) + * + * Sets the noRender flag to true when called. + * + * @param string $script + * @param string $name + * @return void + */ + public function renderScript($script, $name = null) + { + if (null === $name) { + $name = $this->getResponseSegment(); + } + + $this->getResponse()->appendBody( + $this->view->render($script), + $name + ); + + $this->setNoRender(); + } + + /** + * Render a view based on path specifications + * + * Renders a view based on the view script path specifications. + * + * @param string $action + * @param string $name + * @param boolean $noController + * @return void + */ + public function render($action = null, $name = null, $noController = null) + { + $this->setRender($action, $name, $noController); + $path = $this->getViewScript(); + $this->renderScript($path, $name); + } + + /** + * Render a script based on specification variables + * + * Pass an action, and one or more specification variables (view script suffix) + * to determine the view script path, and render that script. + * + * @param string $action + * @param array $vars + * @param string $name + * @return void + */ + public function renderBySpec($action = null, array $vars = array(), $name = null) + { + if (null !== $name) { + $this->setResponseSegment($name); + } + + $path = $this->getViewScript($action, $vars); + + $this->renderScript($path); + } + + /** + * postDispatch - auto render a view + * + * Only autorenders if: + * - _noRender is false + * - action controller is present + * - request has not been re-dispatched (i.e., _forward() has not been called) + * - response is not a redirect + * + * @return void + */ + public function postDispatch() + { + if ($this->_shouldRender()) { + $this->render(); + } + } + + /** + * Should the ViewRenderer render a view script? + * + * @return boolean + */ + protected function _shouldRender() + { + return (!$this->getFrontController()->getParam('noViewRenderer') + && !$this->_neverRender + && !$this->_noRender + && (null !== $this->_actionController) + && $this->getRequest()->isDispatched() + && !$this->getResponse()->isRedirect() + ); + } + + /** + * Use this helper as a method; proxies to setRender() + * + * @param string $action + * @param string $name + * @param boolean $noController + * @return void + */ + public function direct($action = null, $name = null, $noController = null) + { + $this->setRender($action, $name, $noController); + } +} diff --git a/library/Zend/Controller/Action/HelperBroker.php b/library/Zend/Controller/Action/HelperBroker.php new file mode 100644 index 00000000..7c98fd85 --- /dev/null +++ b/library/Zend/Controller/Action/HelperBroker.php @@ -0,0 +1,381 @@ + 'Zend/Controller/Action/Helper/', + )); + } + return self::$_pluginLoader; + } + + /** + * addPrefix() - Add repository of helpers by prefix + * + * @param string $prefix + */ + static public function addPrefix($prefix) + { + $prefix = rtrim($prefix, '_'); + $path = str_replace('_', DIRECTORY_SEPARATOR, $prefix); + self::getPluginLoader()->addPrefixPath($prefix, $path); + } + + /** + * addPath() - Add path to repositories where Action_Helpers could be found. + * + * @param string $path + * @param string $prefix Optional; defaults to 'Zend_Controller_Action_Helper' + * @return void + */ + static public function addPath($path, $prefix = 'Zend_Controller_Action_Helper') + { + self::getPluginLoader()->addPrefixPath($prefix, $path); + } + + /** + * addHelper() - Add helper objects + * + * @param Zend_Controller_Action_Helper_Abstract $helper + * @return void + */ + static public function addHelper(Zend_Controller_Action_Helper_Abstract $helper) + { + self::getStack()->push($helper); + return; + } + + /** + * resetHelpers() + * + * @return void + */ + static public function resetHelpers() + { + self::$_stack = null; + return; + } + + /** + * Retrieve or initialize a helper statically + * + * Retrieves a helper object statically, loading on-demand if the helper + * does not already exist in the stack. Always returns a helper, unless + * the helper class cannot be found. + * + * @param string $name + * @return Zend_Controller_Action_Helper_Abstract + */ + public static function getStaticHelper($name) + { + $name = self::_normalizeHelperName($name); + $stack = self::getStack(); + + if (!isset($stack->{$name})) { + self::_loadHelper($name); + } + + return $stack->{$name}; + } + + /** + * getExistingHelper() - get helper by name + * + * Static method to retrieve helper object. Only retrieves helpers already + * initialized with the broker (either via addHelper() or on-demand loading + * via getHelper()). + * + * Throws an exception if the referenced helper does not exist in the + * stack; use {@link hasHelper()} to check if the helper is registered + * prior to retrieving it. + * + * @param string $name + * @return Zend_Controller_Action_Helper_Abstract + * @throws Zend_Controller_Action_Exception + */ + public static function getExistingHelper($name) + { + $name = self::_normalizeHelperName($name); + $stack = self::getStack(); + + if (!isset($stack->{$name})) { + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('Action helper "' . $name . '" has not been registered with the helper broker'); + } + + return $stack->{$name}; + } + + /** + * Return all registered helpers as helper => object pairs + * + * @return array + */ + public static function getExistingHelpers() + { + return self::getStack()->getHelpersByName(); + } + + /** + * Is a particular helper loaded in the broker? + * + * @param string $name + * @return boolean + */ + public static function hasHelper($name) + { + $name = self::_normalizeHelperName($name); + return isset(self::getStack()->{$name}); + } + + /** + * Remove a particular helper from the broker + * + * @param string $name + * @return boolean + */ + public static function removeHelper($name) + { + $name = self::_normalizeHelperName($name); + $stack = self::getStack(); + if (isset($stack->{$name})) { + unset($stack->{$name}); + } + + return false; + } + + /** + * Lazy load the priority stack and return it + * + * @return Zend_Controller_Action_HelperBroker_PriorityStack + */ + public static function getStack() + { + if (self::$_stack == null) { + self::$_stack = new Zend_Controller_Action_HelperBroker_PriorityStack(); + } + + return self::$_stack; + } + + /** + * Constructor + * + * @param Zend_Controller_Action $actionController + * @return void + */ + public function __construct(Zend_Controller_Action $actionController) + { + $this->_actionController = $actionController; + foreach (self::getStack() as $helper) { + $helper->setActionController($actionController); + $helper->init(); + } + } + + /** + * notifyPreDispatch() - called by action controller dispatch method + * + * @return void + */ + public function notifyPreDispatch() + { + foreach (self::getStack() as $helper) { + $helper->preDispatch(); + } + } + + /** + * notifyPostDispatch() - called by action controller dispatch method + * + * @return void + */ + public function notifyPostDispatch() + { + foreach (self::getStack() as $helper) { + $helper->postDispatch(); + } + } + + /** + * getHelper() - get helper by name + * + * @param string $name + * @return Zend_Controller_Action_Helper_Abstract + */ + public function getHelper($name) + { + $name = self::_normalizeHelperName($name); + $stack = self::getStack(); + + if (!isset($stack->{$name})) { + self::_loadHelper($name); + } + + $helper = $stack->{$name}; + + $initialize = false; + if (null === ($actionController = $helper->getActionController())) { + $initialize = true; + } elseif ($actionController !== $this->_actionController) { + $initialize = true; + } + + if ($initialize) { + $helper->setActionController($this->_actionController) + ->init(); + } + + return $helper; + } + + /** + * Method overloading + * + * @param string $method + * @param array $args + * @return mixed + * @throws Zend_Controller_Action_Exception if helper does not have a direct() method + */ + public function __call($method, $args) + { + $helper = $this->getHelper($method); + if (!method_exists($helper, 'direct')) { + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('Helper "' . $method . '" does not support overloading via direct()'); + } + return call_user_func_array(array($helper, 'direct'), $args); + } + + /** + * Retrieve helper by name as object property + * + * @param string $name + * @return Zend_Controller_Action_Helper_Abstract + */ + public function __get($name) + { + return $this->getHelper($name); + } + + /** + * Normalize helper name for lookups + * + * @param string $name + * @return string + */ + protected static function _normalizeHelperName($name) + { + if (strpos($name, '_') !== false) { + $name = str_replace(' ', '', ucwords(str_replace('_', ' ', $name))); + } + + return ucfirst($name); + } + + /** + * Load a helper + * + * @param string $name + * @return void + */ + protected static function _loadHelper($name) + { + try { + $class = self::getPluginLoader()->load($name); + } catch (Zend_Loader_PluginLoader_Exception $e) { + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('Action Helper by name ' . $name . ' not found', 0, $e); + } + + $helper = new $class(); + + if (!$helper instanceof Zend_Controller_Action_Helper_Abstract) { + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('Helper name ' . $name . ' -> class ' . $class . ' is not of type Zend_Controller_Action_Helper_Abstract'); + } + + self::getStack()->push($helper); + } +} diff --git a/library/Zend/Controller/Action/HelperBroker/PriorityStack.php b/library/Zend/Controller/Action/HelperBroker/PriorityStack.php new file mode 100644 index 00000000..13d0f0c6 --- /dev/null +++ b/library/Zend/Controller/Action/HelperBroker/PriorityStack.php @@ -0,0 +1,280 @@ +_helpersByNameRef)) { + return false; + } + + return $this->_helpersByNameRef[$helperName]; + } + + /** + * Magic property overloading for returning if helper is set by name + * + * @param string $helperName The helper name + * @return Zend_Controller_Action_Helper_Abstract + */ + public function __isset($helperName) + { + return array_key_exists($helperName, $this->_helpersByNameRef); + } + + /** + * Magic property overloading for unsetting if helper is exists by name + * + * @param string $helperName The helper name + * @return Zend_Controller_Action_Helper_Abstract + */ + public function __unset($helperName) + { + return $this->offsetUnset($helperName); + } + + /** + * push helper onto the stack + * + * @param Zend_Controller_Action_Helper_Abstract $helper + * @return Zend_Controller_Action_HelperBroker_PriorityStack + */ + public function push(Zend_Controller_Action_Helper_Abstract $helper) + { + $this->offsetSet($this->getNextFreeHigherPriority(), $helper); + return $this; + } + + /** + * Return something iterable + * + * @return array + */ + public function getIterator() + { + return new ArrayObject($this->_helpersByPriority); + } + + /** + * offsetExists() + * + * @param int|string $priorityOrHelperName + * @return Zend_Controller_Action_HelperBroker_PriorityStack + */ + public function offsetExists($priorityOrHelperName) + { + if (is_string($priorityOrHelperName)) { + return array_key_exists($priorityOrHelperName, $this->_helpersByNameRef); + } else { + return array_key_exists($priorityOrHelperName, $this->_helpersByPriority); + } + } + + /** + * offsetGet() + * + * @param int|string $priorityOrHelperName + * @return Zend_Controller_Action_HelperBroker_PriorityStack + */ + public function offsetGet($priorityOrHelperName) + { + if (!$this->offsetExists($priorityOrHelperName)) { + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('A helper with priority ' . $priorityOrHelperName . ' does not exist.'); + } + + if (is_string($priorityOrHelperName)) { + return $this->_helpersByNameRef[$priorityOrHelperName]; + } else { + return $this->_helpersByPriority[$priorityOrHelperName]; + } + } + + /** + * offsetSet() + * + * @param int $priority + * @param Zend_Controller_Action_Helper_Abstract $helper + * @return Zend_Controller_Action_HelperBroker_PriorityStack + */ + public function offsetSet($priority, $helper) + { + $priority = (int) $priority; + + if (!$helper instanceof Zend_Controller_Action_Helper_Abstract) { + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('$helper must extend Zend_Controller_Action_Helper_Abstract.'); + } + + if (array_key_exists($helper->getName(), $this->_helpersByNameRef)) { + // remove any object with the same name to retain BC compailitbility + // @todo At ZF 2.0 time throw an exception here. + $this->offsetUnset($helper->getName()); + } + + if (array_key_exists($priority, $this->_helpersByPriority)) { + $priority = $this->getNextFreeHigherPriority($priority); // ensures LIFO + trigger_error("A helper with the same priority already exists, reassigning to $priority", E_USER_WARNING); + } + + $this->_helpersByPriority[$priority] = $helper; + $this->_helpersByNameRef[$helper->getName()] = $helper; + + if ($priority == ($nextFreeDefault = $this->getNextFreeHigherPriority($this->_nextDefaultPriority))) { + $this->_nextDefaultPriority = $nextFreeDefault; + } + + krsort($this->_helpersByPriority); // always make sure priority and LIFO are both enforced + return $this; + } + + /** + * offsetUnset() + * + * @param int|string $priorityOrHelperName Priority integer or the helper name + * @return Zend_Controller_Action_HelperBroker_PriorityStack + */ + public function offsetUnset($priorityOrHelperName) + { + if (!$this->offsetExists($priorityOrHelperName)) { + require_once 'Zend/Controller/Action/Exception.php'; + throw new Zend_Controller_Action_Exception('A helper with priority or name ' . $priorityOrHelperName . ' does not exist.'); + } + + if (is_string($priorityOrHelperName)) { + $helperName = $priorityOrHelperName; + $helper = $this->_helpersByNameRef[$helperName]; + $priority = array_search($helper, $this->_helpersByPriority, true); + } else { + $priority = $priorityOrHelperName; + $helperName = $this->_helpersByPriority[$priorityOrHelperName]->getName(); + } + + unset($this->_helpersByNameRef[$helperName]); + unset($this->_helpersByPriority[$priority]); + return $this; + } + + /** + * return the count of helpers + * + * @return int + */ + public function count() + { + return count($this->_helpersByPriority); + } + + /** + * Find the next free higher priority. If an index is given, it will + * find the next free highest priority after it. + * + * @param int $indexPriority OPTIONAL + * @return int + */ + public function getNextFreeHigherPriority($indexPriority = null) + { + if ($indexPriority == null) { + $indexPriority = $this->_nextDefaultPriority; + } + + $priorities = array_keys($this->_helpersByPriority); + + while (in_array($indexPriority, $priorities)) { + $indexPriority++; + } + + return $indexPriority; + } + + /** + * Find the next free lower priority. If an index is given, it will + * find the next free lower priority before it. + * + * @param int $indexPriority + * @return int + */ + public function getNextFreeLowerPriority($indexPriority = null) + { + if ($indexPriority == null) { + $indexPriority = $this->_nextDefaultPriority; + } + + $priorities = array_keys($this->_helpersByPriority); + + while (in_array($indexPriority, $priorities)) { + $indexPriority--; + } + + return $indexPriority; + } + + /** + * return the highest priority + * + * @return int + */ + public function getHighestPriority() + { + return max(array_keys($this->_helpersByPriority)); + } + + /** + * return the lowest priority + * + * @return int + */ + public function getLowestPriority() + { + return min(array_keys($this->_helpersByPriority)); + } + + /** + * return the helpers referenced by name + * + * @return array + */ + public function getHelpersByName() + { + return $this->_helpersByNameRef; + } + +} diff --git a/library/Zend/Controller/Action/Interface.php b/library/Zend/Controller/Action/Interface.php new file mode 100644 index 00000000..5810c016 --- /dev/null +++ b/library/Zend/Controller/Action/Interface.php @@ -0,0 +1,69 @@ +setParams($params); + } + + /** + * Formats a string into a controller name. This is used to take a raw + * controller name, such as one stored inside a Zend_Controller_Request_Abstract + * object, and reformat it to a proper class name that a class extending + * Zend_Controller_Action would use. + * + * @param string $unformatted + * @return string + */ + public function formatControllerName($unformatted) + { + return ucfirst($this->_formatName($unformatted)) . 'Controller'; + } + + /** + * Formats a string into an action name. This is used to take a raw + * action name, such as one that would be stored inside a Zend_Controller_Request_Abstract + * object, and reformat into a proper method name that would be found + * inside a class extending Zend_Controller_Action. + * + * @param string $unformatted + * @return string + */ + public function formatActionName($unformatted) + { + $formatted = $this->_formatName($unformatted, true); + return strtolower(substr($formatted, 0, 1)) . substr($formatted, 1) . 'Action'; + } + + /** + * Verify delimiter + * + * Verify a delimiter to use in controllers or actions. May be a single + * string or an array of strings. + * + * @param string|array $spec + * @return array + * @throws Zend_Controller_Dispatcher_Exception with invalid delimiters + */ + public function _verifyDelimiter($spec) + { + if (is_string($spec)) { + return (array) $spec; + } elseif (is_array($spec)) { + $allStrings = true; + foreach ($spec as $delim) { + if (!is_string($delim)) { + $allStrings = false; + break; + } + } + + if (!$allStrings) { + require_once 'Zend/Controller/Dispatcher/Exception.php'; + throw new Zend_Controller_Dispatcher_Exception('Word delimiter array must contain only strings'); + } + + return $spec; + } + + require_once 'Zend/Controller/Dispatcher/Exception.php'; + throw new Zend_Controller_Dispatcher_Exception('Invalid word delimiter'); + } + + /** + * Retrieve the word delimiter character(s) used in + * controller or action names + * + * @return array + */ + public function getWordDelimiter() + { + return $this->_wordDelimiter; + } + + /** + * Set word delimiter + * + * Set the word delimiter to use in controllers and actions. May be a + * single string or an array of strings. + * + * @param string|array $spec + * @return Zend_Controller_Dispatcher_Abstract + */ + public function setWordDelimiter($spec) + { + $spec = $this->_verifyDelimiter($spec); + $this->_wordDelimiter = $spec; + + return $this; + } + + /** + * Retrieve the path delimiter character(s) used in + * controller names + * + * @return array + */ + public function getPathDelimiter() + { + return $this->_pathDelimiter; + } + + /** + * Set path delimiter + * + * Set the path delimiter to use in controllers. May be a single string or + * an array of strings. + * + * @param string $spec + * @return Zend_Controller_Dispatcher_Abstract + */ + public function setPathDelimiter($spec) + { + if (!is_string($spec)) { + require_once 'Zend/Controller/Dispatcher/Exception.php'; + throw new Zend_Controller_Dispatcher_Exception('Invalid path delimiter'); + } + $this->_pathDelimiter = $spec; + + return $this; + } + + /** + * Formats a string from a URI into a PHP-friendly name. + * + * By default, replaces words separated by the word separator character(s) + * with camelCaps. If $isAction is false, it also preserves replaces words + * separated by the path separation character with an underscore, making + * the following word Title cased. All non-alphanumeric characters are + * removed. + * + * @param string $unformatted + * @param boolean $isAction Defaults to false + * @return string + */ + protected function _formatName($unformatted, $isAction = false) + { + // preserve directories + if (!$isAction) { + $segments = explode($this->getPathDelimiter(), $unformatted); + } else { + $segments = (array) $unformatted; + } + + foreach ($segments as $key => $segment) { + $segment = str_replace($this->getWordDelimiter(), ' ', strtolower($segment)); + $segment = preg_replace('/[^a-z0-9 ]/', '', $segment); + $segments[$key] = str_replace(' ', '', ucwords($segment)); + } + + return implode('_', $segments); + } + + /** + * Retrieve front controller instance + * + * @return Zend_Controller_Front + */ + public function getFrontController() + { + if (null === $this->_frontController) { + require_once 'Zend/Controller/Front.php'; + $this->_frontController = Zend_Controller_Front::getInstance(); + } + + return $this->_frontController; + } + + /** + * Set front controller instance + * + * @param Zend_Controller_Front $controller + * @return Zend_Controller_Dispatcher_Abstract + */ + public function setFrontController(Zend_Controller_Front $controller) + { + $this->_frontController = $controller; + return $this; + } + + /** + * Add or modify a parameter to use when instantiating an action controller + * + * @param string $name + * @param mixed $value + * @return Zend_Controller_Dispatcher_Abstract + */ + public function setParam($name, $value) + { + $name = (string) $name; + $this->_invokeParams[$name] = $value; + return $this; + } + + /** + * Set parameters to pass to action controller constructors + * + * @param array $params + * @return Zend_Controller_Dispatcher_Abstract + */ + public function setParams(array $params) + { + $this->_invokeParams = array_merge($this->_invokeParams, $params); + return $this; + } + + /** + * Retrieve a single parameter from the controller parameter stack + * + * @param string $name + * @return mixed + */ + public function getParam($name) + { + if(isset($this->_invokeParams[$name])) { + return $this->_invokeParams[$name]; + } + + return null; + } + + /** + * Retrieve action controller instantiation parameters + * + * @return array + */ + public function getParams() + { + return $this->_invokeParams; + } + + /** + * Clear the controller parameter stack + * + * By default, clears all parameters. If a parameter name is given, clears + * only that parameter; if an array of parameter names is provided, clears + * each. + * + * @param null|string|array single key or array of keys for params to clear + * @return Zend_Controller_Dispatcher_Abstract + */ + public function clearParams($name = null) + { + if (null === $name) { + $this->_invokeParams = array(); + } elseif (is_string($name) && isset($this->_invokeParams[$name])) { + unset($this->_invokeParams[$name]); + } elseif (is_array($name)) { + foreach ($name as $key) { + if (is_string($key) && isset($this->_invokeParams[$key])) { + unset($this->_invokeParams[$key]); + } + } + } + + return $this; + } + + /** + * Set response object to pass to action controllers + * + * @param Zend_Controller_Response_Abstract|null $response + * @return Zend_Controller_Dispatcher_Abstract + */ + public function setResponse(Zend_Controller_Response_Abstract $response = null) + { + $this->_response = $response; + return $this; + } + + /** + * Return the registered response object + * + * @return Zend_Controller_Response_Abstract|null + */ + public function getResponse() + { + return $this->_response; + } + + /** + * Set the default controller (minus any formatting) + * + * @param string $controller + * @return Zend_Controller_Dispatcher_Abstract + */ + public function setDefaultControllerName($controller) + { + $this->_defaultController = (string) $controller; + return $this; + } + + /** + * Retrieve the default controller name (minus formatting) + * + * @return string + */ + public function getDefaultControllerName() + { + return $this->_defaultController; + } + + /** + * Set the default action (minus any formatting) + * + * @param string $action + * @return Zend_Controller_Dispatcher_Abstract + */ + public function setDefaultAction($action) + { + $this->_defaultAction = (string) $action; + return $this; + } + + /** + * Retrieve the default action name (minus formatting) + * + * @return string + */ + public function getDefaultAction() + { + return $this->_defaultAction; + } + + /** + * Set the default module + * + * @param string $module + * @return Zend_Controller_Dispatcher_Abstract + */ + public function setDefaultModule($module) + { + $this->_defaultModule = (string) $module; + return $this; + } + + /** + * Retrieve the default module + * + * @return string + */ + public function getDefaultModule() + { + return $this->_defaultModule; + } +} diff --git a/library/Zend/Controller/Dispatcher/Exception.php b/library/Zend/Controller/Dispatcher/Exception.php new file mode 100644 index 00000000..46f5345d --- /dev/null +++ b/library/Zend/Controller/Dispatcher/Exception.php @@ -0,0 +1,37 @@ +_curModule = $this->getDefaultModule(); + } + + /** + * Add a single path to the controller directory stack + * + * @param string $path + * @param string $module + * @return Zend_Controller_Dispatcher_Standard + */ + public function addControllerDirectory($path, $module = null) + { + if (null === $module) { + $module = $this->_defaultModule; + } + + $module = (string) $module; + $path = rtrim((string) $path, '/\\'); + + $this->_controllerDirectory[$module] = $path; + return $this; + } + + /** + * Set controller directory + * + * @param array|string $directory + * @return Zend_Controller_Dispatcher_Standard + */ + public function setControllerDirectory($directory, $module = null) + { + $this->_controllerDirectory = array(); + + if (is_string($directory)) { + $this->addControllerDirectory($directory, $module); + } elseif (is_array($directory)) { + foreach ((array) $directory as $module => $path) { + $this->addControllerDirectory($path, $module); + } + } else { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Controller directory spec must be either a string or an array'); + } + + return $this; + } + + /** + * Return the currently set directories for Zend_Controller_Action class + * lookup + * + * If a module is specified, returns just that directory. + * + * @param string $module Module name + * @return array|string Returns array of all directories by default, single + * module directory if module argument provided + */ + public function getControllerDirectory($module = null) + { + if (null === $module) { + return $this->_controllerDirectory; + } + + $module = (string) $module; + if (array_key_exists($module, $this->_controllerDirectory)) { + return $this->_controllerDirectory[$module]; + } + + return null; + } + + /** + * Remove a controller directory by module name + * + * @param string $module + * @return bool + */ + public function removeControllerDirectory($module) + { + $module = (string) $module; + if (array_key_exists($module, $this->_controllerDirectory)) { + unset($this->_controllerDirectory[$module]); + return true; + } + return false; + } + + /** + * Format the module name. + * + * @param string $unformatted + * @return string + */ + public function formatModuleName($unformatted) + { + if (($this->_defaultModule == $unformatted) && !$this->getParam('prefixDefaultModule')) { + return $unformatted; + } + + return ucfirst($this->_formatName($unformatted)); + } + + /** + * Format action class name + * + * @param string $moduleName Name of the current module + * @param string $className Name of the action class + * @return string Formatted class name + */ + public function formatClassName($moduleName, $className) + { + return $this->formatModuleName($moduleName) . '_' . $className; + } + + /** + * Convert a class name to a filename + * + * @param string $class + * @return string + */ + public function classToFilename($class) + { + return str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php'; + } + + /** + * Returns TRUE if the Zend_Controller_Request_Abstract object can be + * dispatched to a controller. + * + * Use this method wisely. By default, the dispatcher will fall back to the + * default controller (either in the module specified or the global default) + * if a given controller does not exist. This method returning false does + * not necessarily indicate the dispatcher will not still dispatch the call. + * + * @param Zend_Controller_Request_Abstract $action + * @return boolean + */ + public function isDispatchable(Zend_Controller_Request_Abstract $request) + { + $className = $this->getControllerClass($request); + if (!$className) { + return false; + } + + $finalClass = $className; + if (($this->_defaultModule != $this->_curModule) + || $this->getParam('prefixDefaultModule')) + { + $finalClass = $this->formatClassName($this->_curModule, $className); + } + if (class_exists($finalClass, false)) { + return true; + } + + $fileSpec = $this->classToFilename($className); + $dispatchDir = $this->getDispatchDirectory(); + $test = $dispatchDir . DIRECTORY_SEPARATOR . $fileSpec; + return Zend_Loader::isReadable($test); + } + + /** + * Dispatch to a controller/action + * + * By default, if a controller is not dispatchable, dispatch() will throw + * an exception. If you wish to use the default controller instead, set the + * param 'useDefaultControllerAlways' via {@link setParam()}. + * + * @param Zend_Controller_Request_Abstract $request + * @param Zend_Controller_Response_Abstract $response + * @return void + * @throws Zend_Controller_Dispatcher_Exception + */ + public function dispatch(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response) + { + $this->setResponse($response); + + /** + * Get controller class + */ + if (!$this->isDispatchable($request)) { + $controller = $request->getControllerName(); + if (!$this->getParam('useDefaultControllerAlways') && !empty($controller)) { + require_once 'Zend/Controller/Dispatcher/Exception.php'; + throw new Zend_Controller_Dispatcher_Exception('Invalid controller specified (' . $request->getControllerName() . ')'); + } + + $className = $this->getDefaultControllerClass($request); + } else { + $className = $this->getControllerClass($request); + if (!$className) { + $className = $this->getDefaultControllerClass($request); + } + } + + /** + * Load the controller class file + */ + $className = $this->loadClass($className); + + /** + * Instantiate controller with request, response, and invocation + * arguments; throw exception if it's not an action controller + */ + $controller = new $className($request, $this->getResponse(), $this->getParams()); + if (!($controller instanceof Zend_Controller_Action_Interface) && + !($controller instanceof Zend_Controller_Action)) { + require_once 'Zend/Controller/Dispatcher/Exception.php'; + throw new Zend_Controller_Dispatcher_Exception( + 'Controller "' . $className . '" is not an instance of Zend_Controller_Action_Interface' + ); + } + + /** + * Retrieve the action name + */ + $action = $this->getActionMethod($request); + + /** + * Dispatch the method call + */ + $request->setDispatched(true); + + // by default, buffer output + $disableOb = $this->getParam('disableOutputBuffering'); + $obLevel = ob_get_level(); + if (empty($disableOb)) { + ob_start(); + } + + try { + $controller->dispatch($action); + } catch (Exception $e) { + // Clean output buffer on error + $curObLevel = ob_get_level(); + if ($curObLevel > $obLevel) { + do { + ob_get_clean(); + $curObLevel = ob_get_level(); + } while ($curObLevel > $obLevel); + } + throw $e; + } + + if (empty($disableOb)) { + $content = ob_get_clean(); + $response->appendBody($content); + } + + // Destroy the page controller instance and reflection objects + $controller = null; + } + + /** + * Load a controller class + * + * Attempts to load the controller class file from + * {@link getControllerDirectory()}. If the controller belongs to a + * module, looks for the module prefix to the controller class. + * + * @param string $className + * @return string Class name loaded + * @throws Zend_Controller_Dispatcher_Exception if class not loaded + */ + public function loadClass($className) + { + $finalClass = $className; + if (($this->_defaultModule != $this->_curModule) + || $this->getParam('prefixDefaultModule')) + { + $finalClass = $this->formatClassName($this->_curModule, $className); + } + if (class_exists($finalClass, false)) { + return $finalClass; + } + + $dispatchDir = $this->getDispatchDirectory(); + $loadFile = $dispatchDir . DIRECTORY_SEPARATOR . $this->classToFilename($className); + + if (Zend_Loader::isReadable($loadFile)) { + include_once $loadFile; + } else { + require_once 'Zend/Controller/Dispatcher/Exception.php'; + throw new Zend_Controller_Dispatcher_Exception('Cannot load controller class "' . $className . '" from file "' . $loadFile . "'"); + } + + if (!class_exists($finalClass, false)) { + require_once 'Zend/Controller/Dispatcher/Exception.php'; + throw new Zend_Controller_Dispatcher_Exception('Invalid controller class ("' . $finalClass . '")'); + } + + return $finalClass; + } + + /** + * Get controller class name + * + * Try request first; if not found, try pulling from request parameter; + * if still not found, fallback to default + * + * @param Zend_Controller_Request_Abstract $request + * @return string|false Returns class name on success + */ + public function getControllerClass(Zend_Controller_Request_Abstract $request) + { + $controllerName = $request->getControllerName(); + if (empty($controllerName)) { + if (!$this->getParam('useDefaultControllerAlways')) { + return false; + } + $controllerName = $this->getDefaultControllerName(); + $request->setControllerName($controllerName); + } + + $className = $this->formatControllerName($controllerName); + + $controllerDirs = $this->getControllerDirectory(); + $module = $request->getModuleName(); + if ($this->isValidModule($module)) { + $this->_curModule = $module; + $this->_curDirectory = $controllerDirs[$module]; + } elseif ($this->isValidModule($this->_defaultModule)) { + $request->setModuleName($this->_defaultModule); + $this->_curModule = $this->_defaultModule; + $this->_curDirectory = $controllerDirs[$this->_defaultModule]; + } else { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('No default module defined for this application'); + } + + return $className; + } + + /** + * Determine if a given module is valid + * + * @param string $module + * @return bool + */ + public function isValidModule($module) + { + if (!is_string($module)) { + return false; + } + + $module = strtolower($module); + $controllerDir = $this->getControllerDirectory(); + foreach (array_keys($controllerDir) as $moduleName) { + if ($module == strtolower($moduleName)) { + return true; + } + } + + return false; + } + + /** + * Retrieve default controller class + * + * Determines whether the default controller to use lies within the + * requested module, or if the global default should be used. + * + * By default, will only use the module default unless that controller does + * not exist; if this is the case, it falls back to the default controller + * in the default module. + * + * @param Zend_Controller_Request_Abstract $request + * @return string + */ + public function getDefaultControllerClass(Zend_Controller_Request_Abstract $request) + { + $controller = $this->getDefaultControllerName(); + $default = $this->formatControllerName($controller); + $request->setControllerName($controller) + ->setActionName(null); + + $module = $request->getModuleName(); + $controllerDirs = $this->getControllerDirectory(); + $this->_curModule = $this->_defaultModule; + $this->_curDirectory = $controllerDirs[$this->_defaultModule]; + if ($this->isValidModule($module)) { + $found = false; + if (class_exists($default, false)) { + $found = true; + } else { + $moduleDir = $controllerDirs[$module]; + $fileSpec = $moduleDir . DIRECTORY_SEPARATOR . $this->classToFilename($default); + if (Zend_Loader::isReadable($fileSpec)) { + $found = true; + $this->_curDirectory = $moduleDir; + } + } + if ($found) { + $request->setModuleName($module); + $this->_curModule = $this->formatModuleName($module); + } + } else { + $request->setModuleName($this->_defaultModule); + } + + return $default; + } + + /** + * Return the value of the currently selected dispatch directory (as set by + * {@link getController()}) + * + * @return string + */ + public function getDispatchDirectory() + { + return $this->_curDirectory; + } + + /** + * Determine the action name + * + * First attempt to retrieve from request; then from request params + * using action key; default to default action + * + * Returns formatted action name + * + * @param Zend_Controller_Request_Abstract $request + * @return string + */ + public function getActionMethod(Zend_Controller_Request_Abstract $request) + { + $action = $request->getActionName(); + if (empty($action)) { + $action = $this->getDefaultAction(); + $request->setActionName($action); + } + + return $this->formatActionName($action); + } +} diff --git a/library/Zend/Controller/Exception.php b/library/Zend/Controller/Exception.php new file mode 100644 index 00000000..43e981bc --- /dev/null +++ b/library/Zend/Controller/Exception.php @@ -0,0 +1,35 @@ +_plugins = new Zend_Controller_Plugin_Broker(); + } + + /** + * Enforce singleton; disallow cloning + * + * @return void + */ + private function __clone() + { + } + + /** + * Singleton instance + * + * @return Zend_Controller_Front + */ + public static function getInstance() + { + if (null === self::$_instance) { + self::$_instance = new self(); + } + + return self::$_instance; + } + + /** + * Resets all object properties of the singleton instance + * + * Primarily used for testing; could be used to chain front controllers. + * + * Also resets action helper broker, clearing all registered helpers. + * + * @return void + */ + public function resetInstance() + { + $reflection = new ReflectionObject($this); + foreach ($reflection->getProperties() as $property) { + $name = $property->getName(); + switch ($name) { + case '_instance': + break; + case '_controllerDir': + case '_invokeParams': + $this->{$name} = array(); + break; + case '_plugins': + $this->{$name} = new Zend_Controller_Plugin_Broker(); + break; + case '_throwExceptions': + case '_returnResponse': + $this->{$name} = false; + break; + case '_moduleControllerDirectoryName': + $this->{$name} = 'controllers'; + break; + default: + $this->{$name} = null; + break; + } + } + Zend_Controller_Action_HelperBroker::resetHelpers(); + } + + /** + * Convenience feature, calls setControllerDirectory()->setRouter()->dispatch() + * + * In PHP 5.1.x, a call to a static method never populates $this -- so run() + * may actually be called after setting up your front controller. + * + * @param string|array $controllerDirectory Path to Zend_Controller_Action + * controller classes or array of such paths + * @return void + * @throws Zend_Controller_Exception if called from an object instance + */ + public static function run($controllerDirectory) + { + self::getInstance() + ->setControllerDirectory($controllerDirectory) + ->dispatch(); + } + + /** + * Add a controller directory to the controller directory stack + * + * If $args is presented and is a string, uses it for the array key mapping + * to the directory specified. + * + * @param string $directory + * @param string $module Optional argument; module with which to associate directory. If none provided, assumes 'default' + * @return Zend_Controller_Front + * @throws Zend_Controller_Exception if directory not found or readable + */ + public function addControllerDirectory($directory, $module = null) + { + $this->getDispatcher()->addControllerDirectory($directory, $module); + return $this; + } + + /** + * Set controller directory + * + * Stores controller directory(ies) in dispatcher. May be an array of + * directories or a string containing a single directory. + * + * @param string|array $directory Path to Zend_Controller_Action controller + * classes or array of such paths + * @param string $module Optional module name to use with string $directory + * @return Zend_Controller_Front + */ + public function setControllerDirectory($directory, $module = null) + { + $this->getDispatcher()->setControllerDirectory($directory, $module); + return $this; + } + + /** + * Retrieve controller directory + * + * Retrieves: + * - Array of all controller directories if no $name passed + * - String path if $name passed and exists as a key in controller directory array + * - null if $name passed but does not exist in controller directory keys + * + * @param string $name Default null + * @return array|string|null + */ + public function getControllerDirectory($name = null) + { + return $this->getDispatcher()->getControllerDirectory($name); + } + + /** + * Remove a controller directory by module name + * + * @param string $module + * @return bool + */ + public function removeControllerDirectory($module) + { + return $this->getDispatcher()->removeControllerDirectory($module); + } + + /** + * Specify a directory as containing modules + * + * Iterates through the directory, adding any subdirectories as modules; + * the subdirectory within each module named after {@link $_moduleControllerDirectoryName} + * will be used as the controller directory path. + * + * @param string $path + * @return Zend_Controller_Front + */ + public function addModuleDirectory($path) + { + try{ + $dir = new DirectoryIterator($path); + } catch(Exception $e) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception("Directory $path not readable", 0, $e); + } + foreach ($dir as $file) { + if ($file->isDot() || !$file->isDir()) { + continue; + } + + $module = $file->getFilename(); + + // Don't use SCCS directories as modules + if (preg_match('/^[^a-z]/i', $module) || ('CVS' == $module)) { + continue; + } + + $moduleDir = $file->getPathname() . DIRECTORY_SEPARATOR . $this->getModuleControllerDirectoryName(); + $this->addControllerDirectory($moduleDir, $module); + } + + return $this; + } + + /** + * Return the path to a module directory (but not the controllers directory within) + * + * @param string $module + * @return string|null + */ + public function getModuleDirectory($module = null) + { + if (null === $module) { + $request = $this->getRequest(); + if (null !== $request) { + $module = $this->getRequest()->getModuleName(); + } + if (empty($module)) { + $module = $this->getDispatcher()->getDefaultModule(); + } + } + + $controllerDir = $this->getControllerDirectory($module); + + if ((null === $controllerDir) || !is_string($controllerDir)) { + return null; + } + + return dirname($controllerDir); + } + + /** + * Set the directory name within a module containing controllers + * + * @param string $name + * @return Zend_Controller_Front + */ + public function setModuleControllerDirectoryName($name = 'controllers') + { + $this->_moduleControllerDirectoryName = (string) $name; + + return $this; + } + + /** + * Return the directory name within a module containing controllers + * + * @return string + */ + public function getModuleControllerDirectoryName() + { + return $this->_moduleControllerDirectoryName; + } + + /** + * Set the default controller (unformatted string) + * + * @param string $controller + * @return Zend_Controller_Front + */ + public function setDefaultControllerName($controller) + { + $dispatcher = $this->getDispatcher(); + $dispatcher->setDefaultControllerName($controller); + return $this; + } + + /** + * Retrieve the default controller (unformatted string) + * + * @return string + */ + public function getDefaultControllerName() + { + return $this->getDispatcher()->getDefaultControllerName(); + } + + /** + * Set the default action (unformatted string) + * + * @param string $action + * @return Zend_Controller_Front + */ + public function setDefaultAction($action) + { + $dispatcher = $this->getDispatcher(); + $dispatcher->setDefaultAction($action); + return $this; + } + + /** + * Retrieve the default action (unformatted string) + * + * @return string + */ + public function getDefaultAction() + { + return $this->getDispatcher()->getDefaultAction(); + } + + /** + * Set the default module name + * + * @param string $module + * @return Zend_Controller_Front + */ + public function setDefaultModule($module) + { + $dispatcher = $this->getDispatcher(); + $dispatcher->setDefaultModule($module); + return $this; + } + + /** + * Retrieve the default module + * + * @return string + */ + public function getDefaultModule() + { + return $this->getDispatcher()->getDefaultModule(); + } + + /** + * Set request class/object + * + * Set the request object. The request holds the request environment. + * + * If a class name is provided, it will instantiate it + * + * @param string|Zend_Controller_Request_Abstract $request + * @throws Zend_Controller_Exception if invalid request class + * @return Zend_Controller_Front + */ + public function setRequest($request) + { + if (is_string($request)) { + if (!class_exists($request)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($request); + } + $request = new $request(); + } + if (!$request instanceof Zend_Controller_Request_Abstract) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Invalid request class'); + } + + $this->_request = $request; + + return $this; + } + + /** + * Return the request object. + * + * @return null|Zend_Controller_Request_Abstract + */ + public function getRequest() + { + return $this->_request; + } + + /** + * Set router class/object + * + * Set the router object. The router is responsible for mapping + * the request to a controller and action. + * + * If a class name is provided, instantiates router with any parameters + * registered via {@link setParam()} or {@link setParams()}. + * + * @param string|Zend_Controller_Router_Interface $router + * @throws Zend_Controller_Exception if invalid router class + * @return Zend_Controller_Front + */ + public function setRouter($router) + { + if (is_string($router)) { + if (!class_exists($router)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($router); + } + $router = new $router(); + } + + if (!$router instanceof Zend_Controller_Router_Interface) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Invalid router class'); + } + + $router->setFrontController($this); + $this->_router = $router; + + return $this; + } + + /** + * Return the router object. + * + * Instantiates a Zend_Controller_Router_Rewrite object if no router currently set. + * + * @return Zend_Controller_Router_Interface + */ + public function getRouter() + { + if (null == $this->_router) { + require_once 'Zend/Controller/Router/Rewrite.php'; + $this->setRouter(new Zend_Controller_Router_Rewrite()); + } + + return $this->_router; + } + + /** + * Set the base URL used for requests + * + * Use to set the base URL segment of the REQUEST_URI to use when + * determining PATH_INFO, etc. Examples: + * - /admin + * - /myapp + * - /subdir/index.php + * + * Note that the URL should not include the full URI. Do not use: + * - http://example.com/admin + * - http://example.com/myapp + * - http://example.com/subdir/index.php + * + * If a null value is passed, this can be used as well for autodiscovery (default). + * + * @param string $base + * @return Zend_Controller_Front + * @throws Zend_Controller_Exception for non-string $base + */ + public function setBaseUrl($base = null) + { + if (!is_string($base) && (null !== $base)) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Rewrite base must be a string'); + } + + $this->_baseUrl = $base; + + if ((null !== ($request = $this->getRequest())) && (method_exists($request, 'setBaseUrl'))) { + $request->setBaseUrl($base); + } + + return $this; + } + + /** + * Retrieve the currently set base URL + * + * @return string + */ + public function getBaseUrl() + { + $request = $this->getRequest(); + if ((null !== $request) && method_exists($request, 'getBaseUrl')) { + return $request->getBaseUrl(); + } + + return $this->_baseUrl; + } + + /** + * Set the dispatcher object. The dispatcher is responsible for + * taking a Zend_Controller_Dispatcher_Token object, instantiating the controller, and + * call the action method of the controller. + * + * @param Zend_Controller_Dispatcher_Interface $dispatcher + * @return Zend_Controller_Front + */ + public function setDispatcher(Zend_Controller_Dispatcher_Interface $dispatcher) + { + $this->_dispatcher = $dispatcher; + return $this; + } + + /** + * Return the dispatcher object. + * + * @return Zend_Controller_Dispatcher_Interface + */ + public function getDispatcher() + { + /** + * Instantiate the default dispatcher if one was not set. + */ + if (!$this->_dispatcher instanceof Zend_Controller_Dispatcher_Interface) { + require_once 'Zend/Controller/Dispatcher/Standard.php'; + $this->_dispatcher = new Zend_Controller_Dispatcher_Standard(); + } + return $this->_dispatcher; + } + + /** + * Set response class/object + * + * Set the response object. The response is a container for action + * responses and headers. Usage is optional. + * + * If a class name is provided, instantiates a response object. + * + * @param string|Zend_Controller_Response_Abstract $response + * @throws Zend_Controller_Exception if invalid response class + * @return Zend_Controller_Front + */ + public function setResponse($response) + { + if (is_string($response)) { + if (!class_exists($response)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($response); + } + $response = new $response(); + } + if (!$response instanceof Zend_Controller_Response_Abstract) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Invalid response class'); + } + + $this->_response = $response; + + return $this; + } + + /** + * Return the response object. + * + * @return null|Zend_Controller_Response_Abstract + */ + public function getResponse() + { + return $this->_response; + } + + /** + * Add or modify a parameter to use when instantiating an action controller + * + * @param string $name + * @param mixed $value + * @return Zend_Controller_Front + */ + public function setParam($name, $value) + { + $name = (string) $name; + $this->_invokeParams[$name] = $value; + return $this; + } + + /** + * Set parameters to pass to action controller constructors + * + * @param array $params + * @return Zend_Controller_Front + */ + public function setParams(array $params) + { + $this->_invokeParams = array_merge($this->_invokeParams, $params); + return $this; + } + + /** + * Retrieve a single parameter from the controller parameter stack + * + * @param string $name + * @return mixed + */ + public function getParam($name) + { + if(isset($this->_invokeParams[$name])) { + return $this->_invokeParams[$name]; + } + + return null; + } + + /** + * Retrieve action controller instantiation parameters + * + * @return array + */ + public function getParams() + { + return $this->_invokeParams; + } + + /** + * Clear the controller parameter stack + * + * By default, clears all parameters. If a parameter name is given, clears + * only that parameter; if an array of parameter names is provided, clears + * each. + * + * @param null|string|array single key or array of keys for params to clear + * @return Zend_Controller_Front + */ + public function clearParams($name = null) + { + if (null === $name) { + $this->_invokeParams = array(); + } elseif (is_string($name) && isset($this->_invokeParams[$name])) { + unset($this->_invokeParams[$name]); + } elseif (is_array($name)) { + foreach ($name as $key) { + if (is_string($key) && isset($this->_invokeParams[$key])) { + unset($this->_invokeParams[$key]); + } + } + } + + return $this; + } + + /** + * Register a plugin. + * + * @param Zend_Controller_Plugin_Abstract $plugin + * @param int $stackIndex Optional; stack index for plugin + * @return Zend_Controller_Front + */ + public function registerPlugin(Zend_Controller_Plugin_Abstract $plugin, $stackIndex = null) + { + $this->_plugins->registerPlugin($plugin, $stackIndex); + return $this; + } + + /** + * Unregister a plugin. + * + * @param string|Zend_Controller_Plugin_Abstract $plugin Plugin class or object to unregister + * @return Zend_Controller_Front + */ + public function unregisterPlugin($plugin) + { + $this->_plugins->unregisterPlugin($plugin); + return $this; + } + + /** + * Is a particular plugin registered? + * + * @param string $class + * @return bool + */ + public function hasPlugin($class) + { + return $this->_plugins->hasPlugin($class); + } + + /** + * Retrieve a plugin or plugins by class + * + * @param string $class + * @return false|Zend_Controller_Plugin_Abstract|array + */ + public function getPlugin($class) + { + return $this->_plugins->getPlugin($class); + } + + /** + * Retrieve all plugins + * + * @return array + */ + public function getPlugins() + { + return $this->_plugins->getPlugins(); + } + + /** + * Set the throwExceptions flag and retrieve current status + * + * Set whether exceptions encounted in the dispatch loop should be thrown + * or caught and trapped in the response object. + * + * Default behaviour is to trap them in the response object; call this + * method to have them thrown. + * + * Passing no value will return the current value of the flag; passing a + * boolean true or false value will set the flag and return the current + * object instance. + * + * @param boolean $flag Defaults to null (return flag state) + * @return boolean|Zend_Controller_Front Used as a setter, returns object; as a getter, returns boolean + */ + public function throwExceptions($flag = null) + { + if ($flag !== null) { + $this->_throwExceptions = (bool) $flag; + return $this; + } + + return $this->_throwExceptions; + } + + /** + * Set whether {@link dispatch()} should return the response without first + * rendering output. By default, output is rendered and dispatch() returns + * nothing. + * + * @param boolean $flag + * @return boolean|Zend_Controller_Front Used as a setter, returns object; as a getter, returns boolean + */ + public function returnResponse($flag = null) + { + if (true === $flag) { + $this->_returnResponse = true; + return $this; + } elseif (false === $flag) { + $this->_returnResponse = false; + return $this; + } + + return $this->_returnResponse; + } + + /** + * Dispatch an HTTP request to a controller/action. + * + * @param Zend_Controller_Request_Abstract|null $request + * @param Zend_Controller_Response_Abstract|null $response + * @return void|Zend_Controller_Response_Abstract Returns response object if returnResponse() is true + */ + public function dispatch(Zend_Controller_Request_Abstract $request = null, Zend_Controller_Response_Abstract $response = null) + { + if (!$this->getParam('noErrorHandler') && !$this->_plugins->hasPlugin('Zend_Controller_Plugin_ErrorHandler')) { + // Register with stack index of 100 + require_once 'Zend/Controller/Plugin/ErrorHandler.php'; + $this->_plugins->registerPlugin(new Zend_Controller_Plugin_ErrorHandler(), 100); + } + + if (!$this->getParam('noViewRenderer') && !Zend_Controller_Action_HelperBroker::hasHelper('viewRenderer')) { + require_once 'Zend/Controller/Action/Helper/ViewRenderer.php'; + Zend_Controller_Action_HelperBroker::getStack()->offsetSet(-80, new Zend_Controller_Action_Helper_ViewRenderer()); + } + + /** + * Instantiate default request object (HTTP version) if none provided + */ + if (null !== $request) { + $this->setRequest($request); + } elseif ((null === $request) && (null === ($request = $this->getRequest()))) { + require_once 'Zend/Controller/Request/Http.php'; + $request = new Zend_Controller_Request_Http(); + $this->setRequest($request); + } + + /** + * Set base URL of request object, if available + */ + if (is_callable(array($this->_request, 'setBaseUrl'))) { + if (null !== $this->_baseUrl) { + $this->_request->setBaseUrl($this->_baseUrl); + } + } + + /** + * Instantiate default response object (HTTP version) if none provided + */ + if (null !== $response) { + $this->setResponse($response); + } elseif ((null === $this->_response) && (null === ($this->_response = $this->getResponse()))) { + require_once 'Zend/Controller/Response/Http.php'; + $response = new Zend_Controller_Response_Http(); + $this->setResponse($response); + } + + /** + * Register request and response objects with plugin broker + */ + $this->_plugins + ->setRequest($this->_request) + ->setResponse($this->_response); + + /** + * Initialize router + */ + $router = $this->getRouter(); + $router->setParams($this->getParams()); + + /** + * Initialize dispatcher + */ + $dispatcher = $this->getDispatcher(); + $dispatcher->setParams($this->getParams()) + ->setResponse($this->_response); + + // Begin dispatch + try { + /** + * Route request to controller/action, if a router is provided + */ + + /** + * Notify plugins of router startup + */ + $this->_plugins->routeStartup($this->_request); + + try { + $router->route($this->_request); + } catch (Exception $e) { + if ($this->throwExceptions()) { + throw $e; + } + + $this->_response->setException($e); + } + + /** + * Notify plugins of router completion + */ + $this->_plugins->routeShutdown($this->_request); + + /** + * Notify plugins of dispatch loop startup + */ + $this->_plugins->dispatchLoopStartup($this->_request); + + /** + * Attempt to dispatch the controller/action. If the $this->_request + * indicates that it needs to be dispatched, move to the next + * action in the request. + */ + do { + $this->_request->setDispatched(true); + + /** + * Notify plugins of dispatch startup + */ + $this->_plugins->preDispatch($this->_request); + + /** + * Skip requested action if preDispatch() has reset it + */ + if (!$this->_request->isDispatched()) { + continue; + } + + /** + * Dispatch request + */ + try { + $dispatcher->dispatch($this->_request, $this->_response); + } catch (Exception $e) { + if ($this->throwExceptions()) { + throw $e; + } + $this->_response->setException($e); + } + + /** + * Notify plugins of dispatch completion + */ + $this->_plugins->postDispatch($this->_request); + } while (!$this->_request->isDispatched()); + } catch (Exception $e) { + if ($this->throwExceptions()) { + throw $e; + } + + $this->_response->setException($e); + } + + /** + * Notify plugins of dispatch loop completion + */ + try { + $this->_plugins->dispatchLoopShutdown(); + } catch (Exception $e) { + if ($this->throwExceptions()) { + throw $e; + } + + $this->_response->setException($e); + } + + if ($this->returnResponse()) { + return $this->_response; + } + + $this->_response->sendResponse(); + } +} diff --git a/library/Zend/Controller/Plugin/Abstract.php b/library/Zend/Controller/Plugin/Abstract.php new file mode 100644 index 00000000..1e05193d --- /dev/null +++ b/library/Zend/Controller/Plugin/Abstract.php @@ -0,0 +1,151 @@ +_request = $request; + return $this; + } + + /** + * Get request object + * + * @return Zend_Controller_Request_Abstract $request + */ + public function getRequest() + { + return $this->_request; + } + + /** + * Set response object + * + * @param Zend_Controller_Response_Abstract $response + * @return Zend_Controller_Plugin_Abstract + */ + public function setResponse(Zend_Controller_Response_Abstract $response) + { + $this->_response = $response; + return $this; + } + + /** + * Get response object + * + * @return Zend_Controller_Response_Abstract $response + */ + public function getResponse() + { + return $this->_response; + } + + /** + * Called before Zend_Controller_Front begins evaluating the + * request against its routes. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function routeStartup(Zend_Controller_Request_Abstract $request) + {} + + /** + * Called after Zend_Controller_Router exits. + * + * Called after Zend_Controller_Front exits from the router. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function routeShutdown(Zend_Controller_Request_Abstract $request) + {} + + /** + * Called before Zend_Controller_Front enters its dispatch loop. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request) + {} + + /** + * Called before an action is dispatched by Zend_Controller_Dispatcher. + * + * This callback allows for proxy or filter behavior. By altering the + * request and resetting its dispatched flag (via + * {@link Zend_Controller_Request_Abstract::setDispatched() setDispatched(false)}), + * the current action may be skipped. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function preDispatch(Zend_Controller_Request_Abstract $request) + {} + + /** + * Called after an action is dispatched by Zend_Controller_Dispatcher. + * + * This callback allows for proxy or filter behavior. By altering the + * request and resetting its dispatched flag (via + * {@link Zend_Controller_Request_Abstract::setDispatched() setDispatched(false)}), + * a new action may be specified for dispatching. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function postDispatch(Zend_Controller_Request_Abstract $request) + {} + + /** + * Called before Zend_Controller_Front exits its dispatch loop. + * + * @return void + */ + public function dispatchLoopShutdown() + {} +} diff --git a/library/Zend/Controller/Plugin/ActionStack.php b/library/Zend/Controller/Plugin/ActionStack.php new file mode 100644 index 00000000..39dd74e7 --- /dev/null +++ b/library/Zend/Controller/Plugin/ActionStack.php @@ -0,0 +1,280 @@ +setRegistry($registry); + + if (null !== $key) { + $this->setRegistryKey($key); + } else { + $key = $this->getRegistryKey(); + } + + $registry[$key] = array(); + } + + /** + * Set registry object + * + * @param Zend_Registry $registry + * @return Zend_Controller_Plugin_ActionStack + */ + public function setRegistry(Zend_Registry $registry) + { + $this->_registry = $registry; + return $this; + } + + /** + * Retrieve registry object + * + * @return Zend_Registry + */ + public function getRegistry() + { + return $this->_registry; + } + + /** + * Retrieve registry key + * + * @return string + */ + public function getRegistryKey() + { + return $this->_registryKey; + } + + /** + * Set registry key + * + * @param string $key + * @return Zend_Controller_Plugin_ActionStack + */ + public function setRegistryKey($key) + { + $this->_registryKey = (string) $key; + return $this; + } + + /** + * Set clearRequestParams flag + * + * @param bool $clearRequestParams + * @return Zend_Controller_Plugin_ActionStack + */ + public function setClearRequestParams($clearRequestParams) + { + $this->_clearRequestParams = (bool) $clearRequestParams; + return $this; + } + + /** + * Retrieve clearRequestParams flag + * + * @return bool + */ + public function getClearRequestParams() + { + return $this->_clearRequestParams; + } + + /** + * Retrieve action stack + * + * @return array + */ + public function getStack() + { + $registry = $this->getRegistry(); + $stack = $registry[$this->getRegistryKey()]; + return $stack; + } + + /** + * Save stack to registry + * + * @param array $stack + * @return Zend_Controller_Plugin_ActionStack + */ + protected function _saveStack(array $stack) + { + $registry = $this->getRegistry(); + $registry[$this->getRegistryKey()] = $stack; + return $this; + } + + /** + * Push an item onto the stack + * + * @param Zend_Controller_Request_Abstract $next + * @return Zend_Controller_Plugin_ActionStack + */ + public function pushStack(Zend_Controller_Request_Abstract $next) + { + $stack = $this->getStack(); + array_push($stack, $next); + return $this->_saveStack($stack); + } + + /** + * Pop an item off the action stack + * + * @return false|Zend_Controller_Request_Abstract + */ + public function popStack() + { + $stack = $this->getStack(); + if (0 == count($stack)) { + return false; + } + + $next = array_pop($stack); + $this->_saveStack($stack); + + if (!$next instanceof Zend_Controller_Request_Abstract) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('ArrayStack should only contain request objects'); + } + $action = $next->getActionName(); + if (empty($action)) { + return $this->popStack($stack); + } + + $request = $this->getRequest(); + $controller = $next->getControllerName(); + if (empty($controller)) { + $next->setControllerName($request->getControllerName()); + } + + $module = $next->getModuleName(); + if (empty($module)) { + $next->setModuleName($request->getModuleName()); + } + + return $next; + } + + /** + * postDispatch() plugin hook -- check for actions in stack, and dispatch if any found + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function postDispatch(Zend_Controller_Request_Abstract $request) + { + // Don't move on to next request if this is already an attempt to + // forward + if (!$request->isDispatched()) { + return; + } + + $this->setRequest($request); + $stack = $this->getStack(); + if (empty($stack)) { + return; + } + $next = $this->popStack(); + if (!$next) { + return; + } + + $this->forward($next); + } + + /** + * Forward request with next action + * + * @param array $next + * @return void + */ + public function forward(Zend_Controller_Request_Abstract $next) + { + $request = $this->getRequest(); + if ($this->getClearRequestParams()) { + $request->clearParams(); + } + + $request->setModuleName($next->getModuleName()) + ->setControllerName($next->getControllerName()) + ->setActionName($next->getActionName()) + ->setParams($next->getParams()) + ->setDispatched(false); + } +} diff --git a/library/Zend/Controller/Plugin/Broker.php b/library/Zend/Controller/Plugin/Broker.php new file mode 100644 index 00000000..bb2a76aa --- /dev/null +++ b/library/Zend/Controller/Plugin/Broker.php @@ -0,0 +1,365 @@ +_plugins, true)) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Plugin already registered'); + } + + $stackIndex = (int) $stackIndex; + + if ($stackIndex) { + if (isset($this->_plugins[$stackIndex])) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Plugin with stackIndex "' . $stackIndex . '" already registered'); + } + $this->_plugins[$stackIndex] = $plugin; + } else { + $stackIndex = count($this->_plugins); + while (isset($this->_plugins[$stackIndex])) { + ++$stackIndex; + } + $this->_plugins[$stackIndex] = $plugin; + } + + $request = $this->getRequest(); + if ($request) { + $this->_plugins[$stackIndex]->setRequest($request); + } + $response = $this->getResponse(); + if ($response) { + $this->_plugins[$stackIndex]->setResponse($response); + } + + ksort($this->_plugins); + + return $this; + } + + /** + * Unregister a plugin. + * + * @param string|Zend_Controller_Plugin_Abstract $plugin Plugin object or class name + * @return Zend_Controller_Plugin_Broker + */ + public function unregisterPlugin($plugin) + { + if ($plugin instanceof Zend_Controller_Plugin_Abstract) { + // Given a plugin object, find it in the array + $key = array_search($plugin, $this->_plugins, true); + if (false === $key) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Plugin never registered.'); + } + unset($this->_plugins[$key]); + } elseif (is_string($plugin)) { + // Given a plugin class, find all plugins of that class and unset them + foreach ($this->_plugins as $key => $_plugin) { + $type = get_class($_plugin); + if ($plugin == $type) { + unset($this->_plugins[$key]); + } + } + } + return $this; + } + + /** + * Is a plugin of a particular class registered? + * + * @param string $class + * @return bool + */ + public function hasPlugin($class) + { + foreach ($this->_plugins as $plugin) { + $type = get_class($plugin); + if ($class == $type) { + return true; + } + } + + return false; + } + + /** + * Retrieve a plugin or plugins by class + * + * @param string $class Class name of plugin(s) desired + * @return false|Zend_Controller_Plugin_Abstract|array Returns false if none found, plugin if only one found, and array of plugins if multiple plugins of same class found + */ + public function getPlugin($class) + { + $found = array(); + foreach ($this->_plugins as $plugin) { + $type = get_class($plugin); + if ($class == $type) { + $found[] = $plugin; + } + } + + switch (count($found)) { + case 0: + return false; + case 1: + return $found[0]; + default: + return $found; + } + } + + /** + * Retrieve all plugins + * + * @return array + */ + public function getPlugins() + { + return $this->_plugins; + } + + /** + * Set request object, and register with each plugin + * + * @param Zend_Controller_Request_Abstract $request + * @return Zend_Controller_Plugin_Broker + */ + public function setRequest(Zend_Controller_Request_Abstract $request) + { + $this->_request = $request; + + foreach ($this->_plugins as $plugin) { + $plugin->setRequest($request); + } + + return $this; + } + + /** + * Get request object + * + * @return Zend_Controller_Request_Abstract $request + */ + public function getRequest() + { + return $this->_request; + } + + /** + * Set response object + * + * @param Zend_Controller_Response_Abstract $response + * @return Zend_Controller_Plugin_Broker + */ + public function setResponse(Zend_Controller_Response_Abstract $response) + { + $this->_response = $response; + + foreach ($this->_plugins as $plugin) { + $plugin->setResponse($response); + } + + + return $this; + } + + /** + * Get response object + * + * @return Zend_Controller_Response_Abstract $response + */ + public function getResponse() + { + return $this->_response; + } + + + /** + * Called before Zend_Controller_Front begins evaluating the + * request against its routes. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function routeStartup(Zend_Controller_Request_Abstract $request) + { + foreach ($this->_plugins as $plugin) { + try { + $plugin->routeStartup($request); + } catch (Exception $e) { + if (Zend_Controller_Front::getInstance()->throwExceptions()) { + throw new Zend_Controller_Exception($e->getMessage() . $e->getTraceAsString(), $e->getCode(), $e); + } else { + $this->getResponse()->setException($e); + } + } + } + } + + + /** + * Called before Zend_Controller_Front exits its iterations over + * the route set. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function routeShutdown(Zend_Controller_Request_Abstract $request) + { + foreach ($this->_plugins as $plugin) { + try { + $plugin->routeShutdown($request); + } catch (Exception $e) { + if (Zend_Controller_Front::getInstance()->throwExceptions()) { + throw new Zend_Controller_Exception($e->getMessage() . $e->getTraceAsString(), $e->getCode(), $e); + } else { + $this->getResponse()->setException($e); + } + } + } + } + + + /** + * Called before Zend_Controller_Front enters its dispatch loop. + * + * During the dispatch loop, Zend_Controller_Front keeps a + * Zend_Controller_Request_Abstract object, and uses + * Zend_Controller_Dispatcher to dispatch the + * Zend_Controller_Request_Abstract object to controllers/actions. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request) + { + foreach ($this->_plugins as $plugin) { + try { + $plugin->dispatchLoopStartup($request); + } catch (Exception $e) { + if (Zend_Controller_Front::getInstance()->throwExceptions()) { + throw new Zend_Controller_Exception($e->getMessage() . $e->getTraceAsString(), $e->getCode(), $e); + } else { + $this->getResponse()->setException($e); + } + } + } + } + + + /** + * Called before an action is dispatched by Zend_Controller_Dispatcher. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function preDispatch(Zend_Controller_Request_Abstract $request) + { + foreach ($this->_plugins as $plugin) { + try { + $plugin->preDispatch($request); + } catch (Exception $e) { + if (Zend_Controller_Front::getInstance()->throwExceptions()) { + throw new Zend_Controller_Exception($e->getMessage() . $e->getTraceAsString(), $e->getCode(), $e); + } else { + $this->getResponse()->setException($e); + // skip rendering of normal dispatch give the error handler a try + $this->getRequest()->setDispatched(false); + } + } + } + } + + + /** + * Called after an action is dispatched by Zend_Controller_Dispatcher. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function postDispatch(Zend_Controller_Request_Abstract $request) + { + foreach ($this->_plugins as $plugin) { + try { + $plugin->postDispatch($request); + } catch (Exception $e) { + if (Zend_Controller_Front::getInstance()->throwExceptions()) { + throw new Zend_Controller_Exception($e->getMessage() . $e->getTraceAsString(), $e->getCode(), $e); + } else { + $this->getResponse()->setException($e); + } + } + } + } + + + /** + * Called before Zend_Controller_Front exits its dispatch loop. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + public function dispatchLoopShutdown() + { + foreach ($this->_plugins as $plugin) { + try { + $plugin->dispatchLoopShutdown(); + } catch (Exception $e) { + if (Zend_Controller_Front::getInstance()->throwExceptions()) { + throw new Zend_Controller_Exception($e->getMessage() . $e->getTraceAsString(), $e->getCode(), $e); + } else { + $this->getResponse()->setException($e); + } + } + } + } +} diff --git a/library/Zend/Controller/Plugin/ErrorHandler.php b/library/Zend/Controller/Plugin/ErrorHandler.php new file mode 100644 index 00000000..2f462f65 --- /dev/null +++ b/library/Zend/Controller/Plugin/ErrorHandler.php @@ -0,0 +1,300 @@ +setErrorHandler($options); + } + + /** + * setErrorHandler() - setup the error handling options + * + * @param array $options + * @return Zend_Controller_Plugin_ErrorHandler + */ + public function setErrorHandler(Array $options = array()) + { + if (isset($options['module'])) { + $this->setErrorHandlerModule($options['module']); + } + if (isset($options['controller'])) { + $this->setErrorHandlerController($options['controller']); + } + if (isset($options['action'])) { + $this->setErrorHandlerAction($options['action']); + } + return $this; + } + + /** + * Set the module name for the error handler + * + * @param string $module + * @return Zend_Controller_Plugin_ErrorHandler + */ + public function setErrorHandlerModule($module) + { + $this->_errorModule = (string) $module; + return $this; + } + + /** + * Retrieve the current error handler module + * + * @return string + */ + public function getErrorHandlerModule() + { + if (null === $this->_errorModule) { + $this->_errorModule = Zend_Controller_Front::getInstance()->getDispatcher()->getDefaultModule(); + } + return $this->_errorModule; + } + + /** + * Set the controller name for the error handler + * + * @param string $controller + * @return Zend_Controller_Plugin_ErrorHandler + */ + public function setErrorHandlerController($controller) + { + $this->_errorController = (string) $controller; + return $this; + } + + /** + * Retrieve the current error handler controller + * + * @return string + */ + public function getErrorHandlerController() + { + return $this->_errorController; + } + + /** + * Set the action name for the error handler + * + * @param string $action + * @return Zend_Controller_Plugin_ErrorHandler + */ + public function setErrorHandlerAction($action) + { + $this->_errorAction = (string) $action; + return $this; + } + + /** + * Retrieve the current error handler action + * + * @return string + */ + public function getErrorHandlerAction() + { + return $this->_errorAction; + } + + /** + * Route shutdown hook -- Ccheck for router exceptions + * + * @param Zend_Controller_Request_Abstract $request + */ + public function routeShutdown(Zend_Controller_Request_Abstract $request) + { + $this->_handleError($request); + } + + /** + * Pre dispatch hook -- check for exceptions and dispatch error handler if + * necessary + * + * @param Zend_Controller_Request_Abstract $request + */ + public function preDispatch(Zend_Controller_Request_Abstract $request) + { + $this->_handleError($request); + } + + /** + * Post dispatch hook -- check for exceptions and dispatch error handler if + * necessary + * + * @param Zend_Controller_Request_Abstract $request + */ + public function postDispatch(Zend_Controller_Request_Abstract $request) + { + $this->_handleError($request); + } + + /** + * Handle errors and exceptions + * + * If the 'noErrorHandler' front controller flag has been set, + * returns early. + * + * @param Zend_Controller_Request_Abstract $request + * @return void + */ + protected function _handleError(Zend_Controller_Request_Abstract $request) + { + $frontController = Zend_Controller_Front::getInstance(); + if ($frontController->getParam('noErrorHandler')) { + return; + } + + $response = $this->getResponse(); + + if ($this->_isInsideErrorHandlerLoop) { + $exceptions = $response->getException(); + if (count($exceptions) > $this->_exceptionCountAtFirstEncounter) { + // Exception thrown by error handler; tell the front controller to throw it + $frontController->throwExceptions(true); + throw array_pop($exceptions); + } + } + + // check for an exception AND allow the error handler controller the option to forward + if (($response->isException()) && (!$this->_isInsideErrorHandlerLoop)) { + $this->_isInsideErrorHandlerLoop = true; + + // Get exception information + $error = new ArrayObject(array(), ArrayObject::ARRAY_AS_PROPS); + $exceptions = $response->getException(); + $exception = $exceptions[0]; + $exceptionType = get_class($exception); + $error->exception = $exception; + switch ($exceptionType) { + case 'Zend_Controller_Router_Exception': + if (404 == $exception->getCode()) { + $error->type = self::EXCEPTION_NO_ROUTE; + } else { + $error->type = self::EXCEPTION_OTHER; + } + break; + case 'Zend_Controller_Dispatcher_Exception': + $error->type = self::EXCEPTION_NO_CONTROLLER; + break; + case 'Zend_Controller_Action_Exception': + if (404 == $exception->getCode()) { + $error->type = self::EXCEPTION_NO_ACTION; + } else { + $error->type = self::EXCEPTION_OTHER; + } + break; + default: + $error->type = self::EXCEPTION_OTHER; + break; + } + + // Keep a copy of the original request + $error->request = clone $request; + + // get a count of the number of exceptions encountered + $this->_exceptionCountAtFirstEncounter = count($exceptions); + + // Forward to the error handler + $request->setParam('error_handler', $error) + ->setModuleName($this->getErrorHandlerModule()) + ->setControllerName($this->getErrorHandlerController()) + ->setActionName($this->getErrorHandlerAction()) + ->setDispatched(false); + } + } +} diff --git a/library/Zend/Controller/Plugin/PutHandler.php b/library/Zend/Controller/Plugin/PutHandler.php new file mode 100644 index 00000000..1f749a1b --- /dev/null +++ b/library/Zend/Controller/Plugin/PutHandler.php @@ -0,0 +1,60 @@ +_request->isPut()) { + $putParams = array(); + parse_str($this->_request->getRawBody(), $putParams); + $request->setParams($putParams); + } + } +} diff --git a/library/Zend/Controller/Request/Abstract.php b/library/Zend/Controller/Request/Abstract.php new file mode 100644 index 00000000..9a8e057e --- /dev/null +++ b/library/Zend/Controller/Request/Abstract.php @@ -0,0 +1,356 @@ +_module) { + $this->_module = $this->getParam($this->getModuleKey()); + } + + return $this->_module; + } + + /** + * Set the module name to use + * + * @param string $value + * @return Zend_Controller_Request_Abstract + */ + public function setModuleName($value) + { + $this->_module = $value; + return $this; + } + + /** + * Retrieve the controller name + * + * @return string + */ + public function getControllerName() + { + if (null === $this->_controller) { + $this->_controller = $this->getParam($this->getControllerKey()); + } + + return $this->_controller; + } + + /** + * Set the controller name to use + * + * @param string $value + * @return Zend_Controller_Request_Abstract + */ + public function setControllerName($value) + { + $this->_controller = $value; + return $this; + } + + /** + * Retrieve the action name + * + * @return string + */ + public function getActionName() + { + if (null === $this->_action) { + $this->_action = $this->getParam($this->getActionKey()); + } + + return $this->_action; + } + + /** + * Set the action name + * + * @param string $value + * @return Zend_Controller_Request_Abstract + */ + public function setActionName($value) + { + $this->_action = $value; + /** + * @see ZF-3465 + */ + if (null === $value) { + $this->setParam($this->getActionKey(), $value); + } + return $this; + } + + /** + * Retrieve the module key + * + * @return string + */ + public function getModuleKey() + { + return $this->_moduleKey; + } + + /** + * Set the module key + * + * @param string $key + * @return Zend_Controller_Request_Abstract + */ + public function setModuleKey($key) + { + $this->_moduleKey = (string) $key; + return $this; + } + + /** + * Retrieve the controller key + * + * @return string + */ + public function getControllerKey() + { + return $this->_controllerKey; + } + + /** + * Set the controller key + * + * @param string $key + * @return Zend_Controller_Request_Abstract + */ + public function setControllerKey($key) + { + $this->_controllerKey = (string) $key; + return $this; + } + + /** + * Retrieve the action key + * + * @return string + */ + public function getActionKey() + { + return $this->_actionKey; + } + + /** + * Set the action key + * + * @param string $key + * @return Zend_Controller_Request_Abstract + */ + public function setActionKey($key) + { + $this->_actionKey = (string) $key; + return $this; + } + + /** + * Get an action parameter + * + * @param string $key + * @param mixed $default Default value to use if key not found + * @return mixed + */ + public function getParam($key, $default = null) + { + $key = (string) $key; + if (isset($this->_params[$key])) { + return $this->_params[$key]; + } + + return $default; + } + + /** + * Retrieve only user params (i.e, any param specific to the object and not the environment) + * + * @return array + */ + public function getUserParams() + { + return $this->_params; + } + + /** + * Retrieve a single user param (i.e, a param specific to the object and not the environment) + * + * @param string $key + * @param string $default Default value to use if key not found + * @return mixed + */ + public function getUserParam($key, $default = null) + { + if (isset($this->_params[$key])) { + return $this->_params[$key]; + } + + return $default; + } + + /** + * Set an action parameter + * + * A $value of null will unset the $key if it exists + * + * @param string $key + * @param mixed $value + * @return Zend_Controller_Request_Abstract + */ + public function setParam($key, $value) + { + $key = (string) $key; + + if ((null === $value) && isset($this->_params[$key])) { + unset($this->_params[$key]); + } elseif (null !== $value) { + $this->_params[$key] = $value; + } + + return $this; + } + + /** + * Get all action parameters + * + * @return array + */ + public function getParams() + { + return $this->_params; + } + + /** + * Set action parameters en masse; does not overwrite + * + * Null values will unset the associated key. + * + * @param array $array + * @return Zend_Controller_Request_Abstract + */ + public function setParams(array $array) + { + $this->_params = $this->_params + (array) $array; + + foreach ($this->_params as $key => $value) { + if (null === $value) { + unset($this->_params[$key]); + } + } + + return $this; + } + + /** + * Unset all user parameters + * + * @return Zend_Controller_Request_Abstract + */ + public function clearParams() + { + $this->_params = array(); + return $this; + } + + /** + * Set flag indicating whether or not request has been dispatched + * + * @param boolean $flag + * @return Zend_Controller_Request_Abstract + */ + public function setDispatched($flag = true) + { + $this->_dispatched = $flag ? true : false; + return $this; + } + + /** + * Determine if the request has been dispatched + * + * @return boolean + */ + public function isDispatched() + { + return $this->_dispatched; + } +} diff --git a/library/Zend/Controller/Request/Apache404.php b/library/Zend/Controller/Request/Apache404.php new file mode 100644 index 00000000..11a5f64b --- /dev/null +++ b/library/Zend/Controller/Request/Apache404.php @@ -0,0 +1,82 @@ +_requestUri = $requestUri; + return $this; + } +} diff --git a/library/Zend/Controller/Request/Exception.php b/library/Zend/Controller/Request/Exception.php new file mode 100644 index 00000000..217b49be --- /dev/null +++ b/library/Zend/Controller/Request/Exception.php @@ -0,0 +1,37 @@ +valid()) { + $path = $uri->getPath(); + $query = $uri->getQuery(); + if (!empty($query)) { + $path .= '?' . $query; + } + + $this->setRequestUri($path); + } else { + require_once 'Zend/Controller/Request/Exception.php'; + throw new Zend_Controller_Request_Exception('Invalid URI provided to constructor'); + } + } else { + $this->setRequestUri(); + } + } + + /** + * Access values contained in the superglobals as public members + * Order of precedence: 1. GET, 2. POST, 3. COOKIE, 4. SERVER, 5. ENV + * + * @see http://msdn.microsoft.com/en-us/library/system.web.httprequest.item.aspx + * @param string $key + * @return mixed + */ + public function __get($key) + { + switch (true) { + case isset($this->_params[$key]): + return $this->_params[$key]; + case isset($_GET[$key]): + return $_GET[$key]; + case isset($_POST[$key]): + return $_POST[$key]; + case isset($_COOKIE[$key]): + return $_COOKIE[$key]; + case ($key == 'REQUEST_URI'): + return $this->getRequestUri(); + case ($key == 'PATH_INFO'): + return $this->getPathInfo(); + case isset($_SERVER[$key]): + return $_SERVER[$key]; + case isset($_ENV[$key]): + return $_ENV[$key]; + default: + return null; + } + } + + /** + * Alias to __get + * + * @param string $key + * @return mixed + */ + public function get($key) + { + return $this->__get($key); + } + + /** + * Set values + * + * In order to follow {@link __get()}, which operates on a number of + * superglobals, setting values through overloading is not allowed and will + * raise an exception. Use setParam() instead. + * + * @param string $key + * @param mixed $value + * @return void + * @throws Zend_Controller_Request_Exception + */ + public function __set($key, $value) + { + require_once 'Zend/Controller/Request/Exception.php'; + throw new Zend_Controller_Request_Exception('Setting values in superglobals not allowed; please use setParam()'); + } + + /** + * Alias to __set() + * + * @param string $key + * @param mixed $value + * @return void + */ + public function set($key, $value) + { + return $this->__set($key, $value); + } + + /** + * Check to see if a property is set + * + * @param string $key + * @return boolean + */ + public function __isset($key) + { + switch (true) { + case isset($this->_params[$key]): + return true; + case isset($_GET[$key]): + return true; + case isset($_POST[$key]): + return true; + case isset($_COOKIE[$key]): + return true; + case isset($_SERVER[$key]): + return true; + case isset($_ENV[$key]): + return true; + default: + return false; + } + } + + /** + * Alias to __isset() + * + * @param string $key + * @return boolean + */ + public function has($key) + { + return $this->__isset($key); + } + + /** + * Set GET values + * + * @param string|array $spec + * @param null|mixed $value + * @return Zend_Controller_Request_Http + */ + public function setQuery($spec, $value = null) + { + if ((null === $value) && !is_array($spec)) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Invalid value passed to setQuery(); must be either array of values or key/value pair'); + } + if ((null === $value) && is_array($spec)) { + foreach ($spec as $key => $value) { + $this->setQuery($key, $value); + } + return $this; + } + $_GET[(string) $spec] = $value; + return $this; + } + + /** + * Retrieve a member of the $_GET superglobal + * + * If no $key is passed, returns the entire $_GET array. + * + * @todo How to retrieve from nested arrays + * @param string $key + * @param mixed $default Default value to use if key not found + * @return mixed Returns null if key does not exist + */ + public function getQuery($key = null, $default = null) + { + if (null === $key) { + return $_GET; + } + + return (isset($_GET[$key])) ? $_GET[$key] : $default; + } + + /** + * Set POST values + * + * @param string|array $spec + * @param null|mixed $value + * @return Zend_Controller_Request_Http + */ + public function setPost($spec, $value = null) + { + if ((null === $value) && !is_array($spec)) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Invalid value passed to setPost(); must be either array of values or key/value pair'); + } + if ((null === $value) && is_array($spec)) { + foreach ($spec as $key => $value) { + $this->setPost($key, $value); + } + return $this; + } + $_POST[(string) $spec] = $value; + return $this; + } + + /** + * Retrieve a member of the $_POST superglobal + * + * If no $key is passed, returns the entire $_POST array. + * + * @todo How to retrieve from nested arrays + * @param string $key + * @param mixed $default Default value to use if key not found + * @return mixed Returns null if key does not exist + */ + public function getPost($key = null, $default = null) + { + if (null === $key) { + return $_POST; + } + + return (isset($_POST[$key])) ? $_POST[$key] : $default; + } + + /** + * Retrieve a member of the $_COOKIE superglobal + * + * If no $key is passed, returns the entire $_COOKIE array. + * + * @todo How to retrieve from nested arrays + * @param string $key + * @param mixed $default Default value to use if key not found + * @return mixed Returns null if key does not exist + */ + public function getCookie($key = null, $default = null) + { + if (null === $key) { + return $_COOKIE; + } + + return (isset($_COOKIE[$key])) ? $_COOKIE[$key] : $default; + } + + /** + * Retrieve a member of the $_SERVER superglobal + * + * If no $key is passed, returns the entire $_SERVER array. + * + * @param string $key + * @param mixed $default Default value to use if key not found + * @return mixed Returns null if key does not exist + */ + public function getServer($key = null, $default = null) + { + if (null === $key) { + return $_SERVER; + } + + return (isset($_SERVER[$key])) ? $_SERVER[$key] : $default; + } + + /** + * Retrieve a member of the $_ENV superglobal + * + * If no $key is passed, returns the entire $_ENV array. + * + * @param string $key + * @param mixed $default Default value to use if key not found + * @return mixed Returns null if key does not exist + */ + public function getEnv($key = null, $default = null) + { + if (null === $key) { + return $_ENV; + } + + return (isset($_ENV[$key])) ? $_ENV[$key] : $default; + } + + /** + * Set the REQUEST_URI on which the instance operates + * + * If no request URI is passed, uses the value in $_SERVER['REQUEST_URI'], + * $_SERVER['HTTP_X_REWRITE_URL'], or $_SERVER['ORIG_PATH_INFO'] + $_SERVER['QUERY_STRING']. + * + * @param string $requestUri + * @return Zend_Controller_Request_Http + */ + public function setRequestUri($requestUri = null) + { + if ($requestUri === null) { + if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { // check this first so IIS will catch + $requestUri = $_SERVER['HTTP_X_REWRITE_URL']; + } elseif ( + // IIS7 with URL Rewrite: make sure we get the unencoded url (double slash problem) + isset($_SERVER['IIS_WasUrlRewritten']) + && $_SERVER['IIS_WasUrlRewritten'] == '1' + && isset($_SERVER['UNENCODED_URL']) + && $_SERVER['UNENCODED_URL'] != '' + ) { + $requestUri = $_SERVER['UNENCODED_URL']; + } elseif (isset($_SERVER['REQUEST_URI'])) { + $requestUri = $_SERVER['REQUEST_URI']; + // Http proxy reqs setup request uri with scheme and host [and port] + the url path, only use url path + $schemeAndHttpHost = $this->getScheme() . '://' . $this->getHttpHost(); + if (strpos($requestUri, $schemeAndHttpHost) === 0) { + $requestUri = substr($requestUri, strlen($schemeAndHttpHost)); + } + } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0, PHP as CGI + $requestUri = $_SERVER['ORIG_PATH_INFO']; + if (!empty($_SERVER['QUERY_STRING'])) { + $requestUri .= '?' . $_SERVER['QUERY_STRING']; + } + } else { + return $this; + } + } elseif (!is_string($requestUri)) { + return $this; + } else { + // Set GET items, if available + if (false !== ($pos = strpos($requestUri, '?'))) { + // Get key => value pairs and set $_GET + $query = substr($requestUri, $pos + 1); + parse_str($query, $vars); + $this->setQuery($vars); + } + } + + $this->_requestUri = $requestUri; + return $this; + } + + /** + * Returns the REQUEST_URI taking into account + * platform differences between Apache and IIS + * + * @return string + */ + public function getRequestUri() + { + if (empty($this->_requestUri)) { + $this->setRequestUri(); + } + + return $this->_requestUri; + } + + /** + * Set the base URL of the request; i.e., the segment leading to the script name + * + * E.g.: + * - /admin + * - /myapp + * - /subdir/index.php + * + * Do not use the full URI when providing the base. The following are + * examples of what not to use: + * - http://example.com/admin (should be just /admin) + * - http://example.com/subdir/index.php (should be just /subdir/index.php) + * + * If no $baseUrl is provided, attempts to determine the base URL from the + * environment, using SCRIPT_FILENAME, SCRIPT_NAME, PHP_SELF, and + * ORIG_SCRIPT_NAME in its determination. + * + * @param mixed $baseUrl + * @return Zend_Controller_Request_Http + */ + public function setBaseUrl($baseUrl = null) + { + if ((null !== $baseUrl) && !is_string($baseUrl)) { + return $this; + } + + if ($baseUrl === null) { + $filename = (isset($_SERVER['SCRIPT_FILENAME'])) ? basename($_SERVER['SCRIPT_FILENAME']) : ''; + + if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $filename) { + $baseUrl = $_SERVER['SCRIPT_NAME']; + } elseif (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $filename) { + $baseUrl = $_SERVER['PHP_SELF']; + } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $filename) { + $baseUrl = $_SERVER['ORIG_SCRIPT_NAME']; // 1and1 shared hosting compatibility + } else { + // Backtrack up the script_filename to find the portion matching + // php_self + $path = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : ''; + $file = isset($_SERVER['SCRIPT_FILENAME']) ? $_SERVER['SCRIPT_FILENAME'] : ''; + $segs = explode('/', trim($file, '/')); + $segs = array_reverse($segs); + $index = 0; + $last = count($segs); + $baseUrl = ''; + do { + $seg = $segs[$index]; + $baseUrl = '/' . $seg . $baseUrl; + ++$index; + } while (($last > $index) && (false !== ($pos = strpos($path, $baseUrl))) && (0 != $pos)); + } + + // Does the baseUrl have anything in common with the request_uri? + $requestUri = $this->getRequestUri(); + + if (0 === strpos($requestUri, $baseUrl)) { + // full $baseUrl matches + $this->_baseUrl = $baseUrl; + return $this; + } + + if (0 === strpos($requestUri, dirname($baseUrl))) { + // directory portion of $baseUrl matches + $this->_baseUrl = rtrim(dirname($baseUrl), '/'); + return $this; + } + + $truncatedRequestUri = $requestUri; + if (($pos = strpos($requestUri, '?')) !== false) { + $truncatedRequestUri = substr($requestUri, 0, $pos); + } + + $basename = basename($baseUrl); + if (empty($basename) || !strpos($truncatedRequestUri, $basename)) { + // no match whatsoever; set it blank + $this->_baseUrl = ''; + return $this; + } + + // If using mod_rewrite or ISAPI_Rewrite strip the script filename + // out of baseUrl. $pos !== 0 makes sure it is not matching a value + // from PATH_INFO or QUERY_STRING + if ((strlen($requestUri) >= strlen($baseUrl)) + && ((false !== ($pos = strpos($requestUri, $baseUrl))) && ($pos !== 0))) + { + $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl)); + } + } + + $this->_baseUrl = rtrim($baseUrl, '/'); + return $this; + } + + /** + * Everything in REQUEST_URI before PATH_INFO + *
+ * + * @return string + */ + public function getBaseUrl($raw = false) + { + if (null === $this->_baseUrl) { + $this->setBaseUrl(); + } + + return (($raw == false) ? urldecode($this->_baseUrl) : $this->_baseUrl); + } + + /** + * Set the base path for the URL + * + * @param string|null $basePath + * @return Zend_Controller_Request_Http + */ + public function setBasePath($basePath = null) + { + if ($basePath === null) { + $filename = (isset($_SERVER['SCRIPT_FILENAME'])) + ? basename($_SERVER['SCRIPT_FILENAME']) + : ''; + + $baseUrl = $this->getBaseUrl(); + if (empty($baseUrl)) { + $this->_basePath = ''; + return $this; + } + + if (basename($baseUrl) === $filename) { + $basePath = dirname($baseUrl); + } else { + $basePath = $baseUrl; + } + } + + if (substr(PHP_OS, 0, 3) === 'WIN') { + $basePath = str_replace('\\', '/', $basePath); + } + + $this->_basePath = rtrim($basePath, '/'); + return $this; + } + + /** + * Everything in REQUEST_URI before PATH_INFO not including the filename + * + * + * @return string + */ + public function getBasePath() + { + if (null === $this->_basePath) { + $this->setBasePath(); + } + + return $this->_basePath; + } + + /** + * Set the PATH_INFO string + * + * @param string|null $pathInfo + * @return Zend_Controller_Request_Http + */ + public function setPathInfo($pathInfo = null) + { + if ($pathInfo === null) { + $baseUrl = $this->getBaseUrl(); // this actually calls setBaseUrl() & setRequestUri() + $baseUrlRaw = $this->getBaseUrl(false); + $baseUrlEncoded = urlencode($baseUrlRaw); + + if (null === ($requestUri = $this->getRequestUri())) { + return $this; + } + + // Remove the query string from REQUEST_URI + if ($pos = strpos($requestUri, '?')) { + $requestUri = substr($requestUri, 0, $pos); + } + + if (!empty($baseUrl) || !empty($baseUrlRaw)) { + if (strpos($requestUri, $baseUrl) === 0) { + $pathInfo = substr($requestUri, strlen($baseUrl)); + } elseif (strpos($requestUri, $baseUrlRaw) === 0) { + $pathInfo = substr($requestUri, strlen($baseUrlRaw)); + } elseif (strpos($requestUri, $baseUrlEncoded) === 0) { + $pathInfo = substr($requestUri, strlen($baseUrlEncoded)); + } else { + $pathInfo = $requestUri; + } + } else { + $pathInfo = $requestUri; + } + + } + + $this->_pathInfo = (string) $pathInfo; + return $this; + } + + /** + * Returns everything between the BaseUrl and QueryString. + * This value is calculated instead of reading PATH_INFO + * directly from $_SERVER due to cross-platform differences. + * + * @return string + */ + public function getPathInfo() + { + if (empty($this->_pathInfo)) { + $this->setPathInfo(); + } + + return $this->_pathInfo; + } + + /** + * Set allowed parameter sources + * + * Can be empty array, or contain one or more of '_GET' or '_POST'. + * + * @param array $paramSoures + * @return Zend_Controller_Request_Http + */ + public function setParamSources(array $paramSources = array()) + { + $this->_paramSources = $paramSources; + return $this; + } + + /** + * Get list of allowed parameter sources + * + * @return array + */ + public function getParamSources() + { + return $this->_paramSources; + } + + /** + * Set a userland parameter + * + * Uses $key to set a userland parameter. If $key is an alias, the actual + * key will be retrieved and used to set the parameter. + * + * @param mixed $key + * @param mixed $value + * @return Zend_Controller_Request_Http + */ + public function setParam($key, $value) + { + $key = (null !== ($alias = $this->getAlias($key))) ? $alias : $key; + parent::setParam($key, $value); + return $this; + } + + /** + * Retrieve a parameter + * + * Retrieves a parameter from the instance. Priority is in the order of + * userland parameters (see {@link setParam()}), $_GET, $_POST. If a + * parameter matching the $key is not found, null is returned. + * + * If the $key is an alias, the actual key aliased will be used. + * + * @param mixed $key + * @param mixed $default Default value to use if key not found + * @return mixed + */ + public function getParam($key, $default = null) + { + $keyName = (null !== ($alias = $this->getAlias($key))) ? $alias : $key; + + $paramSources = $this->getParamSources(); + if (isset($this->_params[$keyName])) { + return $this->_params[$keyName]; + } elseif (in_array('_GET', $paramSources) && (isset($_GET[$keyName]))) { + return $_GET[$keyName]; + } elseif (in_array('_POST', $paramSources) && (isset($_POST[$keyName]))) { + return $_POST[$keyName]; + } + + return $default; + } + + /** + * Retrieve an array of parameters + * + * Retrieves a merged array of parameters, with precedence of userland + * params (see {@link setParam()}), $_GET, $_POST (i.e., values in the + * userland params will take precedence over all others). + * + * @return array + */ + public function getParams() + { + $return = $this->_params; + $paramSources = $this->getParamSources(); + if (in_array('_GET', $paramSources) + && isset($_GET) + && is_array($_GET) + ) { + $return += $_GET; + } + if (in_array('_POST', $paramSources) + && isset($_POST) + && is_array($_POST) + ) { + $return += $_POST; + } + return $return; + } + + /** + * Set parameters + * + * Set one or more parameters. Parameters are set as userland parameters, + * using the keys specified in the array. + * + * @param array $params + * @return Zend_Controller_Request_Http + */ + public function setParams(array $params) + { + foreach ($params as $key => $value) { + $this->setParam($key, $value); + } + return $this; + } + + /** + * Set a key alias + * + * Set an alias used for key lookups. $name specifies the alias, $target + * specifies the actual key to use. + * + * @param string $name + * @param string $target + * @return Zend_Controller_Request_Http + */ + public function setAlias($name, $target) + { + $this->_aliases[$name] = $target; + return $this; + } + + /** + * Retrieve an alias + * + * Retrieve the actual key represented by the alias $name. + * + * @param string $name + * @return string|null Returns null when no alias exists + */ + public function getAlias($name) + { + if (isset($this->_aliases[$name])) { + return $this->_aliases[$name]; + } + + return null; + } + + /** + * Retrieve the list of all aliases + * + * @return array + */ + public function getAliases() + { + return $this->_aliases; + } + + /** + * Return the method by which the request was made + * + * @return string + */ + public function getMethod() + { + return $this->getServer('REQUEST_METHOD'); + } + + /** + * Was the request made by POST? + * + * @return boolean + */ + public function isPost() + { + if ('POST' == $this->getMethod()) { + return true; + } + + return false; + } + + /** + * Was the request made by GET? + * + * @return boolean + */ + public function isGet() + { + if ('GET' == $this->getMethod()) { + return true; + } + + return false; + } + + /** + * Was the request made by PUT? + * + * @return boolean + */ + public function isPut() + { + if ('PUT' == $this->getMethod()) { + return true; + } + + return false; + } + + /** + * Was the request made by DELETE? + * + * @return boolean + */ + public function isDelete() + { + if ('DELETE' == $this->getMethod()) { + return true; + } + + return false; + } + + /** + * Was the request made by HEAD? + * + * @return boolean + */ + public function isHead() + { + if ('HEAD' == $this->getMethod()) { + return true; + } + + return false; + } + + /** + * Was the request made by OPTIONS? + * + * @return boolean + */ + public function isOptions() + { + if ('OPTIONS' == $this->getMethod()) { + return true; + } + + return false; + } + + /** + * Is the request a Javascript XMLHttpRequest? + * + * Should work with Prototype/Script.aculo.us, possibly others. + * + * @return boolean + */ + public function isXmlHttpRequest() + { + return ($this->getHeader('X_REQUESTED_WITH') == 'XMLHttpRequest'); + } + + /** + * Is this a Flash request? + * + * @return boolean + */ + public function isFlashRequest() + { + $header = strtolower($this->getHeader('USER_AGENT')); + return (strstr($header, ' flash')) ? true : false; + } + + /** + * Is https secure request + * + * @return boolean + */ + public function isSecure() + { + return ($this->getScheme() === self::SCHEME_HTTPS); + } + + /** + * Return the raw body of the request, if present + * + * @return string|false Raw body, or false if not present + */ + public function getRawBody() + { + if (null === $this->_rawBody) { + $body = file_get_contents('php://input'); + + if (strlen(trim($body)) > 0) { + $this->_rawBody = $body; + } else { + $this->_rawBody = false; + } + } + return $this->_rawBody; + } + + /** + * Return the value of the given HTTP header. Pass the header name as the + * plain, HTTP-specified header name. Ex.: Ask for 'Accept' to get the + * Accept header, 'Accept-Encoding' to get the Accept-Encoding header. + * + * @param string $header HTTP header name + * @return string|false HTTP header value, or false if not found + * @throws Zend_Controller_Request_Exception + */ + public function getHeader($header) + { + if (empty($header)) { + require_once 'Zend/Controller/Request/Exception.php'; + throw new Zend_Controller_Request_Exception('An HTTP header name is required'); + } + + // Try to get it from the $_SERVER array first + $temp = 'HTTP_' . strtoupper(str_replace('-', '_', $header)); + if (isset($_SERVER[$temp])) { + return $_SERVER[$temp]; + } + + // This seems to be the only way to get the Authorization header on + // Apache + if (function_exists('apache_request_headers')) { + $headers = apache_request_headers(); + if (isset($headers[$header])) { + return $headers[$header]; + } + $header = strtolower($header); + foreach ($headers as $key => $value) { + if (strtolower($key) == $header) { + return $value; + } + } + } + + return false; + } + + /** + * Get the request URI scheme + * + * @return string + */ + public function getScheme() + { + return ($this->getServer('HTTPS') == 'on') ? self::SCHEME_HTTPS : self::SCHEME_HTTP; + } + + /** + * Get the HTTP host. + * + * "Host" ":" host [ ":" port ] ; Section 3.2.2 + * Note the HTTP Host header is not the same as the URI host. + * It includes the port while the URI host doesn't. + * + * @return string + */ + public function getHttpHost() + { + $host = $this->getServer('HTTP_HOST'); + if (!empty($host)) { + return $host; + } + + $scheme = $this->getScheme(); + $name = $this->getServer('SERVER_NAME'); + $port = $this->getServer('SERVER_PORT'); + + if(null === $name) { + return ''; + } + elseif (($scheme == self::SCHEME_HTTP && $port == 80) || ($scheme == self::SCHEME_HTTPS && $port == 443)) { + return $name; + } else { + return $name . ':' . $port; + } + } + + /** + * Get the client's IP addres + * + * @param boolean $checkProxy + * @return string + */ + public function getClientIp($checkProxy = true) + { + if ($checkProxy && $this->getServer('HTTP_CLIENT_IP') != null) { + $ip = $this->getServer('HTTP_CLIENT_IP'); + } else if ($checkProxy && $this->getServer('HTTP_X_FORWARDED_FOR') != null) { + $ip = $this->getServer('HTTP_X_FORWARDED_FOR'); + } else { + $ip = $this->getServer('REMOTE_ADDR'); + } + + return $ip; + } +} diff --git a/library/Zend/Controller/Request/HttpTestCase.php b/library/Zend/Controller/Request/HttpTestCase.php new file mode 100644 index 00000000..a016328e --- /dev/null +++ b/library/Zend/Controller/Request/HttpTestCase.php @@ -0,0 +1,276 @@ +_rawBody = (string) $content; + return $this; + } + + /** + * Get RAW POST body + * + * @return string|null + */ + public function getRawBody() + { + return $this->_rawBody; + } + + /** + * Clear raw POST body + * + * @return Zend_Controller_Request_HttpTestCase + */ + public function clearRawBody() + { + $this->_rawBody = null; + return $this; + } + + /** + * Set a cookie + * + * @param string $key + * @param mixed $value + * @return Zend_Controller_Request_HttpTestCase + */ + public function setCookie($key, $value) + { + $_COOKIE[(string) $key] = $value; + return $this; + } + + /** + * Set multiple cookies at once + * + * @param array $cookies + * @return void + */ + public function setCookies(array $cookies) + { + foreach ($cookies as $key => $value) { + $_COOKIE[$key] = $value; + } + return $this; + } + + /** + * Clear all cookies + * + * @return Zend_Controller_Request_HttpTestCase + */ + public function clearCookies() + { + $_COOKIE = array(); + return $this; + } + + /** + * Set request method + * + * @param string $type + * @return Zend_Controller_Request_HttpTestCase + */ + public function setMethod($type) + { + $type = strtoupper(trim((string) $type)); + if (!in_array($type, $this->_validMethodTypes)) { + require_once 'Zend/Controller/Exception.php'; + throw new Zend_Controller_Exception('Invalid request method specified'); + } + $this->_method = $type; + return $this; + } + + /** + * Get request method + * + * @return string|null + */ + public function getMethod() + { + return $this->_method; + } + + /** + * Set a request header + * + * @param string $key + * @param string $value + * @return Zend_Controller_Request_HttpTestCase + */ + public function setHeader($key, $value) + { + $key = $this->_normalizeHeaderName($key); + $this->_headers[$key] = (string) $value; + return $this; + } + + /** + * Set request headers + * + * @param array $headers + * @return Zend_Controller_Request_HttpTestCase + */ + public function setHeaders(array $headers) + { + foreach ($headers as $key => $value) { + $this->setHeader($key, $value); + } + return $this; + } + + /** + * Get request header + * + * @param string $header + * @param mixed $default + * @return string|null + */ + public function getHeader($header, $default = null) + { + $header = $this->_normalizeHeaderName($header); + if (array_key_exists($header, $this->_headers)) { + return $this->_headers[$header]; + } + return $default; + } + + /** + * Get all request headers + * + * @return array + */ + public function getHeaders() + { + return $this->_headers; + } + + /** + * Clear request headers + * + * @return Zend_Controller_Request_HttpTestCase + */ + public function clearHeaders() + { + $this->_headers = array(); + return $this; + } + + /** + * Get REQUEST_URI + * + * @return null|string + */ + public function getRequestUri() + { + return $this->_requestUri; + } + + /** + * Normalize a header name for setting and retrieval + * + * @param string $name + * @return string + */ + protected function _normalizeHeaderName($name) + { + $name = strtoupper((string) $name); + $name = str_replace('-', '_', $name); + return $name; + } +} diff --git a/library/Zend/Controller/Request/Simple.php b/library/Zend/Controller/Request/Simple.php new file mode 100644 index 00000000..51fd7ea3 --- /dev/null +++ b/library/Zend/Controller/Request/Simple.php @@ -0,0 +1,55 @@ +setActionName($action); + } + + if ($controller) { + $this->setControllerName($controller); + } + + if ($module) { + $this->setModuleName($module); + } + + if ($params) { + $this->setParams($params); + } + } + +} diff --git a/library/Zend/Controller/Response/Abstract.php b/library/Zend/Controller/Response/Abstract.php new file mode 100644 index 00000000..19e278d3 --- /dev/null +++ b/library/Zend/Controller/Response/Abstract.php @@ -0,0 +1,796 @@ +canSendHeaders(true); + $name = $this->_normalizeHeader($name); + $value = (string) $value; + + if ($replace) { + foreach ($this->_headers as $key => $header) { + if ($name == $header['name']) { + unset($this->_headers[$key]); + } + } + } + + $this->_headers[] = array( + 'name' => $name, + 'value' => $value, + 'replace' => $replace + ); + + return $this; + } + + /** + * Set redirect URL + * + * Sets Location header and response code. Forces replacement of any prior + * redirects. + * + * @param string $url + * @param int $code + * @return Zend_Controller_Response_Abstract + */ + public function setRedirect($url, $code = 302) + { + $this->canSendHeaders(true); + $this->setHeader('Location', $url, true) + ->setHttpResponseCode($code); + + return $this; + } + + /** + * Is this a redirect? + * + * @return boolean + */ + public function isRedirect() + { + return $this->_isRedirect; + } + + /** + * Return array of headers; see {@link $_headers} for format + * + * @return array + */ + public function getHeaders() + { + return $this->_headers; + } + + /** + * Clear headers + * + * @return Zend_Controller_Response_Abstract + */ + public function clearHeaders() + { + $this->_headers = array(); + + return $this; + } + + /** + * Clears the specified HTTP header + * + * @param string $name + * @return Zend_Controller_Response_Abstract + */ + public function clearHeader($name) + { + if (! count($this->_headers)) { + return $this; + } + + foreach ($this->_headers as $index => $header) { + if ($name == $header['name']) { + unset($this->_headers[$index]); + } + } + + return $this; + } + + /** + * Set raw HTTP header + * + * Allows setting non key => value headers, such as status codes + * + * @param string $value + * @return Zend_Controller_Response_Abstract + */ + public function setRawHeader($value) + { + $this->canSendHeaders(true); + if ('Location' == substr($value, 0, 8)) { + $this->_isRedirect = true; + } + $this->_headersRaw[] = (string) $value; + return $this; + } + + /** + * Retrieve all {@link setRawHeader() raw HTTP headers} + * + * @return array + */ + public function getRawHeaders() + { + return $this->_headersRaw; + } + + /** + * Clear all {@link setRawHeader() raw HTTP headers} + * + * @return Zend_Controller_Response_Abstract + */ + public function clearRawHeaders() + { + $this->_headersRaw = array(); + return $this; + } + + /** + * Clears the specified raw HTTP header + * + * @param string $headerRaw + * @return Zend_Controller_Response_Abstract + */ + public function clearRawHeader($headerRaw) + { + if (! count($this->_headersRaw)) { + return $this; + } + + $key = array_search($headerRaw, $this->_headersRaw); + if ($key !== false) { + unset($this->_headersRaw[$key]); + } + + return $this; + } + + /** + * Clear all headers, normal and raw + * + * @return Zend_Controller_Response_Abstract + */ + public function clearAllHeaders() + { + return $this->clearHeaders() + ->clearRawHeaders(); + } + + /** + * Set HTTP response code to use with headers + * + * @param int $code + * @return Zend_Controller_Response_Abstract + */ + public function setHttpResponseCode($code) + { + if (!is_int($code) || (100 > $code) || (599 < $code)) { + require_once 'Zend/Controller/Response/Exception.php'; + throw new Zend_Controller_Response_Exception('Invalid HTTP response code'); + } + + if ((300 <= $code) && (307 >= $code)) { + $this->_isRedirect = true; + } else { + $this->_isRedirect = false; + } + + $this->_httpResponseCode = $code; + return $this; + } + + /** + * Retrieve HTTP response code + * + * @return int + */ + public function getHttpResponseCode() + { + return $this->_httpResponseCode; + } + + /** + * Can we send headers? + * + * @param boolean $throw Whether or not to throw an exception if headers have been sent; defaults to false + * @return boolean + * @throws Zend_Controller_Response_Exception + */ + public function canSendHeaders($throw = false) + { + $ok = headers_sent($file, $line); + if ($ok && $throw && $this->headersSentThrowsException) { + require_once 'Zend/Controller/Response/Exception.php'; + throw new Zend_Controller_Response_Exception('Cannot send headers; headers already sent in ' . $file . ', line ' . $line); + } + + return !$ok; + } + + /** + * Send all headers + * + * Sends any headers specified. If an {@link setHttpResponseCode() HTTP response code} + * has been specified, it is sent with the first header. + * + * @return Zend_Controller_Response_Abstract + */ + public function sendHeaders() + { + // Only check if we can send headers if we have headers to send + if (count($this->_headersRaw) || count($this->_headers) || (200 != $this->_httpResponseCode)) { + $this->canSendHeaders(true); + } elseif (200 == $this->_httpResponseCode) { + // Haven't changed the response code, and we have no headers + return $this; + } + + $httpCodeSent = false; + + foreach ($this->_headersRaw as $header) { + if (!$httpCodeSent && $this->_httpResponseCode) { + header($header, true, $this->_httpResponseCode); + $httpCodeSent = true; + } else { + header($header); + } + } + + foreach ($this->_headers as $header) { + if (!$httpCodeSent && $this->_httpResponseCode) { + header($header['name'] . ': ' . $header['value'], $header['replace'], $this->_httpResponseCode); + $httpCodeSent = true; + } else { + header($header['name'] . ': ' . $header['value'], $header['replace']); + } + } + + if (!$httpCodeSent) { + header('HTTP/1.1 ' . $this->_httpResponseCode); + $httpCodeSent = true; + } + + return $this; + } + + /** + * Set body content + * + * If $name is not passed, or is not a string, resets the entire body and + * sets the 'default' key to $content. + * + * If $name is a string, sets the named segment in the body array to + * $content. + * + * @param string $content + * @param null|string $name + * @return Zend_Controller_Response_Abstract + */ + public function setBody($content, $name = null) + { + if ((null === $name) || !is_string($name)) { + $this->_body = array('default' => (string) $content); + } else { + $this->_body[$name] = (string) $content; + } + + return $this; + } + + /** + * Append content to the body content + * + * @param string $content + * @param null|string $name + * @return Zend_Controller_Response_Abstract + */ + public function appendBody($content, $name = null) + { + if ((null === $name) || !is_string($name)) { + if (isset($this->_body['default'])) { + $this->_body['default'] .= (string) $content; + } else { + return $this->append('default', $content); + } + } elseif (isset($this->_body[$name])) { + $this->_body[$name] .= (string) $content; + } else { + return $this->append($name, $content); + } + + return $this; + } + + /** + * Clear body array + * + * With no arguments, clears the entire body array. Given a $name, clears + * just that named segment; if no segment matching $name exists, returns + * false to indicate an error. + * + * @param string $name Named segment to clear + * @return boolean + */ + public function clearBody($name = null) + { + if (null !== $name) { + $name = (string) $name; + if (isset($this->_body[$name])) { + unset($this->_body[$name]); + return true; + } + + return false; + } + + $this->_body = array(); + return true; + } + + /** + * Return the body content + * + * If $spec is false, returns the concatenated values of the body content + * array. If $spec is boolean true, returns the body content array. If + * $spec is a string and matches a named segment, returns the contents of + * that segment; otherwise, returns null. + * + * @param boolean $spec + * @return string|array|null + */ + public function getBody($spec = false) + { + if (false === $spec) { + ob_start(); + $this->outputBody(); + return ob_get_clean(); + } elseif (true === $spec) { + return $this->_body; + } elseif (is_string($spec) && isset($this->_body[$spec])) { + return $this->_body[$spec]; + } + + return null; + } + + /** + * Append a named body segment to the body content array + * + * If segment already exists, replaces with $content and places at end of + * array. + * + * @param string $name + * @param string $content + * @return Zend_Controller_Response_Abstract + */ + public function append($name, $content) + { + if (!is_string($name)) { + require_once 'Zend/Controller/Response/Exception.php'; + throw new Zend_Controller_Response_Exception('Invalid body segment key ("' . gettype($name) . '")'); + } + + if (isset($this->_body[$name])) { + unset($this->_body[$name]); + } + $this->_body[$name] = (string) $content; + return $this; + } + + /** + * Prepend a named body segment to the body content array + * + * If segment already exists, replaces with $content and places at top of + * array. + * + * @param string $name + * @param string $content + * @return void + */ + public function prepend($name, $content) + { + if (!is_string($name)) { + require_once 'Zend/Controller/Response/Exception.php'; + throw new Zend_Controller_Response_Exception('Invalid body segment key ("' . gettype($name) . '")'); + } + + if (isset($this->_body[$name])) { + unset($this->_body[$name]); + } + + $new = array($name => (string) $content); + $this->_body = $new + $this->_body; + + return $this; + } + + /** + * Insert a named segment into the body content array + * + * @param string $name + * @param string $content + * @param string $parent + * @param boolean $before Whether to insert the new segment before or + * after the parent. Defaults to false (after) + * @return Zend_Controller_Response_Abstract + */ + public function insert($name, $content, $parent = null, $before = false) + { + if (!is_string($name)) { + require_once 'Zend/Controller/Response/Exception.php'; + throw new Zend_Controller_Response_Exception('Invalid body segment key ("' . gettype($name) . '")'); + } + + if ((null !== $parent) && !is_string($parent)) { + require_once 'Zend/Controller/Response/Exception.php'; + throw new Zend_Controller_Response_Exception('Invalid body segment parent key ("' . gettype($parent) . '")'); + } + + if (isset($this->_body[$name])) { + unset($this->_body[$name]); + } + + if ((null === $parent) || !isset($this->_body[$parent])) { + return $this->append($name, $content); + } + + $ins = array($name => (string) $content); + $keys = array_keys($this->_body); + $loc = array_search($parent, $keys); + if (!$before) { + // Increment location if not inserting before + ++$loc; + } + + if (0 === $loc) { + // If location of key is 0, we're prepending + $this->_body = $ins + $this->_body; + } elseif ($loc >= (count($this->_body))) { + // If location of key is maximal, we're appending + $this->_body = $this->_body + $ins; + } else { + // Otherwise, insert at location specified + $pre = array_slice($this->_body, 0, $loc); + $post = array_slice($this->_body, $loc); + $this->_body = $pre + $ins + $post; + } + + return $this; + } + + /** + * Echo the body segments + * + * @return void + */ + public function outputBody() + { + $body = implode('', $this->_body); + echo $body; + } + + /** + * Register an exception with the response + * + * @param Exception $e + * @return Zend_Controller_Response_Abstract + */ + public function setException(Exception $e) + { + $this->_exceptions[] = $e; + return $this; + } + + /** + * Retrieve the exception stack + * + * @return array + */ + public function getException() + { + return $this->_exceptions; + } + + /** + * Has an exception been registered with the response? + * + * @return boolean + */ + public function isException() + { + return !empty($this->_exceptions); + } + + /** + * Does the response object contain an exception of a given type? + * + * @param string $type + * @return boolean + */ + public function hasExceptionOfType($type) + { + foreach ($this->_exceptions as $e) { + if ($e instanceof $type) { + return true; + } + } + + return false; + } + + /** + * Does the response object contain an exception with a given message? + * + * @param string $message + * @return boolean + */ + public function hasExceptionOfMessage($message) + { + foreach ($this->_exceptions as $e) { + if ($message == $e->getMessage()) { + return true; + } + } + + return false; + } + + /** + * Does the response object contain an exception with a given code? + * + * @param int $code + * @return boolean + */ + public function hasExceptionOfCode($code) + { + $code = (int) $code; + foreach ($this->_exceptions as $e) { + if ($code == $e->getCode()) { + return true; + } + } + + return false; + } + + /** + * Retrieve all exceptions of a given type + * + * @param string $type + * @return false|array + */ + public function getExceptionByType($type) + { + $exceptions = array(); + foreach ($this->_exceptions as $e) { + if ($e instanceof $type) { + $exceptions[] = $e; + } + } + + if (empty($exceptions)) { + $exceptions = false; + } + + return $exceptions; + } + + /** + * Retrieve all exceptions of a given message + * + * @param string $message + * @return false|array + */ + public function getExceptionByMessage($message) + { + $exceptions = array(); + foreach ($this->_exceptions as $e) { + if ($message == $e->getMessage()) { + $exceptions[] = $e; + } + } + + if (empty($exceptions)) { + $exceptions = false; + } + + return $exceptions; + } + + /** + * Retrieve all exceptions of a given code + * + * @param mixed $code + * @return void + */ + public function getExceptionByCode($code) + { + $code = (int) $code; + $exceptions = array(); + foreach ($this->_exceptions as $e) { + if ($code == $e->getCode()) { + $exceptions[] = $e; + } + } + + if (empty($exceptions)) { + $exceptions = false; + } + + return $exceptions; + } + + /** + * Whether or not to render exceptions (off by default) + * + * If called with no arguments or a null argument, returns the value of the + * flag; otherwise, sets it and returns the current value. + * + * @param boolean $flag Optional + * @return boolean + */ + public function renderExceptions($flag = null) + { + if (null !== $flag) { + $this->_renderExceptions = $flag ? true : false; + } + + return $this->_renderExceptions; + } + + /** + * Send the response, including all headers, rendering exceptions if so + * requested. + * + * @return void + */ + public function sendResponse() + { + $this->sendHeaders(); + + if ($this->isException() && $this->renderExceptions()) { + $exceptions = ''; + foreach ($this->getException() as $e) { + $exceptions .= $e->__toString() . "\n"; + } + echo $exceptions; + return; + } + + $this->outputBody(); + } + + /** + * Magic __toString functionality + * + * Proxies to {@link sendResponse()} and returns response value as string + * using output buffering. + * + * @return string + */ + public function __toString() + { + ob_start(); + $this->sendResponse(); + return ob_get_clean(); + } +} diff --git a/library/Zend/Controller/Response/Cli.php b/library/Zend/Controller/Response/Cli.php new file mode 100644 index 00000000..74268f37 --- /dev/null +++ b/library/Zend/Controller/Response/Cli.php @@ -0,0 +1,68 @@ +isException() && $this->renderExceptions()) { + $exceptions = ''; + foreach ($this->getException() as $e) { + $exceptions .= $e->__toString() . "\n"; + } + return $exceptions; + } + + return $this->_body; + } +} diff --git a/library/Zend/Controller/Response/Exception.php b/library/Zend/Controller/Response/Exception.php new file mode 100644 index 00000000..2bc60055 --- /dev/null +++ b/library/Zend/Controller/Response/Exception.php @@ -0,0 +1,36 @@ +_headersRaw as $header) { + $headers[] = $header; + } + foreach ($this->_headers as $header) { + $name = $header['name']; + $key = strtolower($name); + if (array_key_exists($name, $headers)) { + if ($header['replace']) { + $headers[$key] = $header['name'] . ': ' . $header['value']; + } + } else { + $headers[$key] = $header['name'] . ': ' . $header['value']; + } + } + return $headers; + } + + /** + * Can we send headers? + * + * @param bool $throw + * @return void + */ + public function canSendHeaders($throw = false) + { + return true; + } + + /** + * Return the concatenated body segments + * + * @return string + */ + public function outputBody() + { + $fullContent = ''; + foreach ($this->_body as $content) { + $fullContent .= $content; + } + return $fullContent; + } + + /** + * Get body and/or body segments + * + * @param bool|string $spec + * @return string|array|null + */ + public function getBody($spec = false) + { + if (false === $spec) { + return $this->outputBody(); + } elseif (true === $spec) { + return $this->_body; + } elseif (is_string($spec) && isset($this->_body[$spec])) { + return $this->_body[$spec]; + } + + return null; + } + + /** + * "send" Response + * + * Concats all response headers, and then final body (separated by two + * newlines) + * + * @return string + */ + public function sendResponse() + { + $headers = $this->sendHeaders(); + $content = implode("\n", $headers) . "\n\n"; + + if ($this->isException() && $this->renderExceptions()) { + $exceptions = ''; + foreach ($this->getException() as $e) { + $exceptions .= $e->__toString() . "\n"; + } + $content .= $exceptions; + } else { + $content .= $this->outputBody(); + } + + return $content; + } +} diff --git a/library/Zend/Controller/Router/Abstract.php b/library/Zend/Controller/Router/Abstract.php new file mode 100644 index 00000000..14687403 --- /dev/null +++ b/library/Zend/Controller/Router/Abstract.php @@ -0,0 +1,175 @@ +setParams($params); + } + + /** + * Add or modify a parameter to use when instantiating an action controller + * + * @param string $name + * @param mixed $value + * @return Zend_Controller_Router + */ + public function setParam($name, $value) + { + $name = (string) $name; + $this->_invokeParams[$name] = $value; + return $this; + } + + /** + * Set parameters to pass to action controller constructors + * + * @param array $params + * @return Zend_Controller_Router + */ + public function setParams(array $params) + { + $this->_invokeParams = array_merge($this->_invokeParams, $params); + return $this; + } + + /** + * Retrieve a single parameter from the controller parameter stack + * + * @param string $name + * @return mixed + */ + public function getParam($name) + { + if(isset($this->_invokeParams[$name])) { + return $this->_invokeParams[$name]; + } + + return null; + } + + /** + * Retrieve action controller instantiation parameters + * + * @return array + */ + public function getParams() + { + return $this->_invokeParams; + } + + /** + * Clear the controller parameter stack + * + * By default, clears all parameters. If a parameter name is given, clears + * only that parameter; if an array of parameter names is provided, clears + * each. + * + * @param null|string|array single key or array of keys for params to clear + * @return Zend_Controller_Router + */ + public function clearParams($name = null) + { + if (null === $name) { + $this->_invokeParams = array(); + } elseif (is_string($name) && isset($this->_invokeParams[$name])) { + unset($this->_invokeParams[$name]); + } elseif (is_array($name)) { + foreach ($name as $key) { + if (is_string($key) && isset($this->_invokeParams[$key])) { + unset($this->_invokeParams[$key]); + } + } + } + + return $this; + } + + /** + * Retrieve Front Controller + * + * @return Zend_Controller_Front + */ + public function getFrontController() + { + // Used cache version if found + if (null !== $this->_frontController) { + return $this->_frontController; + } + + require_once 'Zend/Controller/Front.php'; + $this->_frontController = Zend_Controller_Front::getInstance(); + return $this->_frontController; + } + + /** + * Set Front Controller + * + * @param Zend_Controller_Front $controller + * @return Zend_Controller_Router_Interface + */ + public function setFrontController(Zend_Controller_Front $controller) + { + $this->_frontController = $controller; + return $this; + } + +} diff --git a/library/Zend/Controller/Router/Exception.php b/library/Zend/Controller/Router/Exception.php new file mode 100644 index 00000000..7e5a7b7d --- /dev/null +++ b/library/Zend/Controller/Router/Exception.php @@ -0,0 +1,36 @@ +hasRoute('default')) { + $dispatcher = $this->getFrontController()->getDispatcher(); + $request = $this->getFrontController()->getRequest(); + + require_once 'Zend/Controller/Router/Route/Module.php'; + $compat = new Zend_Controller_Router_Route_Module(array(), $dispatcher, $request); + + $this->_routes = array('default' => $compat) + $this->_routes; + } + + return $this; + } + + /** + * Add route to the route chain + * + * If route contains method setRequest(), it is initialized with a request object + * + * @param string $name Name of the route + * @param Zend_Controller_Router_Route_Interface $route Instance of the route + * @return Zend_Controller_Router_Rewrite + */ + public function addRoute($name, Zend_Controller_Router_Route_Interface $route) + { + if (method_exists($route, 'setRequest')) { + $route->setRequest($this->getFrontController()->getRequest()); + } + + $this->_routes[$name] = $route; + + return $this; + } + + /** + * Add routes to the route chain + * + * @param array $routes Array of routes with names as keys and routes as values + * @return Zend_Controller_Router_Rewrite + */ + public function addRoutes($routes) { + foreach ($routes as $name => $route) { + $this->addRoute($name, $route); + } + + return $this; + } + + /** + * Create routes out of Zend_Config configuration + * + * Example INI: + * routes.archive.route = "archive/:year/*" + * routes.archive.defaults.controller = archive + * routes.archive.defaults.action = show + * routes.archive.defaults.year = 2000 + * routes.archive.reqs.year = "\d+" + * + * routes.news.type = "Zend_Controller_Router_Route_Static" + * routes.news.route = "news" + * routes.news.defaults.controller = "news" + * routes.news.defaults.action = "list" + * + * And finally after you have created a Zend_Config with above ini: + * $router = new Zend_Controller_Router_Rewrite(); + * $router->addConfig($config, 'routes'); + * + * @param Zend_Config $config Configuration object + * @param string $section Name of the config section containing route's definitions + * @throws Zend_Controller_Router_Exception + * @return Zend_Controller_Router_Rewrite + */ + public function addConfig(Zend_Config $config, $section = null) + { + if ($section !== null) { + if ($config->{$section} === null) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception("No route configuration in section '{$section}'"); + } + + $config = $config->{$section}; + } + + foreach ($config as $name => $info) { + $route = $this->_getRouteFromConfig($info); + + if ($route instanceof Zend_Controller_Router_Route_Chain) { + if (!isset($info->chain)) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception("No chain defined"); + } + + if ($info->chain instanceof Zend_Config) { + $childRouteNames = $info->chain; + } else { + $childRouteNames = explode(',', $info->chain); + } + + foreach ($childRouteNames as $childRouteName) { + $childRoute = $this->getRoute(trim($childRouteName)); + $route->chain($childRoute); + } + + $this->addRoute($name, $route); + } elseif (isset($info->chains) && $info->chains instanceof Zend_Config) { + $this->_addChainRoutesFromConfig($name, $route, $info->chains); + } else { + $this->addRoute($name, $route); + } + } + + return $this; + } + + /** + * Get a route frm a config instance + * + * @param Zend_Config $info + * @return Zend_Controller_Router_Route_Interface + */ + protected function _getRouteFromConfig(Zend_Config $info) + { + $class = (isset($info->type)) ? $info->type : 'Zend_Controller_Router_Route'; + if (!class_exists($class)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($class); + } + + $route = call_user_func(array($class, 'getInstance'), $info); + + if (isset($info->abstract) && $info->abstract && method_exists($route, 'isAbstract')) { + $route->isAbstract(true); + } + + return $route; + } + + /** + * Add chain routes from a config route + * + * @param string $name + * @param Zend_Controller_Router_Route_Interface $route + * @param Zend_Config $childRoutesInfo + * @return void + */ + protected function _addChainRoutesFromConfig($name, + Zend_Controller_Router_Route_Interface $route, + Zend_Config $childRoutesInfo) + { + foreach ($childRoutesInfo as $childRouteName => $childRouteInfo) { + if (is_string($childRouteInfo)) { + $childRouteName = $childRouteInfo; + $childRoute = $this->getRoute($childRouteName); + } else { + $childRoute = $this->_getRouteFromConfig($childRouteInfo); + } + + if ($route instanceof Zend_Controller_Router_Route_Chain) { + $chainRoute = clone $route; + $chainRoute->chain($childRoute); + } else { + $chainRoute = $route->chain($childRoute); + } + + $chainName = $name . $this->_chainNameSeparator . $childRouteName; + + if (isset($childRouteInfo->chains)) { + $this->_addChainRoutesFromConfig($chainName, $chainRoute, $childRouteInfo->chains); + } else { + $this->addRoute($chainName, $chainRoute); + } + } + } + + /** + * Remove a route from the route chain + * + * @param string $name Name of the route + * @throws Zend_Controller_Router_Exception + * @return Zend_Controller_Router_Rewrite + */ + public function removeRoute($name) + { + if (!isset($this->_routes[$name])) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception("Route $name is not defined"); + } + + unset($this->_routes[$name]); + + return $this; + } + + /** + * Remove all standard default routes + * + * @param Zend_Controller_Router_Route_Interface Route + * @return Zend_Controller_Router_Rewrite + */ + public function removeDefaultRoutes() + { + $this->_useDefaultRoutes = false; + + return $this; + } + + /** + * Check if named route exists + * + * @param string $name Name of the route + * @return boolean + */ + public function hasRoute($name) + { + return isset($this->_routes[$name]); + } + + /** + * Retrieve a named route + * + * @param string $name Name of the route + * @throws Zend_Controller_Router_Exception + * @return Zend_Controller_Router_Route_Interface Route object + */ + public function getRoute($name) + { + if (!isset($this->_routes[$name])) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception("Route $name is not defined"); + } + + return $this->_routes[$name]; + } + + /** + * Retrieve a currently matched route + * + * @throws Zend_Controller_Router_Exception + * @return Zend_Controller_Router_Route_Interface Route object + */ + public function getCurrentRoute() + { + if (!isset($this->_currentRoute)) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception("Current route is not defined"); + } + return $this->getRoute($this->_currentRoute); + } + + /** + * Retrieve a name of currently matched route + * + * @throws Zend_Controller_Router_Exception + * @return Zend_Controller_Router_Route_Interface Route object + */ + public function getCurrentRouteName() + { + if (!isset($this->_currentRoute)) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception("Current route is not defined"); + } + return $this->_currentRoute; + } + + /** + * Retrieve an array of routes added to the route chain + * + * @return array All of the defined routes + */ + public function getRoutes() + { + return $this->_routes; + } + + /** + * Find a matching route to the current PATH_INFO and inject + * returning values to the Request object. + * + * @throws Zend_Controller_Router_Exception + * @return Zend_Controller_Request_Abstract Request object + */ + public function route(Zend_Controller_Request_Abstract $request) + { + if (!$request instanceof Zend_Controller_Request_Http) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception('Zend_Controller_Router_Rewrite requires a Zend_Controller_Request_Http-based request object'); + } + + if ($this->_useDefaultRoutes) { + $this->addDefaultRoutes(); + } + + // Find the matching route + $routeMatched = false; + + foreach (array_reverse($this->_routes, true) as $name => $route) { + // TODO: Should be an interface method. Hack for 1.0 BC + if (method_exists($route, 'isAbstract') && $route->isAbstract()) { + continue; + } + + // TODO: Should be an interface method. Hack for 1.0 BC + if (!method_exists($route, 'getVersion') || $route->getVersion() == 1) { + $match = $request->getPathInfo(); + } else { + $match = $request; + } + + if ($params = $route->match($match)) { + $this->_setRequestParams($request, $params); + $this->_currentRoute = $name; + $routeMatched = true; + break; + } + } + + if (!$routeMatched) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception('No route matched the request', 404); + } + + if($this->_useCurrentParamsAsGlobal) { + $params = $request->getParams(); + foreach($params as $param => $value) { + $this->setGlobalParam($param, $value); + } + } + + return $request; + + } + + protected function _setRequestParams($request, $params) + { + foreach ($params as $param => $value) { + + $request->setParam($param, $value); + + if ($param === $request->getModuleKey()) { + $request->setModuleName($value); + } + if ($param === $request->getControllerKey()) { + $request->setControllerName($value); + } + if ($param === $request->getActionKey()) { + $request->setActionName($value); + } + + } + } + + /** + * Generates a URL path that can be used in URL creation, redirection, etc. + * + * @param array $userParams Options passed by a user used to override parameters + * @param mixed $name The name of a Route to use + * @param bool $reset Whether to reset to the route defaults ignoring URL params + * @param bool $encode Tells to encode URL parts on output + * @throws Zend_Controller_Router_Exception + * @return string Resulting absolute URL path + */ + public function assemble($userParams, $name = null, $reset = false, $encode = true) + { + if (!is_array($userParams)) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception('userParams must be an array'); + } + + if ($name == null) { + try { + $name = $this->getCurrentRouteName(); + } catch (Zend_Controller_Router_Exception $e) { + $name = 'default'; + } + } + + // Use UNION (+) in order to preserve numeric keys + $params = $userParams + $this->_globalParams; + + $route = $this->getRoute($name); + $url = $route->assemble($params, $reset, $encode); + + if (!preg_match('|^[a-z]+://|', $url)) { + $url = rtrim($this->getFrontController()->getBaseUrl(), self::URI_DELIMITER) . self::URI_DELIMITER . $url; + } + + return $url; + } + + /** + * Set a global parameter + * + * @param string $name + * @param mixed $value + * @return Zend_Controller_Router_Rewrite + */ + public function setGlobalParam($name, $value) + { + $this->_globalParams[$name] = $value; + + return $this; + } + + /** + * Set the separator to use with chain names + * + * @param string $separator The separator to use + * @return Zend_Controller_Router_Rewrite + */ + public function setChainNameSeparator($separator) { + $this->_chainNameSeparator = $separator; + + return $this; + } + + /** + * Get the separator to use for chain names + * + * @return string + */ + public function getChainNameSeparator() { + return $this->_chainNameSeparator; + } + + /** + * Determines/returns whether to use the request parameters as global parameters. + * + * @param boolean|null $use + * Null/unset when you want to retrieve the current state. + * True when request parameters should be global, false otherwise + * @return boolean|Zend_Controller_Router_Rewrite + * Returns a boolean if first param isn't set, returns an + * instance of Zend_Controller_Router_Rewrite otherwise. + * + */ + public function useRequestParametersAsGlobal($use = null) { + if($use === null) { + return $this->_useCurrentParamsAsGlobal; + } + + $this->_useCurrentParamsAsGlobal = (bool) $use; + + return $this; + } +} diff --git a/library/Zend/Controller/Router/Route.php b/library/Zend/Controller/Router/Route.php new file mode 100644 index 00000000..91a7c156 --- /dev/null +++ b/library/Zend/Controller/Router/Route.php @@ -0,0 +1,562 @@ +reqs instanceof Zend_Config) ? $config->reqs->toArray() : array(); + $defs = ($config->defaults instanceof Zend_Config) ? $config->defaults->toArray() : array(); + return new self($config->route, $defs, $reqs); + } + + /** + * Prepares the route for mapping by splitting (exploding) it + * to a corresponding atomic parts. These parts are assigned + * a position which is later used for matching and preparing values. + * + * @param string $route Map used to match with later submitted URL path + * @param array $defaults Defaults for map variables with keys as variable names + * @param array $reqs Regular expression requirements for variables (keys as variable names) + * @param Zend_Translate $translator Translator to use for this instance + */ + public function __construct($route, $defaults = array(), $reqs = array(), Zend_Translate $translator = null, $locale = null) + { + $route = trim($route, $this->_urlDelimiter); + $this->_defaults = (array) $defaults; + $this->_requirements = (array) $reqs; + $this->_translator = $translator; + $this->_locale = $locale; + + if ($route !== '') { + foreach (explode($this->_urlDelimiter, $route) as $pos => $part) { + if (substr($part, 0, 1) == $this->_urlVariable && substr($part, 1, 1) != $this->_urlVariable) { + $name = substr($part, 1); + + if (substr($name, 0, 1) === '@' && substr($name, 1, 1) !== '@') { + $name = substr($name, 1); + $this->_translatable[] = $name; + $this->_isTranslated = true; + } + + $this->_parts[$pos] = (isset($reqs[$name]) ? $reqs[$name] : $this->_defaultRegex); + $this->_variables[$pos] = $name; + } else { + if (substr($part, 0, 1) == $this->_urlVariable) { + $part = substr($part, 1); + } + + if (substr($part, 0, 1) === '@' && substr($part, 1, 1) !== '@') { + $this->_isTranslated = true; + } + + $this->_parts[$pos] = $part; + + if ($part !== '*') { + $this->_staticCount++; + } + } + } + } + } + + /** + * Matches a user submitted path with parts defined by a map. Assigns and + * returns an array of variables on a successful match. + * + * @param string $path Path used to match against this routing map + * @return array|false An array of assigned values or a false on a mismatch + */ + public function match($path, $partial = false) + { + if ($this->_isTranslated) { + $translateMessages = $this->getTranslator()->getMessages(); + } + + $pathStaticCount = 0; + $values = array(); + $matchedPath = ''; + + if (!$partial) { + $path = trim($path, $this->_urlDelimiter); + } + + if ($path !== '') { + $path = explode($this->_urlDelimiter, $path); + + foreach ($path as $pos => $pathPart) { + // Path is longer than a route, it's not a match + if (!array_key_exists($pos, $this->_parts)) { + if ($partial) { + break; + } else { + return false; + } + } + + $matchedPath .= $pathPart . $this->_urlDelimiter; + + // If it's a wildcard, get the rest of URL as wildcard data and stop matching + if ($this->_parts[$pos] == '*') { + $count = count($path); + for($i = $pos; $i < $count; $i+=2) { + $var = urldecode($path[$i]); + if (!isset($this->_wildcardData[$var]) && !isset($this->_defaults[$var]) && !isset($values[$var])) { + $this->_wildcardData[$var] = (isset($path[$i+1])) ? urldecode($path[$i+1]) : null; + } + } + + $matchedPath = implode($this->_urlDelimiter, $path); + break; + } + + $name = isset($this->_variables[$pos]) ? $this->_variables[$pos] : null; + $pathPart = urldecode($pathPart); + + // Translate value if required + $part = $this->_parts[$pos]; + if ($this->_isTranslated && (substr($part, 0, 1) === '@' && substr($part, 1, 1) !== '@' && $name === null) || $name !== null && in_array($name, $this->_translatable)) { + if (substr($part, 0, 1) === '@') { + $part = substr($part, 1); + } + + if (($originalPathPart = array_search($pathPart, $translateMessages)) !== false) { + $pathPart = $originalPathPart; + } + } + + if (substr($part, 0, 2) === '@@') { + $part = substr($part, 1); + } + + // If it's a static part, match directly + if ($name === null && $part != $pathPart) { + return false; + } + + // If it's a variable with requirement, match a regex. If not - everything matches + if ($part !== null && !preg_match($this->_regexDelimiter . '^' . $part . '$' . $this->_regexDelimiter . 'iu', $pathPart)) { + return false; + } + + // If it's a variable store it's value for later + if ($name !== null) { + $values[$name] = $pathPart; + } else { + $pathStaticCount++; + } + } + } + + // Check if all static mappings have been matched + if ($this->_staticCount != $pathStaticCount) { + return false; + } + + $return = $values + $this->_wildcardData + $this->_defaults; + + // Check if all map variables have been initialized + foreach ($this->_variables as $var) { + if (!array_key_exists($var, $return)) { + return false; + } elseif ($return[$var] == '' || $return[$var] === null) { + // Empty variable? Replace with the default value. + $return[$var] = $this->_defaults[$var]; + } + } + + $this->setMatchedPath(rtrim($matchedPath, $this->_urlDelimiter)); + + $this->_values = $values; + + return $return; + + } + + /** + * Assembles user submitted parameters forming a URL path defined by this route + * + * @param array $data An array of variable and value pairs used as parameters + * @param boolean $reset Whether or not to set route defaults with those provided in $data + * @return string Route path with user submitted parameters + */ + public function assemble($data = array(), $reset = false, $encode = false, $partial = false) + { + if ($this->_isTranslated) { + $translator = $this->getTranslator(); + + if (isset($data['@locale'])) { + $locale = $data['@locale']; + unset($data['@locale']); + } else { + $locale = $this->getLocale(); + } + } + + $url = array(); + $flag = false; + + foreach ($this->_parts as $key => $part) { + $name = isset($this->_variables[$key]) ? $this->_variables[$key] : null; + + $useDefault = false; + if (isset($name) && array_key_exists($name, $data) && $data[$name] === null) { + $useDefault = true; + } + + if (isset($name)) { + if (isset($data[$name]) && !$useDefault) { + $value = $data[$name]; + unset($data[$name]); + } elseif (!$reset && !$useDefault && isset($this->_values[$name])) { + $value = $this->_values[$name]; + } elseif (!$reset && !$useDefault && isset($this->_wildcardData[$name])) { + $value = $this->_wildcardData[$name]; + } elseif (array_key_exists($name, $this->_defaults)) { + $value = $this->_defaults[$name]; + } else { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception($name . ' is not specified'); + } + + if ($this->_isTranslated && in_array($name, $this->_translatable)) { + $url[$key] = $translator->translate($value, $locale); + } else { + $url[$key] = $value; + } + } elseif ($part != '*') { + if ($this->_isTranslated && substr($part, 0, 1) === '@') { + if (substr($part, 1, 1) !== '@') { + $url[$key] = $translator->translate(substr($part, 1), $locale); + } else { + $url[$key] = substr($part, 1); + } + } else { + if (substr($part, 0, 2) === '@@') { + $part = substr($part, 1); + } + + $url[$key] = $part; + } + } else { + if (!$reset) $data += $this->_wildcardData; + $defaults = $this->getDefaults(); + foreach ($data as $var => $value) { + if ($value !== null && (!isset($defaults[$var]) || $value != $defaults[$var])) { + $url[$key++] = $var; + $url[$key++] = $value; + $flag = true; + } + } + } + } + + $return = ''; + + foreach (array_reverse($url, true) as $key => $value) { + $defaultValue = null; + + if (isset($this->_variables[$key])) { + $defaultValue = $this->getDefault($this->_variables[$key]); + + if ($this->_isTranslated && $defaultValue !== null && isset($this->_translatable[$this->_variables[$key]])) { + $defaultValue = $translator->translate($defaultValue, $locale); + } + } + + if ($flag || $value !== $defaultValue || $partial) { + if ($encode) $value = urlencode($value); + $return = $this->_urlDelimiter . $value . $return; + $flag = true; + } + } + + return trim($return, $this->_urlDelimiter); + + } + + /** + * Return a single parameter of route's defaults + * + * @param string $name Array key of the parameter + * @return string Previously set default + */ + public function getDefault($name) { + if (isset($this->_defaults[$name])) { + return $this->_defaults[$name]; + } + return null; + } + + /** + * Return an array of defaults + * + * @return array Route defaults + */ + public function getDefaults() { + return $this->_defaults; + } + + /** + * Get all variables which are used by the route + * + * @return array + */ + public function getVariables() + { + return $this->_variables; + } + + /** + * Set a default translator + * + * @param Zend_Translate $translator + * @return void + */ + public static function setDefaultTranslator(Zend_Translate $translator = null) + { + self::$_defaultTranslator = $translator; + } + + /** + * Get the default translator + * + * @return Zend_Translate + */ + public static function getDefaultTranslator() + { + return self::$_defaultTranslator; + } + + /** + * Set a translator + * + * @param Zend_Translate $translator + * @return void + */ + public function setTranslator(Zend_Translate $translator) + { + $this->_translator = $translator; + } + + /** + * Get the translator + * + * @throws Zend_Controller_Router_Exception When no translator can be found + * @return Zend_Translate + */ + public function getTranslator() + { + if ($this->_translator !== null) { + return $this->_translator; + } else if (($translator = self::getDefaultTranslator()) !== null) { + return $translator; + } else { + try { + $translator = Zend_Registry::get('Zend_Translate'); + } catch (Zend_Exception $e) { + $translator = null; + } + + if ($translator instanceof Zend_Translate) { + return $translator; + } + } + + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception('Could not find a translator'); + } + + /** + * Set a default locale + * + * @param mixed $locale + * @return void + */ + public static function setDefaultLocale($locale = null) + { + self::$_defaultLocale = $locale; + } + + /** + * Get the default locale + * + * @return mixed + */ + public static function getDefaultLocale() + { + return self::$_defaultLocale; + } + + /** + * Set a locale + * + * @param mixed $locale + * @return void + */ + public function setLocale($locale) + { + $this->_locale = $locale; + } + + /** + * Get the locale + * + * @return mixed + */ + public function getLocale() + { + if ($this->_locale !== null) { + return $this->_locale; + } else if (($locale = self::getDefaultLocale()) !== null) { + return $locale; + } else { + try { + $locale = Zend_Registry::get('Zend_Locale'); + } catch (Zend_Exception $e) { + $locale = null; + } + + if ($locale !== null) { + return $locale; + } + } + + return null; + } +} diff --git a/library/Zend/Controller/Router/Route/Abstract.php b/library/Zend/Controller/Router/Route/Abstract.php new file mode 100644 index 00000000..929472f4 --- /dev/null +++ b/library/Zend/Controller/Router/Route/Abstract.php @@ -0,0 +1,122 @@ +_matchedPath = $path; + } + + /** + * Get partially matched path + * + * @return string + */ + public function getMatchedPath() + { + return $this->_matchedPath; + } + + /** + * Check or set wether this is an abstract route or not + * + * @param boolean $flag + * @return boolean + */ + public function isAbstract($flag = null) + { + if ($flag !== null) { + $this->_isAbstract = $flag; + } + + return $this->_isAbstract; + } + + /** + * Create a new chain + * + * @param Zend_Controller_Router_Route_Abstract $route + * @param string $separator + * @return Zend_Controller_Router_Route_Chain + */ + public function chain(Zend_Controller_Router_Route_Abstract $route, $separator = '/') + { + require_once 'Zend/Controller/Router/Route/Chain.php'; + + $chain = new Zend_Controller_Router_Route_Chain(); + $chain->chain($this)->chain($route, $separator); + + return $chain; + } + +} diff --git a/library/Zend/Controller/Router/Route/Chain.php b/library/Zend/Controller/Router/Route/Chain.php new file mode 100644 index 00000000..b09f6fa5 --- /dev/null +++ b/library/Zend/Controller/Router/Route/Chain.php @@ -0,0 +1,173 @@ +defaults instanceof Zend_Config) ? $config->defaults->toArray() : array(); + return new self($config->route, $defs); + } + + /** + * Add a route to this chain + * + * @param Zend_Controller_Router_Route_Abstract $route + * @param string $separator + * @return Zend_Controller_Router_Route_Chain + */ + public function chain(Zend_Controller_Router_Route_Abstract $route, $separator = self::URI_DELIMITER) + { + $this->_routes[] = $route; + $this->_separators[] = $separator; + + return $this; + + } + + /** + * Matches a user submitted path with a previously defined route. + * Assigns and returns an array of defaults on a successful match. + * + * @param Zend_Controller_Request_Http $request Request to get the path info from + * @return array|false An array of assigned values or a false on a mismatch + */ + public function match($request, $partial = null) + { + $path = trim($request->getPathInfo(), self::URI_DELIMITER); + $subPath = $path; + $values = array(); + + foreach ($this->_routes as $key => $route) { + if ($key > 0 + && $matchedPath !== null + && $subPath !== '' + && $subPath !== false + ) { + $separator = substr($subPath, 0, strlen($this->_separators[$key])); + + if ($separator !== $this->_separators[$key]) { + return false; + } + + $subPath = substr($subPath, strlen($separator)); + } + + // TODO: Should be an interface method. Hack for 1.0 BC + if (!method_exists($route, 'getVersion') || $route->getVersion() == 1) { + $match = $subPath; + } else { + $request->setPathInfo($subPath); + $match = $request; + } + + $res = $route->match($match, true); + if ($res === false) { + return false; + } + + $matchedPath = $route->getMatchedPath(); + + if ($matchedPath !== null) { + $subPath = substr($subPath, strlen($matchedPath)); + $separator = substr($subPath, 0, strlen($this->_separators[$key])); + } + + $values = $res + $values; + } + + $request->setPathInfo($path); + + if ($subPath !== '' && $subPath !== false) { + return false; + } + + return $values; + } + + /** + * Assembles a URL path defined by this route + * + * @param array $data An array of variable and value pairs used as parameters + * @return string Route path with user submitted parameters + */ + public function assemble($data = array(), $reset = false, $encode = false) + { + $value = ''; + $numRoutes = count($this->_routes); + + foreach ($this->_routes as $key => $route) { + if ($key > 0) { + $value .= $this->_separators[$key]; + } + + $value .= $route->assemble($data, $reset, $encode, (($numRoutes - 1) > $key)); + + if (method_exists($route, 'getVariables')) { + $variables = $route->getVariables(); + + foreach ($variables as $variable) { + $data[$variable] = null; + } + } + } + + return $value; + } + + /** + * Set the request object for this and the child routes + * + * @param Zend_Controller_Request_Abstract|null $request + * @return void + */ + public function setRequest(Zend_Controller_Request_Abstract $request = null) + { + $this->_request = $request; + + foreach ($this->_routes as $route) { + if (method_exists($route, 'setRequest')) { + $route->setRequest($request); + } + } + } + +} diff --git a/library/Zend/Controller/Router/Route/Hostname.php b/library/Zend/Controller/Router/Route/Hostname.php new file mode 100644 index 00000000..f5c7044d --- /dev/null +++ b/library/Zend/Controller/Router/Route/Hostname.php @@ -0,0 +1,341 @@ +_request = $request; + } + + /** + * Get the request object + * + * @return Zend_Controller_Request_Abstract $request + */ + public function getRequest() + { + if ($this->_request === null) { + require_once 'Zend/Controller/Front.php'; + $this->_request = Zend_Controller_Front::getInstance()->getRequest(); + } + + return $this->_request; + } + + /** + * Instantiates route based on passed Zend_Config structure + * + * @param Zend_Config $config Configuration object + */ + public static function getInstance(Zend_Config $config) + { + $reqs = ($config->reqs instanceof Zend_Config) ? $config->reqs->toArray() : array(); + $defs = ($config->defaults instanceof Zend_Config) ? $config->defaults->toArray() : array(); + $scheme = (isset($config->scheme)) ? $config->scheme : null; + return new self($config->route, $defs, $reqs, $scheme); + } + + /** + * Prepares the route for mapping by splitting (exploding) it + * to a corresponding atomic parts. These parts are assigned + * a position which is later used for matching and preparing values. + * + * @param string $route Map used to match with later submitted hostname + * @param array $defaults Defaults for map variables with keys as variable names + * @param array $reqs Regular expression requirements for variables (keys as variable names) + * @param string $scheme + */ + public function __construct($route, $defaults = array(), $reqs = array(), $scheme = null) + { + $route = trim($route, '.'); + $this->_defaults = (array) $defaults; + $this->_requirements = (array) $reqs; + $this->_scheme = $scheme; + + if ($route != '') { + foreach (explode('.', $route) as $pos => $part) { + if (substr($part, 0, 1) == $this->_hostVariable) { + $name = substr($part, 1); + $this->_parts[$pos] = (isset($reqs[$name]) ? $reqs[$name] : $this->_defaultRegex); + $this->_variables[$pos] = $name; + } else { + $this->_parts[$pos] = $part; + $this->_staticCount++; + } + } + } + } + + /** + * Matches a user submitted path with parts defined by a map. Assigns and + * returns an array of variables on a successful match. + * + * @param Zend_Controller_Request_Http $request Request to get the host from + * @return array|false An array of assigned values or a false on a mismatch + */ + public function match($request) + { + // Check the scheme if required + if ($this->_scheme !== null) { + $scheme = $request->getScheme(); + + if ($scheme !== $this->_scheme) { + return false; + } + } + + // Get the host and remove unnecessary port information + $host = $request->getHttpHost(); + if (preg_match('#:\d+$#', $host, $result) === 1) { + $host = substr($host, 0, -strlen($result[0])); + } + + $hostStaticCount = 0; + $values = array(); + + $host = trim($host, '.'); + + if ($host != '') { + $host = explode('.', $host); + + foreach ($host as $pos => $hostPart) { + // Host is longer than a route, it's not a match + if (!array_key_exists($pos, $this->_parts)) { + return false; + } + + $name = isset($this->_variables[$pos]) ? $this->_variables[$pos] : null; + $hostPart = urldecode($hostPart); + + // If it's a static part, match directly + if ($name === null && $this->_parts[$pos] != $hostPart) { + return false; + } + + // If it's a variable with requirement, match a regex. If not - everything matches + if ($this->_parts[$pos] !== null && !preg_match($this->_regexDelimiter . '^' . $this->_parts[$pos] . '$' . $this->_regexDelimiter . 'iu', $hostPart)) { + return false; + } + + // If it's a variable store it's value for later + if ($name !== null) { + $values[$name] = $hostPart; + } else { + $hostStaticCount++; + } + } + } + + // Check if all static mappings have been matched + if ($this->_staticCount != $hostStaticCount) { + return false; + } + + $return = $values + $this->_defaults; + + // Check if all map variables have been initialized + foreach ($this->_variables as $var) { + if (!array_key_exists($var, $return)) { + return false; + } + } + + $this->_values = $values; + + return $return; + + } + + /** + * Assembles user submitted parameters forming a hostname defined by this route + * + * @param array $data An array of variable and value pairs used as parameters + * @param boolean $reset Whether or not to set route defaults with those provided in $data + * @return string Route path with user submitted parameters + */ + public function assemble($data = array(), $reset = false, $encode = false, $partial = false) + { + $host = array(); + $flag = false; + + foreach ($this->_parts as $key => $part) { + $name = isset($this->_variables[$key]) ? $this->_variables[$key] : null; + + $useDefault = false; + if (isset($name) && array_key_exists($name, $data) && $data[$name] === null) { + $useDefault = true; + } + + if (isset($name)) { + if (isset($data[$name]) && !$useDefault) { + $host[$key] = $data[$name]; + unset($data[$name]); + } elseif (!$reset && !$useDefault && isset($this->_values[$name])) { + $host[$key] = $this->_values[$name]; + } elseif (isset($this->_defaults[$name])) { + $host[$key] = $this->_defaults[$name]; + } else { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception($name . ' is not specified'); + } + } else { + $host[$key] = $part; + } + } + + $return = ''; + + foreach (array_reverse($host, true) as $key => $value) { + if ($flag || !isset($this->_variables[$key]) || $value !== $this->getDefault($this->_variables[$key]) || $partial) { + if ($encode) $value = urlencode($value); + $return = '.' . $value . $return; + $flag = true; + } + } + + $url = trim($return, '.'); + + if ($this->_scheme !== null) { + $scheme = $this->_scheme; + } else { + $request = $this->getRequest(); + if ($request instanceof Zend_Controller_Request_Http) { + $scheme = $request->getScheme(); + } else { + $scheme = 'http'; + } + } + + $url = $scheme . '://' . $url; + + return $url; + } + + /** + * Return a single parameter of route's defaults + * + * @param string $name Array key of the parameter + * @return string Previously set default + */ + public function getDefault($name) { + if (isset($this->_defaults[$name])) { + return $this->_defaults[$name]; + } + return null; + } + + /** + * Return an array of defaults + * + * @return array Route defaults + */ + public function getDefaults() { + return $this->_defaults; + } + + /** + * Get all variables which are used by the route + * + * @return array + */ + public function getVariables() + { + return $this->_variables; + } +} diff --git a/library/Zend/Controller/Router/Route/Interface.php b/library/Zend/Controller/Router/Route/Interface.php new file mode 100644 index 00000000..36d6e34c --- /dev/null +++ b/library/Zend/Controller/Router/Route/Interface.php @@ -0,0 +1,37 @@ +defaults instanceof Zend_Config) ? $config->defaults->toArray() : array(); + $dispatcher = $frontController->getDispatcher(); + $request = $frontController->getRequest(); + + return new self($defs, $dispatcher, $request); + } + + /** + * Constructor + * + * @param array $defaults Defaults for map variables with keys as variable names + * @param Zend_Controller_Dispatcher_Interface $dispatcher Dispatcher object + * @param Zend_Controller_Request_Abstract $request Request object + */ + public function __construct(array $defaults = array(), + Zend_Controller_Dispatcher_Interface $dispatcher = null, + Zend_Controller_Request_Abstract $request = null) + { + $this->_defaults = $defaults; + + if (isset($request)) { + $this->_request = $request; + } + + if (isset($dispatcher)) { + $this->_dispatcher = $dispatcher; + } + } + + /** + * Set request keys based on values in request object + * + * @return void + */ + protected function _setRequestKeys() + { + if (null !== $this->_request) { + $this->_moduleKey = $this->_request->getModuleKey(); + $this->_controllerKey = $this->_request->getControllerKey(); + $this->_actionKey = $this->_request->getActionKey(); + } + + if (null !== $this->_dispatcher) { + $this->_defaults += array( + $this->_controllerKey => $this->_dispatcher->getDefaultControllerName(), + $this->_actionKey => $this->_dispatcher->getDefaultAction(), + $this->_moduleKey => $this->_dispatcher->getDefaultModule() + ); + } + + $this->_keysSet = true; + } + + /** + * Matches a user submitted path. Assigns and returns an array of variables + * on a successful match. + * + * If a request object is registered, it uses its setModuleName(), + * setControllerName(), and setActionName() accessors to set those values. + * Always returns the values as an array. + * + * @param string $path Path used to match against this routing map + * @return array An array of assigned values or a false on a mismatch + */ + public function match($path, $partial = false) + { + $this->_setRequestKeys(); + + $values = array(); + $params = array(); + + if (!$partial) { + $path = trim($path, self::URI_DELIMITER); + } else { + $matchedPath = $path; + } + + if ($path != '') { + $path = explode(self::URI_DELIMITER, $path); + + if ($this->_dispatcher && $this->_dispatcher->isValidModule($path[0])) { + $values[$this->_moduleKey] = array_shift($path); + $this->_moduleValid = true; + } + + if (count($path) && !empty($path[0])) { + $values[$this->_controllerKey] = array_shift($path); + } + + if (count($path) && !empty($path[0])) { + $values[$this->_actionKey] = array_shift($path); + } + + if ($numSegs = count($path)) { + for ($i = 0; $i < $numSegs; $i = $i + 2) { + $key = urldecode($path[$i]); + $val = isset($path[$i + 1]) ? urldecode($path[$i + 1]) : null; + $params[$key] = (isset($params[$key]) ? (array_merge((array) $params[$key], array($val))): $val); + } + } + } + + if ($partial) { + $this->setMatchedPath($matchedPath); + } + + $this->_values = $values + $params; + + return $this->_values + $this->_defaults; + } + + /** + * Assembles user submitted parameters forming a URL path defined by this route + * + * @param array $data An array of variable and value pairs used as parameters + * @param bool $reset Weither to reset the current params + * @return string Route path with user submitted parameters + */ + public function assemble($data = array(), $reset = false, $encode = true, $partial = false) + { + if (!$this->_keysSet) { + $this->_setRequestKeys(); + } + + $params = (!$reset) ? $this->_values : array(); + + foreach ($data as $key => $value) { + if ($value !== null) { + $params[$key] = $value; + } elseif (isset($params[$key])) { + unset($params[$key]); + } + } + + $params += $this->_defaults; + + $url = ''; + + if ($this->_moduleValid || array_key_exists($this->_moduleKey, $data)) { + if ($params[$this->_moduleKey] != $this->_defaults[$this->_moduleKey]) { + $module = $params[$this->_moduleKey]; + } + } + unset($params[$this->_moduleKey]); + + $controller = $params[$this->_controllerKey]; + unset($params[$this->_controllerKey]); + + $action = $params[$this->_actionKey]; + unset($params[$this->_actionKey]); + + foreach ($params as $key => $value) { + $key = ($encode) ? urlencode($key) : $key; + if (is_array($value)) { + foreach ($value as $arrayValue) { + $arrayValue = ($encode) ? urlencode($arrayValue) : $arrayValue; + $url .= self::URI_DELIMITER . $key; + $url .= self::URI_DELIMITER . $arrayValue; + } + } else { + if ($encode) $value = urlencode($value); + $url .= self::URI_DELIMITER . $key; + $url .= self::URI_DELIMITER . $value; + } + } + + if (!empty($url) || $action !== $this->_defaults[$this->_actionKey]) { + if ($encode) $action = urlencode($action); + $url = self::URI_DELIMITER . $action . $url; + } + + if (!empty($url) || $controller !== $this->_defaults[$this->_controllerKey]) { + if ($encode) $controller = urlencode($controller); + $url = self::URI_DELIMITER . $controller . $url; + } + + if (isset($module)) { + if ($encode) $module = urlencode($module); + $url = self::URI_DELIMITER . $module . $url; + } + + return ltrim($url, self::URI_DELIMITER); + } + + /** + * Return a single parameter of route's defaults + * + * @param string $name Array key of the parameter + * @return string Previously set default + */ + public function getDefault($name) { + if (isset($this->_defaults[$name])) { + return $this->_defaults[$name]; + } + } + + /** + * Return an array of defaults + * + * @return array Route defaults + */ + public function getDefaults() { + return $this->_defaults; + } + +} diff --git a/library/Zend/Controller/Router/Route/Regex.php b/library/Zend/Controller/Router/Route/Regex.php new file mode 100644 index 00000000..72ac23c5 --- /dev/null +++ b/library/Zend/Controller/Router/Route/Regex.php @@ -0,0 +1,269 @@ +defaults instanceof Zend_Config) ? $config->defaults->toArray() : array(); + $map = ($config->map instanceof Zend_Config) ? $config->map->toArray() : array(); + $reverse = (isset($config->reverse)) ? $config->reverse : null; + return new self($config->route, $defs, $map, $reverse); + } + + public function __construct($route, $defaults = array(), $map = array(), $reverse = null) + { + $this->_regex = $route; + $this->_defaults = (array) $defaults; + $this->_map = (array) $map; + $this->_reverse = $reverse; + } + + public function getVersion() { + return 1; + } + + /** + * Matches a user submitted path with a previously defined route. + * Assigns and returns an array of defaults on a successful match. + * + * @param string $path Path used to match against this routing map + * @return array|false An array of assigned values or a false on a mismatch + */ + public function match($path, $partial = false) + { + if (!$partial) { + $path = trim(urldecode($path), self::URI_DELIMITER); + $regex = '#^' . $this->_regex . '$#i'; + } else { + $regex = '#^' . $this->_regex . '#i'; + } + + $res = preg_match($regex, $path, $values); + + if ($res === 0) { + return false; + } + + if ($partial) { + $this->setMatchedPath($values[0]); + } + + // array_filter_key()? Why isn't this in a standard PHP function set yet? :) + foreach ($values as $i => $value) { + if (!is_int($i) || $i === 0) { + unset($values[$i]); + } + } + + $this->_values = $values; + + $values = $this->_getMappedValues($values); + $defaults = $this->_getMappedValues($this->_defaults, false, true); + $return = $values + $defaults; + + return $return; + } + + /** + * Maps numerically indexed array values to it's associative mapped counterpart. + * Or vice versa. Uses user provided map array which consists of index => name + * parameter mapping. If map is not found, it returns original array. + * + * Method strips destination type of keys form source array. Ie. if source array is + * indexed numerically then every associative key will be stripped. Vice versa if reversed + * is set to true. + * + * @param array $values Indexed or associative array of values to map + * @param boolean $reversed False means translation of index to association. True means reverse. + * @param boolean $preserve Should wrong type of keys be preserved or stripped. + * @return array An array of mapped values + */ + protected function _getMappedValues($values, $reversed = false, $preserve = false) + { + if (count($this->_map) == 0) { + return $values; + } + + $return = array(); + + foreach ($values as $key => $value) { + if (is_int($key) && !$reversed) { + if (array_key_exists($key, $this->_map)) { + $index = $this->_map[$key]; + } elseif (false === ($index = array_search($key, $this->_map))) { + $index = $key; + } + $return[$index] = $values[$key]; + } elseif ($reversed) { + $index = $key; + if (!is_int($key)) { + if (array_key_exists($key, $this->_map)) { + $index = $this->_map[$key]; + } else { + $index = array_search($key, $this->_map, true); + } + } + if (false !== $index) { + $return[$index] = $values[$key]; + } + } elseif ($preserve) { + $return[$key] = $value; + } + } + + return $return; + } + + /** + * Assembles a URL path defined by this route + * + * @param array $data An array of name (or index) and value pairs used as parameters + * @return string Route path with user submitted parameters + */ + public function assemble($data = array(), $reset = false, $encode = false, $partial = false) + { + if ($this->_reverse === null) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception('Cannot assemble. Reversed route is not specified.'); + } + + $defaultValuesMapped = $this->_getMappedValues($this->_defaults, true, false); + $matchedValuesMapped = $this->_getMappedValues($this->_values, true, false); + $dataValuesMapped = $this->_getMappedValues($data, true, false); + + // handle resets, if so requested (By null value) to do so + if (($resetKeys = array_search(null, $dataValuesMapped, true)) !== false) { + foreach ((array) $resetKeys as $resetKey) { + if (isset($matchedValuesMapped[$resetKey])) { + unset($matchedValuesMapped[$resetKey]); + unset($dataValuesMapped[$resetKey]); + } + } + } + + // merge all the data together, first defaults, then values matched, then supplied + $mergedData = $defaultValuesMapped; + $mergedData = $this->_arrayMergeNumericKeys($mergedData, $matchedValuesMapped); + $mergedData = $this->_arrayMergeNumericKeys($mergedData, $dataValuesMapped); + + if ($encode) { + foreach ($mergedData as $key => &$value) { + $value = urlencode($value); + } + } + + ksort($mergedData); + + $return = @vsprintf($this->_reverse, $mergedData); + + if ($return === false) { + require_once 'Zend/Controller/Router/Exception.php'; + throw new Zend_Controller_Router_Exception('Cannot assemble. Too few arguments?'); + } + + return $return; + + } + + /** + * Return a single parameter of route's defaults + * + * @param string $name Array key of the parameter + * @return string Previously set default + */ + public function getDefault($name) { + if (isset($this->_defaults[$name])) { + return $this->_defaults[$name]; + } + } + + /** + * Return an array of defaults + * + * @return array Route defaults + */ + public function getDefaults() { + return $this->_defaults; + } + + /** + * Get all variables which are used by the route + * + * @return array + */ + public function getVariables() + { + $variables = array(); + + foreach ($this->_map as $key => $value) { + if (is_numeric($key)) { + $variables[] = $value; + } else { + $variables[] = $key; + } + } + + return $variables; + } + + /** + * _arrayMergeNumericKeys() - allows for a strict key (numeric's included) array_merge. + * php's array_merge() lacks the ability to merge with numeric keys. + * + * @param array $array1 + * @param array $array2 + * @return array + */ + protected function _arrayMergeNumericKeys(Array $array1, Array $array2) + { + $returnArray = $array1; + foreach ($array2 as $array2Index => $array2Value) { + $returnArray[$array2Index] = $array2Value; + } + return $returnArray; + } + + +} diff --git a/library/Zend/Controller/Router/Route/Static.php b/library/Zend/Controller/Router/Route/Static.php new file mode 100644 index 00000000..46d7c606 --- /dev/null +++ b/library/Zend/Controller/Router/Route/Static.php @@ -0,0 +1,127 @@ +defaults instanceof Zend_Config) ? $config->defaults->toArray() : array(); + return new self($config->route, $defs); + } + + /** + * Prepares the route for mapping. + * + * @param string $route Map used to match with later submitted URL path + * @param array $defaults Defaults for map variables with keys as variable names + */ + public function __construct($route, $defaults = array()) + { + $this->_route = trim($route, self::URI_DELIMITER); + $this->_defaults = (array) $defaults; + } + + /** + * Matches a user submitted path with a previously defined route. + * Assigns and returns an array of defaults on a successful match. + * + * @param string $path Path used to match against this routing map + * @return array|false An array of assigned values or a false on a mismatch + */ + public function match($path, $partial = false) + { + if ($partial) { + if ((empty($path) && empty($this->_route)) + || (substr($path, 0, strlen($this->_route)) === $this->_route) + ) { + $this->setMatchedPath($this->_route); + return $this->_defaults; + } + } else { + if (trim($path, self::URI_DELIMITER) == $this->_route) { + return $this->_defaults; + } + } + + return false; + } + + /** + * Assembles a URL path defined by this route + * + * @param array $data An array of variable and value pairs used as parameters + * @return string Route path with user submitted parameters + */ + public function assemble($data = array(), $reset = false, $encode = false, $partial = false) + { + return $this->_route; + } + + /** + * Return a single parameter of route's defaults + * + * @param string $name Array key of the parameter + * @return string Previously set default + */ + public function getDefault($name) { + if (isset($this->_defaults[$name])) { + return $this->_defaults[$name]; + } + return null; + } + + /** + * Return an array of defaults + * + * @return array Route defaults + */ + public function getDefaults() { + return $this->_defaults; + } + +} diff --git a/library/Zend/Crypt.php b/library/Zend/Crypt.php new file mode 100644 index 00000000..cc3405a8 --- /dev/null +++ b/library/Zend/Crypt.php @@ -0,0 +1,168 @@ +setPrime($prime); + $this->setGenerator($generator); + if ($privateKey !== null) { + $this->setPrivateKey($privateKey, $privateKeyType); + } + $this->setBigIntegerMath(); + } + + /** + * Generate own public key. If a private number has not already been + * set, one will be generated at this stage. + * + * @return Zend_Crypt_DiffieHellman + */ + public function generateKeys() + { + if (function_exists('openssl_dh_compute_key') && self::$useOpenssl !== false) { + $details = array(); + $details['p'] = $this->getPrime(); + $details['g'] = $this->getGenerator(); + if ($this->hasPrivateKey()) { + $details['priv_key'] = $this->getPrivateKey(); + } + $opensslKeyResource = openssl_pkey_new( array('dh' => $details) ); + $data = openssl_pkey_get_details($opensslKeyResource); + $this->setPrivateKey($data['dh']['priv_key'], self::BINARY); + $this->setPublicKey($data['dh']['pub_key'], self::BINARY); + } else { + // Private key is lazy generated in the absence of PHP 5.3's ext/openssl + $publicKey = $this->_math->powmod($this->getGenerator(), $this->getPrivateKey(), $this->getPrime()); + $this->setPublicKey($publicKey); + } + return $this; + } + + /** + * Setter for the value of the public number + * + * @param string $number + * @param string $type + * @return Zend_Crypt_DiffieHellman + */ + public function setPublicKey($number, $type = self::NUMBER) + { + if ($type == self::BINARY) { + $number = $this->_math->fromBinary($number); + } + if (!preg_match("/^\d+$/", $number)) { + require_once('Zend/Crypt/DiffieHellman/Exception.php'); + throw new Zend_Crypt_DiffieHellman_Exception('invalid parameter; not a positive natural number'); + } + $this->_publicKey = (string) $number; + return $this; + } + + /** + * Returns own public key for communication to the second party to this + * transaction. + * + * @param string $type + * @return string + */ + public function getPublicKey($type = self::NUMBER) + { + if ($this->_publicKey === null) { + require_once 'Zend/Crypt/DiffieHellman/Exception.php'; + throw new Zend_Crypt_DiffieHellman_Exception('A public key has not yet been generated using a prior call to generateKeys()'); + } + if ($type == self::BINARY) { + return $this->_math->toBinary($this->_publicKey); + } elseif ($type == self::BTWOC) { + return $this->_math->btwoc($this->_math->toBinary($this->_publicKey)); + } + return $this->_publicKey; + } + + /** + * Compute the shared secret key based on the public key received from the + * the second party to this transaction. This should agree to the secret + * key the second party computes on our own public key. + * Once in agreement, the key is known to only to both parties. + * By default, the function expects the public key to be in binary form + * which is the typical format when being transmitted. + * + * If you need the binary form of the shared secret key, call + * getSharedSecretKey() with the optional parameter for Binary output. + * + * @param string $publicKey + * @param string $type + * @return mixed + */ + public function computeSecretKey($publicKey, $type = self::NUMBER, $output = self::NUMBER) + { + if ($type == self::BINARY) { + $publicKey = $this->_math->fromBinary($publicKey); + } + if (!preg_match("/^\d+$/", $publicKey)) { + require_once('Zend/Crypt/DiffieHellman/Exception.php'); + throw new Zend_Crypt_DiffieHellman_Exception('invalid parameter; not a positive natural number'); + } + if (function_exists('openssl_dh_compute_key') && self::$useOpenssl !== false) { + $this->_secretKey = openssl_dh_compute_key($publicKey, $this->getPublicKey()); + } else { + $this->_secretKey = $this->_math->powmod($publicKey, $this->getPrivateKey(), $this->getPrime()); + } + return $this->getSharedSecretKey($output); + } + + /** + * Return the computed shared secret key from the DiffieHellman transaction + * + * @param string $type + * @return string + */ + public function getSharedSecretKey($type = self::NUMBER) + { + if (!isset($this->_secretKey)) { + require_once('Zend/Crypt/DiffieHellman/Exception.php'); + throw new Zend_Crypt_DiffieHellman_Exception('A secret key has not yet been computed; call computeSecretKey()'); + } + if ($type == self::BINARY) { + return $this->_math->toBinary($this->_secretKey); + } elseif ($type == self::BTWOC) { + return $this->_math->btwoc($this->_math->toBinary($this->_secretKey)); + } + return $this->_secretKey; + } + + /** + * Setter for the value of the prime number + * + * @param string $number + * @return Zend_Crypt_DiffieHellman + */ + public function setPrime($number) + { + if (!preg_match("/^\d+$/", $number) || $number < 11) { + require_once('Zend/Crypt/DiffieHellman/Exception.php'); + throw new Zend_Crypt_DiffieHellman_Exception('invalid parameter; not a positive natural number or too small: should be a large natural number prime'); + } + $this->_prime = (string) $number; + return $this; + } + + /** + * Getter for the value of the prime number + * + * @return string + */ + public function getPrime() + { + if (!isset($this->_prime)) { + require_once('Zend/Crypt/DiffieHellman/Exception.php'); + throw new Zend_Crypt_DiffieHellman_Exception('No prime number has been set'); + } + return $this->_prime; + } + + + /** + * Setter for the value of the generator number + * + * @param string $number + * @return Zend_Crypt_DiffieHellman + */ + public function setGenerator($number) + { + if (!preg_match("/^\d+$/", $number) || $number < 2) { + require_once('Zend/Crypt/DiffieHellman/Exception.php'); + throw new Zend_Crypt_DiffieHellman_Exception('invalid parameter; not a positive natural number greater than 1'); + } + $this->_generator = (string) $number; + return $this; + } + + /** + * Getter for the value of the generator number + * + * @return string + */ + public function getGenerator() + { + if (!isset($this->_generator)) { + require_once('Zend/Crypt/DiffieHellman/Exception.php'); + throw new Zend_Crypt_DiffieHellman_Exception('No generator number has been set'); + } + return $this->_generator; + } + + /** + * Setter for the value of the private number + * + * @param string $number + * @param string $type + * @return Zend_Crypt_DiffieHellman + */ + public function setPrivateKey($number, $type = self::NUMBER) + { + if ($type == self::BINARY) { + $number = $this->_math->fromBinary($number); + } + if (!preg_match("/^\d+$/", $number)) { + require_once('Zend/Crypt/DiffieHellman/Exception.php'); + throw new Zend_Crypt_DiffieHellman_Exception('invalid parameter; not a positive natural number'); + } + $this->_privateKey = (string) $number; + return $this; + } + + /** + * Getter for the value of the private number + * + * @param string $type + * @return string + */ + public function getPrivateKey($type = self::NUMBER) + { + if (!$this->hasPrivateKey()) { + $this->setPrivateKey($this->_generatePrivateKey(), self::BINARY); + } + if ($type == self::BINARY) { + return $this->_math->toBinary($this->_privateKey); + } elseif ($type == self::BTWOC) { + return $this->_math->btwoc($this->_math->toBinary($this->_privateKey)); + } + return $this->_privateKey; + } + + /** + * Check whether a private key currently exists. + * + * @return boolean + */ + public function hasPrivateKey() + { + return isset($this->_privateKey); + } + + /** + * Setter to pass an extension parameter which is used to create + * a specific BigInteger instance for a specific extension type. + * Allows manual setting of the class in case of an extension + * problem or bug. + * + * @param string $extension + * @return void + */ + public function setBigIntegerMath($extension = null) + { + /** + * @see Zend_Crypt_Math + */ + require_once 'Zend/Crypt/Math.php'; + $this->_math = new Zend_Crypt_Math($extension); + } + + /** + * In the event a private number/key has not been set by the user, + * or generated by ext/openssl, a best attempt will be made to + * generate a random key. Having a random number generator installed + * on linux/bsd is highly recommended! The alternative is not recommended + * for production unless without any other option. + * + * @return string + */ + protected function _generatePrivateKey() + { + $rand = $this->_math->rand($this->getGenerator(), $this->getPrime()); + return $rand; + } + +} diff --git a/library/Zend/Crypt/DiffieHellman/Exception.php b/library/Zend/Crypt/DiffieHellman/Exception.php new file mode 100644 index 00000000..34d85b32 --- /dev/null +++ b/library/Zend/Crypt/DiffieHellman/Exception.php @@ -0,0 +1,36 @@ +80 using internal algo) + * @todo Check if mhash() is a required alternative (will be PECL-only soon) + * @category Zend + * @package Zend_Crypt + * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Crypt_Hmac extends Zend_Crypt +{ + + /** + * The key to use for the hash + * + * @var string + */ + protected static $_key = null; + + /** + * pack() format to be used for current hashing method + * + * @var string + */ + protected static $_packFormat = null; + + /** + * Hashing algorithm; can be the md5/sha1 functions or any algorithm name + * listed in the output of PHP 5.1.2+ hash_algos(). + * + * @var string + */ + protected static $_hashAlgorithm = 'md5'; + + /** + * List of algorithms supported my mhash() + * + * @var array + */ + protected static $_supportedMhashAlgorithms = array('adler32',' crc32', 'crc32b', 'gost', + 'haval128', 'haval160', 'haval192', 'haval256', 'md4', 'md5', 'ripemd160', + 'sha1', 'sha256', 'tiger', 'tiger128', 'tiger160'); + + /** + * Constants representing the output mode of the hash algorithm + */ + const STRING = 'string'; + const BINARY = 'binary'; + + /** + * Performs a HMAC computation given relevant details such as Key, Hashing + * algorithm, the data to compute MAC of, and an output format of String, + * Binary notation or BTWOC. + * + * @param string $key + * @param string $hash + * @param string $data + * @param string $output + * @param boolean $internal + * @return string + */ + public static function compute($key, $hash, $data, $output = self::STRING) + { + // set the key + if (!isset($key) || empty($key)) { + require_once 'Zend/Crypt/Hmac/Exception.php'; + throw new Zend_Crypt_Hmac_Exception('provided key is null or empty'); + } + self::$_key = $key; + + // set the hash + self::_setHashAlgorithm($hash); + + // perform hashing and return + return self::_hash($data, $output); + } + + /** + * Setter for the hash method. + * + * @param string $hash + * @return Zend_Crypt_Hmac + */ + protected static function _setHashAlgorithm($hash) + { + if (!isset($hash) || empty($hash)) { + require_once 'Zend/Crypt/Hmac/Exception.php'; + throw new Zend_Crypt_Hmac_Exception('provided hash string is null or empty'); + } + + $hash = strtolower($hash); + $hashSupported = false; + + if (function_exists('hash_algos') && in_array($hash, hash_algos())) { + $hashSupported = true; + } + + if ($hashSupported === false && function_exists('mhash') && in_array($hash, self::$_supportedAlgosMhash)) { + $hashSupported = true; + } + + if ($hashSupported === false) { + require_once 'Zend/Crypt/Hmac/Exception.php'; + throw new Zend_Crypt_Hmac_Exception('hash algorithm provided is not supported on this PHP installation; please enable the hash or mhash extensions'); + } + self::$_hashAlgorithm = $hash; + } + + /** + * Perform HMAC and return the keyed data + * + * @param string $data + * @param string $output + * @param bool $internal Option to not use hash() functions for testing + * @return string + */ + protected static function _hash($data, $output = self::STRING, $internal = false) + { + if (function_exists('hash_hmac')) { + if ($output == self::BINARY) { + return hash_hmac(self::$_hashAlgorithm, $data, self::$_key, 1); + } + return hash_hmac(self::$_hashAlgorithm, $data, self::$_key); + } + + if (function_exists('mhash')) { + if ($output == self::BINARY) { + return mhash(self::_getMhashDefinition(self::$_hashAlgorithm), $data, self::$_key); + } + $bin = mhash(self::_getMhashDefinition(self::$_hashAlgorithm), $data, self::$_key); + return bin2hex($bin); + } + } + + /** + * Since MHASH accepts an integer constant representing the hash algorithm + * we need to make a small detour to get the correct integer matching our + * algorithm's name. + * + * @param string $hashAlgorithm + * @return integer + */ + protected static function _getMhashDefinition($hashAlgorithm) + { + for ($i = 0; $i <= mhash_count(); $i++) + { + $types[mhash_get_hash_name($i)] = $i; + } + return $types[strtoupper($hashAlgorithm)]; + } + +} \ No newline at end of file diff --git a/library/Zend/Crypt/Hmac/Exception.php b/library/Zend/Crypt/Hmac/Exception.php new file mode 100644 index 00000000..ebb8082a --- /dev/null +++ b/library/Zend/Crypt/Hmac/Exception.php @@ -0,0 +1,36 @@ + 127) { + return "\x00" . $long; + } + return $long; + } + + /** + * Translate a binary form into a big integer string + * + * @param string $binary + * @return string + */ + public function fromBinary($binary) { + return $this->_math->binaryToInteger($binary); + } + + /** + * Translate a big integer string into a binary form + * + * @param string $integer + * @return string + */ + public function toBinary($integer) + { + return $this->_math->integerToBinary($integer); + } + +} diff --git a/library/Zend/Crypt/Math/BigInteger.php b/library/Zend/Crypt/Math/BigInteger.php new file mode 100644 index 00000000..750ae701 --- /dev/null +++ b/library/Zend/Crypt/Math/BigInteger.php @@ -0,0 +1,117 @@ +_loadAdapter($extension); + } + + /** + * Redirect all public method calls to the wrapped extension object. + * + * @param string $methodName + * @param array $args + * @throws Zend_Crypt_Math_BigInteger_Exception + */ + public function __call($methodName, $args) + { + if(!method_exists($this->_math, $methodName)) { + require_once 'Zend/Crypt/Math/BigInteger/Exception.php'; + throw new Zend_Crypt_Math_BigInteger_Exception('invalid method call: ' . get_class($this->_math) . '::' . $methodName . '() does not exist'); + } + return call_user_func_array(array($this->_math, $methodName), $args); + } + + /** + * @param string $extension + * @throws Zend_Crypt_Math_BigInteger_Exception + */ + protected function _loadAdapter($extension = null) + { + if ($extension === null) { + if (extension_loaded('gmp')) { + $extension = 'gmp'; + //} elseif (extension_loaded('big_int')) { + // $extension = 'big_int'; + } else { + $extension = 'bcmath'; + } + } + if($extension == 'gmp' && extension_loaded('gmp')) { + require_once 'Zend/Crypt/Math/BigInteger/Gmp.php'; + $this->_math = new Zend_Crypt_Math_BigInteger_Gmp(); + //} elseif($extension == 'bigint' && extension_loaded('big_int')) { + // require_once 'Zend/Crypt_Math/BigInteger/Bigint.php'; + // $this->_math = new Zend_Crypt_Math_BigInteger_Bigint(); + } elseif ($extension == 'bcmath' && extension_loaded('bcmath')) { + require_once 'Zend/Crypt/Math/BigInteger/Bcmath.php'; + $this->_math = new Zend_Crypt_Math_BigInteger_Bcmath(); + } else { + require_once 'Zend/Crypt/Math/BigInteger/Exception.php'; + throw new Zend_Crypt_Math_BigInteger_Exception($extension . ' big integer precision math support not detected'); + } + } + +} \ No newline at end of file diff --git a/library/Zend/Crypt/Math/BigInteger/Bcmath.php b/library/Zend/Crypt/Math/BigInteger/Bcmath.php new file mode 100644 index 00000000..ca67d14a --- /dev/null +++ b/library/Zend/Crypt/Math/BigInteger/Bcmath.php @@ -0,0 +1,203 @@ + 0) { + $return = chr(bcmod($operand, 256)) . $return; + $operand = bcdiv($operand, 256); + } + if (ord($return[0]) > 127) { + $return = "\0" . $return; + } + return $return; + } + + /**public function integerToBinary($operand) + { + $return = ''; + while(bccomp($operand, '0')) { + $return .= chr(bcmod($operand, '256')); + $operand = bcdiv($operand, '256'); + } + return $return; + }**/ // Prior version for referenced offset + + + public function hexToDecimal($operand) + { + $return = '0'; + while(strlen($hex)) { + $hex = hexdec(substr($operand, 0, 4)); + $dec = bcadd(bcmul($return, 65536), $hex); + $operand = substr($operand, 4); + } + return $return; + } + +} \ No newline at end of file diff --git a/library/Zend/Crypt/Math/BigInteger/Exception.php b/library/Zend/Crypt/Math/BigInteger/Exception.php new file mode 100644 index 00000000..bebfdae9 --- /dev/null +++ b/library/Zend/Crypt/Math/BigInteger/Exception.php @@ -0,0 +1,36 @@ + '7') { + $bigInt = '00' . $bigInt; + } + $return = pack("H*", $bigInt); + return $return; + } + + + public function hexToDecimal($operand) + { + $return = '0'; + while(strlen($hex)) { + $hex = hexdec(substr($operand, 0, 4)); + $dec = gmp_add(gmp_mul($return, 65536), $hex); + $operand = substr($operand, 4); + } + return $return; + } + +} \ No newline at end of file diff --git a/library/Zend/Crypt/Math/BigInteger/Interface.php b/library/Zend/Crypt/Math/BigInteger/Interface.php new file mode 100644 index 00000000..9dace60a --- /dev/null +++ b/library/Zend/Crypt/Math/BigInteger/Interface.php @@ -0,0 +1,51 @@ +_hashAlgorithm = OPENSSL_ALGO_SHA1; + + if (isset($options)) { + $this->setOptions($options); + } + } + + public function setOptions(array $options) + { + if (isset($options['passPhrase'])) { + $this->_passPhrase = $options['passPhrase']; + } + foreach ($options as $option=>$value) { + switch ($option) { + case 'pemString': + $this->setPemString($value); + break; + case 'pemPath': + $this->setPemPath($value); + break; + case 'certificateString': + $this->setCertificateString($value); + break; + case 'certificatePath': + $this->setCertificatePath($value); + break; + case 'hashAlgorithm': + $this->setHashAlgorithm($value); + break; + } + } + } + + public function getPrivateKey() + { + return $this->_privateKey; + } + + public function getPublicKey() + { + return $this->_publicKey; + } + + /** + * @param string $data + * @param Zend_Crypt_Rsa_Key_Private $privateKey + * @param string $format + * @return string + */ + public function sign($data, Zend_Crypt_Rsa_Key_Private $privateKey = null, $format = null) + { + $signature = ''; + if (isset($privateKey)) { + $opensslKeyResource = $privateKey->getOpensslKeyResource(); + } else { + $opensslKeyResource = $this->_privateKey->getOpensslKeyResource(); + } + $result = openssl_sign( + $data, $signature, + $opensslKeyResource, + $this->getHashAlgorithm() + ); + if ($format == self::BASE64) { + return base64_encode($signature); + } + return $signature; + } + + /** + * @param string $data + * @param string $signature + * @param string $format + * @return string + */ + public function verifySignature($data, $signature, $format = null) + { + if ($format == self::BASE64) { + $signature = base64_decode($signature); + } + $result = openssl_verify($data, $signature, + $this->getPublicKey()->getOpensslKeyResource(), + $this->getHashAlgorithm()); + return $result; + } + + /** + * @param string $data + * @param Zend_Crypt_Rsa_Key $key + * @param string $format + * @return string + */ + public function encrypt($data, Zend_Crypt_Rsa_Key $key, $format = null) + { + $encrypted = ''; + $function = 'openssl_public_encrypt'; + if ($key instanceof Zend_Crypt_Rsa_Key_Private) { + $function = 'openssl_private_encrypt'; + } + $function($data, $encrypted, $key->getOpensslKeyResource()); + if ($format == self::BASE64) { + return base64_encode($encrypted); + } + return $encrypted; + } + + /** + * @param string $data + * @param Zend_Crypt_Rsa_Key $key + * @param string $format + * @return string + */ + public function decrypt($data, Zend_Crypt_Rsa_Key $key, $format = null) + { + $decrypted = ''; + if ($format == self::BASE64) { + $data = base64_decode($data); + } + $function = 'openssl_private_decrypt'; + if ($key instanceof Zend_Crypt_Rsa_Key_Public) { + $function = 'openssl_public_decrypt'; + } + $function($data, $decrypted, $key->getOpensslKeyResource()); + return $decrypted; + } + + public function generateKeys(array $configargs = null) + { + $config = null; + $passPhrase = null; + if ($configargs !== null) { + if (isset($configargs['passPhrase'])) { + $passPhrase = $configargs['passPhrase']; + unset($configargs['passPhrase']); + } + $config = $this->_parseConfigArgs($configargs); + } + $privateKey = null; + $publicKey = null; + $resource = openssl_pkey_new($config); + // above fails on PHP 5.3 + openssl_pkey_export($resource, $private, $passPhrase); + $privateKey = new Zend_Crypt_Rsa_Key_Private($private, $passPhrase); + $details = openssl_pkey_get_details($resource); + $publicKey = new Zend_Crypt_Rsa_Key_Public($details['key']); + $return = new ArrayObject(array( + 'privateKey'=>$privateKey, + 'publicKey'=>$publicKey + ), ArrayObject::ARRAY_AS_PROPS); + return $return; + } + + /** + * @param string $value + */ + public function setPemString($value) + { + $this->_pemString = $value; + try { + $this->_privateKey = new Zend_Crypt_Rsa_Key_Private($this->_pemString, $this->_passPhrase); + $this->_publicKey = $this->_privateKey->getPublicKey(); + } catch (Zend_Crypt_Exception $e) { + $this->_privateKey = null; + $this->_publicKey = new Zend_Crypt_Rsa_Key_Public($this->_pemString); + } + } + + public function setPemPath($value) + { + $this->_pemPath = $value; + $this->setPemString(file_get_contents($this->_pemPath)); + } + + public function setCertificateString($value) + { + $this->_certificateString = $value; + $this->_publicKey = new Zend_Crypt_Rsa_Key_Public($this->_certificateString, $this->_passPhrase); + } + + public function setCertificatePath($value) + { + $this->_certificatePath = $value; + $this->setCertificateString(file_get_contents($this->_certificatePath)); + } + + public function setHashAlgorithm($name) + { + switch (strtolower($name)) { + case 'md2': + $this->_hashAlgorithm = OPENSSL_ALGO_MD2; + break; + case 'md4': + $this->_hashAlgorithm = OPENSSL_ALGO_MD4; + break; + case 'md5': + $this->_hashAlgorithm = OPENSSL_ALGO_MD5; + break; + case 'sha1': + $this->_hashAlgorithm = OPENSSL_ALGO_SHA1; + break; + case 'dss1': + $this->_hashAlgorithm = OPENSSL_ALGO_DSS1; + break; + } + } + + /** + * @return string + */ + public function getPemString() + { + return $this->_pemString; + } + + public function getPemPath() + { + return $this->_pemPath; + } + + public function getCertificateString() + { + return $this->_certificateString; + } + + public function getCertificatePath() + { + return $this->_certificatePath; + } + + public function getHashAlgorithm() + { + return $this->_hashAlgorithm; + } + + protected function _parseConfigArgs(array $config = null) + { + $configs = array(); + if (isset($config['privateKeyBits'])) { + $configs['private_key_bits'] = $config['privateKeyBits']; + } + if (!empty($configs)) { + return $configs; + } + return null; + } + +} diff --git a/library/Zend/Crypt/Rsa/Exception.php b/library/Zend/Crypt/Rsa/Exception.php new file mode 100644 index 00000000..e5b2f365 --- /dev/null +++ b/library/Zend/Crypt/Rsa/Exception.php @@ -0,0 +1,36 @@ +_opensslKeyResource; + } + + /** + * @return string + * @throws Zend_Crypt_Exception + */ + public function toString() + { + if (!empty($this->_pemString)) { + return $this->_pemString; + } elseif (!empty($this->_certificateString)) { + return $this->_certificateString; + } + /** + * @see Zend_Crypt_Exception + */ + require_once 'Zend/Crypt/Exception.php'; + throw new Zend_Crypt_Exception('No public key string representation is available'); + } + + /** + * @return string + */ + public function __toString() + { + return $this->toString(); + } + + public function count() + { + return $this->_details['bits']; + } + + public function getType() + { + return $this->_details['type']; + } +} \ No newline at end of file diff --git a/library/Zend/Crypt/Rsa/Key/Private.php b/library/Zend/Crypt/Rsa/Key/Private.php new file mode 100644 index 00000000..1fe00460 --- /dev/null +++ b/library/Zend/Crypt/Rsa/Key/Private.php @@ -0,0 +1,75 @@ +_pemString = $pemString; + $this->_parse($passPhrase); + } + + /** + * @param string $passPhrase + * @throws Zend_Crypt_Exception + */ + protected function _parse($passPhrase) + { + $result = openssl_get_privatekey($this->_pemString, $passPhrase); + if (!$result) { + /** + * @see Zend_Crypt_Exception + */ + require_once 'Zend/Crypt/Exception.php'; + throw new Zend_Crypt_Exception('Unable to load private key'); + } + $this->_opensslKeyResource = $result; + $this->_details = openssl_pkey_get_details($this->_opensslKeyResource); + } + + public function getPublicKey() + { + if ($this->_publicKey === null) { + /** + * @see Zend_Crypt_Rsa_Key_Public + */ + require_once 'Zend/Crypt/Rsa/Key/Public.php'; + $this->_publicKey = new Zend_Crypt_Rsa_Key_Public($this->_details['key']); + } + return $this->_publicKey; + } + +} \ No newline at end of file diff --git a/library/Zend/Crypt/Rsa/Key/Public.php b/library/Zend/Crypt/Rsa/Key/Public.php new file mode 100644 index 00000000..12ef2f0e --- /dev/null +++ b/library/Zend/Crypt/Rsa/Key/Public.php @@ -0,0 +1,74 @@ +_parse($string); + } + + /** + * @param string $string + * @throws Zend_Crypt_Exception + */ + protected function _parse($string) + { + if (preg_match("/^-----BEGIN CERTIFICATE-----/", $string)) { + $this->_certificateString = $string; + } else { + $this->_pemString = $string; + } + $result = openssl_get_publickey($string); + if (!$result) { + /** + * @see Zend_Crypt_Exception + */ + require_once 'Zend/Crypt/Exception.php'; + throw new Zend_Crypt_Exception('Unable to load public key'); + } + //openssl_pkey_export($result, $public); + //$this->_pemString = $public; + $this->_opensslKeyResource = $result; + $this->_details = openssl_pkey_get_details($this->_opensslKeyResource); + } + + public function getCertificate() + { + return $this->_certificateString; + } + +} \ No newline at end of file diff --git a/library/Zend/Currency.php b/library/Zend/Currency.php new file mode 100644 index 00000000..d3b51310 --- /dev/null +++ b/library/Zend/Currency.php @@ -0,0 +1,894 @@ + Position for the currency sign + * 'script' => Script for the output + * 'format' => Locale for numeric output + * 'display' => Currency detail to show + * 'precision' => Precision for the currency + * 'name' => Name for this currency + * 'currency' => 3 lettered international abbreviation + * 'symbol' => Currency symbol + * 'locale' => Locale for this currency + * 'value' => Money value + * 'service' => Exchange service to use + * + * @var array + * @see Zend_Locale + */ + protected $_options = array( + 'position' => self::STANDARD, + 'script' => null, + 'format' => null, + 'display' => self::NO_SYMBOL, + 'precision' => 2, + 'name' => null, + 'currency' => null, + 'symbol' => null, + 'locale' => null, + 'value' => 0, + 'service' => null, + 'tag' => 'Zend_Locale' + ); + + /** + * Creates a currency instance. Every supressed parameter is used from the actual or the given locale. + * + * @param string|array $options OPTIONAL Options array or currency short name + * when string is given + * @param string|Zend_Locale $locale OPTIONAL locale name + * @throws Zend_Currency_Exception When currency is invalid + */ + public function __construct($options = null, $locale = null) + { + if (is_array($options)) { + $this->setLocale($locale); + $this->setFormat($options); + } else if (Zend_Locale::isLocale($options, false, false)) { + $this->setLocale($options); + $options = $locale; + } else { + $this->setLocale($locale); + } + + // Get currency details + if (!isset($this->_options['currency']) || !is_array($options)) { + $this->_options['currency'] = self::getShortName($options, $this->_options['locale']); + } + + if (!isset($this->_options['name']) || !is_array($options)) { + $this->_options['name'] = self::getName($options, $this->_options['locale']); + } + + if (!isset($this->_options['symbol']) || !is_array($options)) { + $this->_options['symbol'] = self::getSymbol($options, $this->_options['locale']); + } + + if (($this->_options['currency'] === null) and ($this->_options['name'] === null)) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception("Currency '$options' not found"); + } + + // Get the format + if (!empty($this->_options['symbol'])) { + $this->_options['display'] = self::USE_SYMBOL; + } else if (!empty($this->_options['currency'])) { + $this->_options['display'] = self::USE_SHORTNAME; + } + } + + /** + * Returns a localized currency string + * + * @param integer|float $value OPTIONAL Currency value + * @param array $options OPTIONAL options to set temporary + * @throws Zend_Currency_Exception When the value is not a number + * @return string + */ + public function toCurrency($value = null, array $options = array()) + { + if ($value === null) { + if (is_array($options) && isset($options['value'])) { + $value = $options['value']; + } else { + $value = $this->_options['value']; + } + } + + if (is_array($value)) { + $options += $value; + if (isset($options['value'])) { + $value = $options['value']; + } + } + + // Validate the passed number + if (!(isset($value)) or (is_numeric($value) === false)) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception("Value '$value' has to be numeric"); + } + + if (isset($options['currency'])) { + if (!isset($options['locale'])) { + $options['locale'] = $this->_options['locale']; + } + + $options['currency'] = self::getShortName($options['currency'], $options['locale']); + $options['name'] = self::getName($options['currency'], $options['locale']); + $options['symbol'] = self::getSymbol($options['currency'], $options['locale']); + } + + $options = $this->_checkOptions($options) + $this->_options; + + // Format the number + $format = $options['format']; + $locale = $options['locale']; + if (empty($format)) { + $format = Zend_Locale_Data::getContent($locale, 'currencynumber'); + } else if (Zend_Locale::isLocale($format, true, false)) { + $locale = $format; + $format = Zend_Locale_Data::getContent($format, 'currencynumber'); + } + + $original = $value; + $value = Zend_Locale_Format::toNumber($value, array('locale' => $locale, + 'number_format' => $format, + 'precision' => $options['precision'])); + + if ($options['position'] !== self::STANDARD) { + $value = str_replace('¤', '', $value); + $space = ''; + if (iconv_strpos($value, ' ') !== false) { + $value = str_replace(' ', '', $value); + $space = ' '; + } + + if ($options['position'] == self::LEFT) { + $value = '¤' . $space . $value; + } else { + $value = $value . $space . '¤'; + } + } + + // Localize the number digits + if (empty($options['script']) === false) { + $value = Zend_Locale_Format::convertNumerals($value, 'Latn', $options['script']); + } + + // Get the sign to be placed next to the number + if (is_numeric($options['display']) === false) { + $sign = $options['display']; + } else { + switch($options['display']) { + case self::USE_SYMBOL: + $sign = $this->_extractPattern($options['symbol'], $original); + break; + + case self::USE_SHORTNAME: + $sign = $options['currency']; + break; + + case self::USE_NAME: + $sign = $options['name']; + break; + + default: + $sign = ''; + $value = str_replace(' ', '', $value); + break; + } + } + + $value = str_replace('¤', $sign, $value); + return $value; + } + + /** + * Internal method to extract the currency pattern + * when a choice is given based on the given value + * + * @param string $pattern + * @param float|integer $value + * @return string + */ + private function _extractPattern($pattern, $value) + { + if (strpos($pattern, '|') === false) { + return $pattern; + } + + $patterns = explode('|', $pattern); + $token = $pattern; + $value = trim(str_replace('¤', '', $value)); + krsort($patterns); + foreach($patterns as $content) { + if (strpos($content, '<') !== false) { + $check = iconv_substr($content, 0, iconv_strpos($content, '<')); + $token = iconv_substr($content, iconv_strpos($content, '<') + 1); + if ($check < $value) { + return $token; + } + } else { + $check = iconv_substr($content, 0, iconv_strpos($content, '≤')); + $token = iconv_substr($content, iconv_strpos($content, '≤') + 1); + if ($check <= $value) { + return $token; + } + } + + } + + return $token; + } + + /** + * Sets the formating options of the localized currency string + * If no parameter is passed, the standard setting of the + * actual set locale will be used + * + * @param array $options (Optional) Options to set + * @return Zend_Currency + */ + public function setFormat(array $options = array()) + { + $this->_options = $this->_checkOptions($options) + $this->_options; + return $this; + } + + /** + * Internal function for checking static given locale parameter + * + * @param string $currency (Optional) Currency name + * @param string|Zend_Locale $locale (Optional) Locale to display informations + * @throws Zend_Currency_Exception When locale contains no region + * @return string The extracted locale representation as string + */ + private function _checkParams($currency = null, $locale = null) + { + // Manage the params + if ((empty($locale)) and (!empty($currency)) and + (Zend_Locale::isLocale($currency, true, false))) { + $locale = $currency; + $currency = null; + } + + // Validate the locale and get the country short name + $country = null; + if ((Zend_Locale::isLocale($locale, true, false)) and (strlen($locale) > 4)) { + $country = substr($locale, (strpos($locale, '_') + 1)); + } else { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception("No region found within the locale '" . (string) $locale . "'"); + } + + // Get the available currencies for this country + $data = Zend_Locale_Data::getContent($locale, 'currencytoregion', $country); + if ((empty($currency) === false) and (empty($data) === false)) { + $abbreviation = $currency; + } else { + $abbreviation = $data; + } + + return array('locale' => $locale, 'currency' => $currency, 'name' => $abbreviation, 'country' => $country); + } + + /** + * Returns the actual or details of other currency symbols, + * when no symbol is available it returns the currency shortname (f.e. FIM for Finnian Mark) + * + * @param string $currency (Optional) Currency name + * @param string|Zend_Locale $locale (Optional) Locale to display informations + * @return string + */ + public function getSymbol($currency = null, $locale = null) + { + if (($currency === null) and ($locale === null)) { + return $this->_options['symbol']; + } + + $params = self::_checkParams($currency, $locale); + + // Get the symbol + $symbol = Zend_Locale_Data::getContent($params['locale'], 'currencysymbol', $params['currency']); + if (empty($symbol) === true) { + $symbol = Zend_Locale_Data::getContent($params['locale'], 'currencysymbol', $params['name']); + } + + if (empty($symbol) === true) { + return null; + } + + return $symbol; + } + + /** + * Returns the actual or details of other currency shortnames + * + * @param string $currency OPTIONAL Currency's name + * @param string|Zend_Locale $locale OPTIONAL The locale + * @return string + */ + public function getShortName($currency = null, $locale = null) + { + if (($currency === null) and ($locale === null)) { + return $this->_options['currency']; + } + + $params = self::_checkParams($currency, $locale); + + // Get the shortname + if (empty($params['currency']) === true) { + return $params['name']; + } + + $list = Zend_Locale_Data::getContent($params['locale'], 'currencytoname', $params['currency']); + if (empty($list) === true) { + $list = Zend_Locale_Data::getContent($params['locale'], 'nametocurrency', $params['currency']); + if (empty($list) === false) { + $list = $params['currency']; + } + } + + if (empty($list) === true) { + return null; + } + + return $list; + } + + /** + * Returns the actual or details of other currency names + * + * @param string $currency (Optional) Currency's short name + * @param string|Zend_Locale $locale (Optional) The locale + * @return string + */ + public function getName($currency = null, $locale = null) + { + if (($currency === null) and ($locale === null)) { + return $this->_options['name']; + } + + $params = self::_checkParams($currency, $locale); + + // Get the name + $name = Zend_Locale_Data::getContent($params['locale'], 'nametocurrency', $params['currency']); + if (empty($name) === true) { + $name = Zend_Locale_Data::getContent($params['locale'], 'nametocurrency', $params['name']); + } + + if (empty($name) === true) { + return null; + } + + return $name; + } + + /** + * Returns a list of regions where this currency is or was known + * + * @param string $currency OPTIONAL Currency's short name + * @throws Zend_Currency_Exception When no currency was defined + * @return array List of regions + */ + public function getRegionList($currency = null) + { + if ($currency === null) { + $currency = $this->_options['currency']; + } + + if (empty($currency) === true) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception('No currency defined'); + } + + $data = Zend_Locale_Data::getContent($this->_options['locale'], 'regiontocurrency', $currency); + + $result = explode(' ', $data); + return $result; + } + + /** + * Returns a list of currencies which are used in this region + * a region name should be 2 charachters only (f.e. EG, DE, US) + * If no region is given, the actual region is used + * + * @param string $region OPTIONAL Region to return the currencies for + * @return array List of currencies + */ + public function getCurrencyList($region = null) + { + if (empty($region) === true) { + if (strlen($this->_options['locale']) > 4) { + $region = substr($this->_options['locale'], (strpos($this->_options['locale'], '_') + 1)); + } + } + + $data = Zend_Locale_Data::getContent($this->_options['locale'], 'currencytoregion', $region); + + $result = explode(' ', $data); + return $result; + } + + /** + * Returns the actual currency name + * + * @return string + */ + public function toString() + { + return $this->toCurrency(); + } + + /** + * Returns the currency name + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Returns the set cache + * + * @return Zend_Cache_Core The set cache + */ + public static function getCache() + { + return Zend_Locale_Data::getCache(); + } + + /** + * Sets a cache for Zend_Currency + * + * @param Zend_Cache_Core $cache Cache to set + * @return void + */ + public static function setCache(Zend_Cache_Core $cache) + { + Zend_Locale_Data::setCache($cache); + } + + /** + * Returns true when a cache is set + * + * @return boolean + */ + public static function hasCache() + { + return Zend_Locale_Data::hasCache(); + } + + /** + * Removes any set cache + * + * @return void + */ + public static function removeCache() + { + Zend_Locale_Data::removeCache(); + } + + /** + * Clears all set cache data + * + * @param string $tag Tag to clear when the default tag name is not used + * @return void + */ + public static function clearCache($tag = null) + { + Zend_Locale_Data::clearCache($tag); + } + + /** + * Sets a new locale for data retreivement + * Example: 'de_XX' will be set to 'de' because 'de_XX' does not exist + * 'xx_YY' will be set to 'root' because 'xx' does not exist + * + * @param string|Zend_Locale $locale (Optional) Locale for parsing input + * @throws Zend_Currency_Exception When the given locale does not exist + * @return Zend_Currency Provides fluent interface + */ + public function setLocale($locale = null) + { + require_once 'Zend/Locale.php'; + try { + $locale = Zend_Locale::findLocale($locale); + if (strlen($locale) > 4) { + $this->_options['locale'] = $locale; + } else { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception("No region found within the locale '" . (string) $locale . "'"); + } + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception($e->getMessage()); + } + + // Get currency details + $this->_options['currency'] = $this->getShortName(null, $this->_options['locale']); + $this->_options['name'] = $this->getName(null, $this->_options['locale']); + $this->_options['symbol'] = $this->getSymbol(null, $this->_options['locale']); + + return $this; + } + + /** + * Returns the actual set locale + * + * @return string + */ + public function getLocale() + { + return $this->_options['locale']; + } + + /** + * Returns the value + * + * @return float + */ + public function getValue() + { + return $this->_options['value']; + } + + /** + * Adds a currency + * + * @param float|integer|Zend_Currency $value Add this value to currency + * @param string|Zend_Currency $currency The currency to add + * @return Zend_Currency + */ + public function setValue($value, $currency = null) + { + $this->_options['value'] = $this->_exchangeCurrency($value, $currency); + return $this; + } + + /** + * Adds a currency + * + * @param float|integer|Zend_Currency $value Add this value to currency + * @param string|Zend_Currency $currency The currency to add + * @return Zend_Currency + */ + public function add($value, $currency = null) + { + $value = $this->_exchangeCurrency($value, $currency); + $this->_options['value'] += (float) $value; + return $this; + } + + /** + * Substracts a currency + * + * @param float|integer|Zend_Currency $value Substracts this value from currency + * @param string|Zend_Currency $currency The currency to substract + * @return Zend_Currency + */ + public function sub($value, $currency = null) + { + $value = $this->_exchangeCurrency($value, $currency); + $this->_options['value'] -= (float) $value; + return $this; + } + + /** + * Divides a currency + * + * @param float|integer|Zend_Currency $value Divides this value from currency + * @param string|Zend_Currency $currency The currency to divide + * @return Zend_Currency + */ + public function div($value, $currency = null) + { + $value = $this->_exchangeCurrency($value, $currency); + $this->_options['value'] /= (float) $value; + return $this; + } + + /** + * Multiplies a currency + * + * @param float|integer|Zend_Currency $value Multiplies this value from currency + * @param string|Zend_Currency $currency The currency to multiply + * @return Zend_Currency + */ + public function mul($value, $currency = null) + { + $value = $this->_exchangeCurrency($value, $currency); + $this->_options['value'] *= (float) $value; + return $this; + } + + /** + * Calculates the modulo from a currency + * + * @param float|integer|Zend_Currency $value Calculate modulo from this value + * @param string|Zend_Currency $currency The currency to calculate the modulo + * @return Zend_Currency + */ + public function mod($value, $currency = null) + { + $value = $this->_exchangeCurrency($value, $currency); + $this->_options['value'] %= (float) $value; + return $this; + } + + /** + * Compares two currencies + * + * @param float|integer|Zend_Currency $value Compares the currency with this value + * @param string|Zend_Currency $currency The currency to compare this value from + * @return Zend_Currency + */ + public function compare($value, $currency = null) + { + $value = $this->_exchangeCurrency($value, $currency); + $value = $this->_options['value'] - $value; + if ($value < 0) { + return -1; + } else if ($value > 0) { + return 1; + } + + return 0; + } + + /** + * Returns true when the two currencies are equal + * + * @param float|integer|Zend_Currency $value Compares the currency with this value + * @param string|Zend_Currency $currency The currency to compare this value from + * @return boolean + */ + public function equals($value, $currency = null) + { + $value = $this->_exchangeCurrency($value, $currency); + if ($this->_options['value'] == $value) { + return true; + } + + return false; + } + + /** + * Returns true when the currency is more than the given value + * + * @param float|integer|Zend_Currency $value Compares the currency with this value + * @param string|Zend_Currency $currency The currency to compare this value from + * @return boolean + */ + public function isMore($value, $currency = null) + { + $value = $this->_exchangeCurrency($value, $currency); + if ($this->_options['value'] > $value) { + return true; + } + + return false; + } + + /** + * Returns true when the currency is less than the given value + * + * @param float|integer|Zend_Currency $value Compares the currency with this value + * @param string|Zend_Currency $currency The currency to compare this value from + * @return boolean + */ + public function isLess($value, $currency = null) + { + $value = $this->_exchangeCurrency($value, $currency); + if ($this->_options['value'] < $value) { + return true; + } + + return false; + + } + + /** + * Internal method which calculates the exchanges currency + * + * @param float|integer|Zend_Currency $value Compares the currency with this value + * @param string|Zend_Currency $currency The currency to compare this value from + * @return unknown + */ + protected function _exchangeCurrency($value, $currency) + { + if ($value instanceof Zend_Currency) { + $currency = $value->getShortName(); + $value = $value->getValue(); + } else { + $currency = $this->getShortName($currency, $this->getLocale()); + } + + $rate = 1; + if ($currency !== $this->getShortName()) { + $service = $this->getService(); + if (!($service instanceof Zend_Currency_CurrencyInterface)) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception('No exchange service applied'); + } + + $rate = $service->getRate($currency, $this->getShortName()); + } + + $value *= $rate; + return $value; + } + + /** + * Returns the set service class + * + * @return Zend_Service + */ + public function getService() + { + return $this->_options['service']; + } + + /** + * Sets a new exchange service + * + * @param string|Zend_Currency_CurrencyInterface $service Service class + * @return Zend_Currency + */ + public function setService($service) + { + if (is_string($service)) { + require_once 'Zend/Loader.php'; + if (!class_exists($service)) { + $file = str_replace('_', DIRECTORY_SEPARATOR, $service) . '.php'; + if (Zend_Loader::isReadable($file)) { + Zend_Loader::loadClass($service); + } + } + + $service = new $service; + } + + if (!($service instanceof Zend_Currency_CurrencyInterface)) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception('A currency service must implement Zend_Currency_CurrencyInterface'); + } + + $this->_options['service'] = $service; + return $this; + } + + /** + * Internal method for checking the options array + * + * @param array $options Options to check + * @throws Zend_Currency_Exception On unknown position + * @throws Zend_Currency_Exception On unknown locale + * @throws Zend_Currency_Exception On unknown display + * @throws Zend_Currency_Exception On precision not between -1 and 30 + * @throws Zend_Currency_Exception On problem with script conversion + * @throws Zend_Currency_Exception On unknown options + * @return array + */ + protected function _checkOptions(array $options = array()) + { + if (count($options) === 0) { + return $this->_options; + } + + foreach ($options as $name => $value) { + $name = strtolower($name); + if ($name !== 'format') { + if (gettype($value) === 'string') { + $value = strtolower($value); + } + } + + switch($name) { + case 'position': + if (($value !== self::STANDARD) and ($value !== self::RIGHT) and ($value !== self::LEFT)) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception("Unknown position '" . $value . "'"); + } + + break; + + case 'format': + if ((empty($value) === false) and (Zend_Locale::isLocale($value, null, false) === false)) { + if (!is_string($value) || (strpos($value, '0') === false)) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception("'" . + ((gettype($value) === 'object') ? get_class($value) : $value) + . "' is no format token"); + } + } + break; + + case 'display': + if (is_numeric($value) and ($value !== self::NO_SYMBOL) and ($value !== self::USE_SYMBOL) and + ($value !== self::USE_SHORTNAME) and ($value !== self::USE_NAME)) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception("Unknown display '$value'"); + } + break; + + case 'precision': + if ($value === null) { + $value = -1; + } + + if (($value < -1) or ($value > 30)) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception("'$value' precision has to be between -1 and 30."); + } + break; + + case 'script': + try { + Zend_Locale_Format::convertNumerals(0, $options['script']); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Currency/Exception.php'; + throw new Zend_Currency_Exception($e->getMessage()); + } + break; + + default: + break; + } + } + + return $options; + } +} diff --git a/library/Zend/Currency/CurrencyInterface.php b/library/Zend/Currency/CurrencyInterface.php new file mode 100644 index 00000000..771013b3 --- /dev/null +++ b/library/Zend/Currency/CurrencyInterface.php @@ -0,0 +1,39 @@ + 'iso', // format for date strings 'iso' or 'php' + 'fix_dst' => true, // fix dst on summer/winter time change + 'extend_month' => false, // false - addMonth like SQL, true like excel + 'cache' => null, // cache to set + 'timesync' => null // timesync server to set + ); + + // Class wide Date Constants + const DAY = 'dd'; + const DAY_SHORT = 'd'; + const DAY_SUFFIX = 'SS'; + const DAY_OF_YEAR = 'D'; + const WEEKDAY = 'EEEE'; + const WEEKDAY_SHORT = 'EEE'; + const WEEKDAY_NARROW = 'E'; + const WEEKDAY_NAME = 'EE'; + const WEEKDAY_8601 = 'eee'; + const WEEKDAY_DIGIT = 'e'; + const WEEK = 'ww'; + const MONTH = 'MM'; + const MONTH_SHORT = 'M'; + const MONTH_DAYS = 'ddd'; + const MONTH_NAME = 'MMMM'; + const MONTH_NAME_SHORT = 'MMM'; + const MONTH_NAME_NARROW = 'MMMMM'; + const YEAR = 'y'; + const YEAR_SHORT = 'yy'; + const YEAR_8601 = 'Y'; + const YEAR_SHORT_8601 = 'YY'; + const LEAPYEAR = 'l'; + const MERIDIEM = 'a'; + const SWATCH = 'B'; + const HOUR = 'HH'; + const HOUR_SHORT = 'H'; + const HOUR_AM = 'hh'; + const HOUR_SHORT_AM = 'h'; + const MINUTE = 'mm'; + const MINUTE_SHORT = 'm'; + const SECOND = 'ss'; + const SECOND_SHORT = 's'; + const MILLISECOND = 'S'; + const TIMEZONE_NAME = 'zzzz'; + const DAYLIGHT = 'I'; + const GMT_DIFF = 'Z'; + const GMT_DIFF_SEP = 'ZZZZ'; + const TIMEZONE = 'z'; + const TIMEZONE_SECS = 'X'; + const ISO_8601 = 'c'; + const RFC_2822 = 'r'; + const TIMESTAMP = 'U'; + const ERA = 'G'; + const ERA_NAME = 'GGGG'; + const ERA_NARROW = 'GGGGG'; + const DATES = 'F'; + const DATE_FULL = 'FFFFF'; + const DATE_LONG = 'FFFF'; + const DATE_MEDIUM = 'FFF'; + const DATE_SHORT = 'FF'; + const TIMES = 'WW'; + const TIME_FULL = 'TTTTT'; + const TIME_LONG = 'TTTT'; + const TIME_MEDIUM = 'TTT'; + const TIME_SHORT = 'TT'; + const DATETIME = 'K'; + const DATETIME_FULL = 'KKKKK'; + const DATETIME_LONG = 'KKKK'; + const DATETIME_MEDIUM = 'KKK'; + const DATETIME_SHORT = 'KK'; + const ATOM = 'OOO'; + const COOKIE = 'CCC'; + const RFC_822 = 'R'; + const RFC_850 = 'RR'; + const RFC_1036 = 'RRR'; + const RFC_1123 = 'RRRR'; + const RFC_3339 = 'RRRRR'; + const RSS = 'SSS'; + const W3C = 'WWW'; + + /** + * Generates the standard date object, could be a unix timestamp, localized date, + * string, integer, array and so on. Also parts of dates or time are supported + * Always set the default timezone: http://php.net/date_default_timezone_set + * For example, in your bootstrap: date_default_timezone_set('America/Los_Angeles'); + * For detailed instructions please look in the docu. + * + * @param string|integer|Zend_Date|array $date OPTIONAL Date value or value of date part to set + * ,depending on $part. If null the actual time is set + * @param string $part OPTIONAL Defines the input format of $date + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + * @throws Zend_Date_Exception + */ + public function __construct($date = null, $part = null, $locale = null) + { + if (is_object($date) and !($date instanceof Zend_TimeSync_Protocol) and + !($date instanceof Zend_Date)) { + if ($locale instanceof Zend_Locale) { + $locale = $date; + $date = null; + $part = null; + } else { + $date = (string) $date; + } + } + + if (($date !== null) and !is_array($date) and !($date instanceof Zend_TimeSync_Protocol) and + !($date instanceof Zend_Date) and !defined($date) and Zend_Locale::isLocale($date, true, false)) { + $locale = $date; + $date = null; + $part = null; + } else if (($part !== null) and !defined($part) and Zend_Locale::isLocale($part, true, false)) { + $locale = $part; + $part = null; + } + + $this->setLocale($locale); + if (is_string($date) && ($part === null) && (strlen($date) <= 5)) { + $part = $date; + $date = null; + } + + if ($date === null) { + if ($part === null) { + $date = time(); + } else if ($part !== self::TIMESTAMP) { + $date = self::now($locale); + $date = $date->get($part); + } + } + + if ($date instanceof Zend_TimeSync_Protocol) { + $date = $date->getInfo(); + $date = $this->_getTime($date['offset']); + $part = null; + } else if (parent::$_defaultOffset != 0) { + $date = $this->_getTime(parent::$_defaultOffset); + } + + // set the timezone and offset for $this + $zone = @date_default_timezone_get(); + $this->setTimezone($zone); + + // try to get timezone from date-string + if (!is_int($date)) { + $zone = $this->getTimezoneFromString($date); + $this->setTimezone($zone); + } + + // set datepart + if (($part !== null && $part !== self::TIMESTAMP) or (!is_numeric($date))) { + // switch off dst handling for value setting + $this->setUnixTimestamp($this->getGmtOffset()); + $this->set($date, $part, $this->_locale); + + // DST fix + if (is_array($date) === true) { + if (!isset($date['hour'])) { + $date['hour'] = 0; + } + + $hour = $this->toString('H', 'iso', true); + $hour = $date['hour'] - $hour; + switch ($hour) { + case 1 : + case -23 : + $this->addTimestamp(3600); + break; + case -1 : + case 23 : + $this->subTimestamp(3600); + break; + case 2 : + case -22 : + $this->addTimestamp(7200); + break; + case -2 : + case 22 : + $this->subTimestamp(7200); + break; + } + } + } else { + $this->setUnixTimestamp($date); + } + } + + /** + * Sets class wide options, if no option was given, the actual set options will be returned + * + * @param array $options Options to set + * @throws Zend_Date_Exception + * @return Options array if no option was given + */ + public static function setOptions(array $options = array()) + { + if (empty($options)) { + return self::$_options; + } + + foreach ($options as $name => $value) { + $name = strtolower($name); + + if (array_key_exists($name, self::$_options)) { + switch($name) { + case 'format_type' : + if ((strtolower($value) != 'php') && (strtolower($value) != 'iso')) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("Unknown format type ($value) for dates, only 'iso' and 'php' supported", 0, null, $value); + } + break; + case 'fix_dst' : + if (!is_bool($value)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("'fix_dst' has to be boolean", 0, null, $value); + } + break; + case 'extend_month' : + if (!is_bool($value)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("'extend_month' has to be boolean", 0, null, $value); + } + break; + case 'cache' : + if ($value === null) { + parent::$_cache = null; + } else { + if (!$value instanceof Zend_Cache_Core) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("Instance of Zend_Cache expected"); + } + + parent::$_cache = $value; + parent::$_cacheTags = Zend_Date_DateObject::_getTagSupportForCache(); + Zend_Locale_Data::setCache($value); + } + break; + case 'timesync' : + if ($value === null) { + parent::$_defaultOffset = 0; + } else { + if (!$value instanceof Zend_TimeSync_Protocol) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("Instance of Zend_TimeSync expected"); + } + + $date = $value->getInfo(); + parent::$_defaultOffset = $date['offset']; + } + break; + } + self::$_options[$name] = $value; + } + else { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("Unknown option: $name = $value"); + } + } + } + + /** + * Returns this object's internal UNIX timestamp (equivalent to Zend_Date::TIMESTAMP). + * If the timestamp is too large for integers, then the return value will be a string. + * This function does not return the timestamp as an object. + * Use clone() or copyPart() instead. + * + * @return integer|string UNIX timestamp + */ + public function getTimestamp() + { + return $this->getUnixTimestamp(); + } + + /** + * Returns the calculated timestamp + * HINT: timestamps are always GMT + * + * @param string $calc Type of calculation to make + * @param string|integer|array|Zend_Date $stamp Timestamp to calculate, when null the actual timestamp is calculated + * @return Zend_Date|integer + * @throws Zend_Date_Exception + */ + private function _timestamp($calc, $stamp) + { + if ($stamp instanceof Zend_Date) { + // extract timestamp from object + $stamp = $stamp->getTimestamp(); + } + + if (is_array($stamp)) { + if (isset($stamp['timestamp']) === true) { + $stamp = $stamp['timestamp']; + } else { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('no timestamp given in array'); + } + } + + if ($calc === 'set') { + $return = $this->setUnixTimestamp($stamp); + } else { + $return = $this->_calcdetail($calc, $stamp, self::TIMESTAMP, null); + } + if ($calc != 'cmp') { + return $this; + } + return $return; + } + + /** + * Sets a new timestamp + * + * @param integer|string|array|Zend_Date $timestamp Timestamp to set + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setTimestamp($timestamp) + { + return $this->_timestamp('set', $timestamp); + } + + /** + * Adds a timestamp + * + * @param integer|string|array|Zend_Date $timestamp Timestamp to add + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addTimestamp($timestamp) + { + return $this->_timestamp('add', $timestamp); + } + + /** + * Subtracts a timestamp + * + * @param integer|string|array|Zend_Date $timestamp Timestamp to sub + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subTimestamp($timestamp) + { + return $this->_timestamp('sub', $timestamp); + } + + /** + * Compares two timestamps, returning the difference as integer + * + * @param integer|string|array|Zend_Date $timestamp Timestamp to compare + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareTimestamp($timestamp) + { + return $this->_timestamp('cmp', $timestamp); + } + + /** + * Returns a string representation of the object + * Supported format tokens are: + * G - era, y - year, Y - ISO year, M - month, w - week of year, D - day of year, d - day of month + * E - day of week, e - number of weekday (1-7), h - hour 1-12, H - hour 0-23, m - minute, s - second + * A - milliseconds of day, z - timezone, Z - timezone offset, S - fractional second, a - period of day + * + * Additionally format tokens but non ISO conform are: + * SS - day suffix, eee - php number of weekday(0-6), ddd - number of days per month + * l - Leap year, B - swatch internet time, I - daylight saving time, X - timezone offset in seconds + * r - RFC2822 format, U - unix timestamp + * + * Not supported ISO tokens are + * u - extended year, Q - quarter, q - quarter, L - stand alone month, W - week of month + * F - day of week of month, g - modified julian, c - stand alone weekday, k - hour 0-11, K - hour 1-24 + * v - wall zone + * + * @param string $format OPTIONAL Rule for formatting output. If null the default date format is used + * @param string $type OPTIONAL Type for the format string which overrides the standard setting + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return string + */ + public function toString($format = null, $type = null, $locale = null) + { + if (is_object($format)) { + if ($format instanceof Zend_Locale) { + $locale = $format; + $format = null; + } else { + $format = (string) $format; + } + } + + if (is_object($type)) { + if ($type instanceof Zend_Locale) { + $locale = $type; + $type = null; + } else { + $type = (string) $type; + } + } + + if (($format !== null) && !defined($format) + && ($format != 'ee') && ($format != 'ss') && ($format != 'GG') && ($format != 'MM') && ($format != 'EE') && ($format != 'TT') + && Zend_Locale::isLocale($format, null, false)) { + $locale = $format; + $format = null; + } + + if (($type !== null) and ($type != 'php') and ($type != 'iso') and + Zend_Locale::isLocale($type, null, false)) { + $locale = $type; + $type = null; + } + + if ($locale === null) { + $locale = $this->getLocale(); + } + + if ($format === null) { + $format = Zend_Locale_Format::getDateFormat($locale) . ' ' . Zend_Locale_Format::getTimeFormat($locale); + } else if (((self::$_options['format_type'] == 'php') && ($type === null)) or ($type == 'php')) { + $format = Zend_Locale_Format::convertPhpToIsoFormat($format); + } + + return $this->date($this->_toToken($format, $locale), $this->getUnixTimestamp(), false); + } + + /** + * Returns a string representation of the date which is equal with the timestamp + * + * @return string + */ + public function __toString() + { + return $this->toString(null, $this->_locale); + } + + /** + * Returns a integer representation of the object + * But returns false when the given part is no value f.e. Month-Name + * + * @param string|integer|Zend_Date $part OPTIONAL Defines the date or datepart to return as integer + * @return integer|false + */ + public function toValue($part = null) + { + $result = $this->get($part); + if (is_numeric($result)) { + return intval("$result"); + } else { + return false; + } + } + + /** + * Returns an array representation of the object + * + * @return array + */ + public function toArray() + { + return array('day' => $this->toString(self::DAY_SHORT, 'iso'), + 'month' => $this->toString(self::MONTH_SHORT, 'iso'), + 'year' => $this->toString(self::YEAR, 'iso'), + 'hour' => $this->toString(self::HOUR_SHORT, 'iso'), + 'minute' => $this->toString(self::MINUTE_SHORT, 'iso'), + 'second' => $this->toString(self::SECOND_SHORT, 'iso'), + 'timezone' => $this->toString(self::TIMEZONE, 'iso'), + 'timestamp' => $this->toString(self::TIMESTAMP, 'iso'), + 'weekday' => $this->toString(self::WEEKDAY_8601, 'iso'), + 'dayofyear' => $this->toString(self::DAY_OF_YEAR, 'iso'), + 'week' => $this->toString(self::WEEK, 'iso'), + 'gmtsecs' => $this->toString(self::TIMEZONE_SECS, 'iso')); + } + + /** + * Returns a representation of a date or datepart + * This could be for example a localized monthname, the time without date, + * the era or only the fractional seconds. There are about 50 different supported date parts. + * For a complete list of supported datepart values look into the docu + * + * @param string $part OPTIONAL Part of the date to return, if null the timestamp is returned + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return string date or datepart + */ + public function get($part = null, $locale = null) + { + if ($locale === null) { + $locale = $this->getLocale(); + } + + if (($part !== null) && !defined($part) + && ($part != 'ee') && ($part != 'ss') && ($part != 'GG') && ($part != 'MM') && ($part != 'EE') && ($part != 'TT') + && Zend_Locale::isLocale($part, null, false)) { + $locale = $part; + $part = null; + } + + if ($part === null) { + $part = self::TIMESTAMP; + } else if (self::$_options['format_type'] == 'php') { + $part = Zend_Locale_Format::convertPhpToIsoFormat($part); + } + + return $this->date($this->_toToken($part, $locale), $this->getUnixTimestamp(), false); + } + + /** + * Internal method to apply tokens + * + * @param string $part + * @param string $locale + * @return string + */ + private function _toToken($part, $locale) { + // get format tokens + $comment = false; + $format = ''; + $orig = ''; + for ($i = 0; isset($part[$i]); ++$i) { + if ($part[$i] == "'") { + $comment = $comment ? false : true; + if (isset($part[$i+1]) && ($part[$i+1] == "'")) { + $comment = $comment ? false : true; + $format .= "\\'"; + ++$i; + } + + $orig = ''; + continue; + } + + if ($comment) { + $format .= '\\' . $part[$i]; + $orig = ''; + } else { + $orig .= $part[$i]; + if (!isset($part[$i+1]) || (isset($orig[0]) && ($orig[0] != $part[$i+1]))) { + $format .= $this->_parseIsoToDate($orig, $locale); + $orig = ''; + } + } + } + + return $format; + } + + /** + * Internal parsing method + * + * @param string $token + * @param string $locale + * @return string + */ + private function _parseIsoToDate($token, $locale) { + switch($token) { + case self::DAY : + return 'd'; + break; + + case self::WEEKDAY_SHORT : + $weekday = strtolower($this->date('D', $this->getUnixTimestamp(), false)); + $day = Zend_Locale_Data::getContent($locale, 'day', array('gregorian', 'format', 'wide', $weekday)); + return $this->_toComment(iconv_substr($day, 0, 3, 'UTF-8')); + break; + + case self::DAY_SHORT : + return 'j'; + break; + + case self::WEEKDAY : + $weekday = strtolower($this->date('D', $this->getUnixTimestamp(), false)); + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'day', array('gregorian', 'format', 'wide', $weekday))); + break; + + case self::WEEKDAY_8601 : + return 'N'; + break; + + case 'ee' : + return $this->_toComment(str_pad($this->date('N', $this->getUnixTimestamp(), false), 2, '0', STR_PAD_LEFT)); + break; + + case self::DAY_SUFFIX : + return 'S'; + break; + + case self::WEEKDAY_DIGIT : + return 'w'; + break; + + case self::DAY_OF_YEAR : + return 'z'; + break; + + case 'DDD' : + return $this->_toComment(str_pad($this->date('z', $this->getUnixTimestamp(), false), 3, '0', STR_PAD_LEFT)); + break; + + case 'DD' : + return $this->_toComment(str_pad($this->date('z', $this->getUnixTimestamp(), false), 2, '0', STR_PAD_LEFT)); + break; + + case self::WEEKDAY_NARROW : + case 'EEEEE' : + $weekday = strtolower($this->date('D', $this->getUnixTimestamp(), false)); + $day = Zend_Locale_Data::getContent($locale, 'day', array('gregorian', 'format', 'abbreviated', $weekday)); + return $this->_toComment(iconv_substr($day, 0, 1, 'UTF-8')); + break; + + case self::WEEKDAY_NAME : + $weekday = strtolower($this->date('D', $this->getUnixTimestamp(), false)); + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'day', array('gregorian', 'format', 'abbreviated', $weekday))); + break; + + case 'w' : + $week = $this->date('W', $this->getUnixTimestamp(), false); + return $this->_toComment(($week[0] == '0') ? $week[1] : $week); + break; + + case self::WEEK : + return 'W'; + break; + + case self::MONTH_NAME : + $month = $this->date('n', $this->getUnixTimestamp(), false); + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'month', array('gregorian', 'format', 'wide', $month))); + break; + + case self::MONTH : + return 'm'; + break; + + case self::MONTH_NAME_SHORT : + $month = $this->date('n', $this->getUnixTimestamp(), false); + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'month', array('gregorian', 'format', 'abbreviated', $month))); + break; + + case self::MONTH_SHORT : + return 'n'; + break; + + case self::MONTH_DAYS : + return 't'; + break; + + case self::MONTH_NAME_NARROW : + $month = $this->date('n', $this->getUnixTimestamp(), false); + $mon = Zend_Locale_Data::getContent($locale, 'month', array('gregorian', 'format', 'abbreviated', $month)); + return $this->_toComment(iconv_substr($mon, 0, 1, 'UTF-8')); + break; + + case self::LEAPYEAR : + return 'L'; + break; + + case self::YEAR_8601 : + return 'o'; + break; + + case self::YEAR : + return 'Y'; + break; + + case self::YEAR_SHORT : + return 'y'; + break; + + case self::YEAR_SHORT_8601 : + return $this->_toComment(substr($this->date('o', $this->getUnixTimestamp(), false), -2, 2)); + break; + + case self::MERIDIEM : + $am = $this->date('a', $this->getUnixTimestamp(), false); + if ($am == 'am') { + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'am')); + } + + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'pm')); + break; + + case self::SWATCH : + return 'B'; + break; + + case self::HOUR_SHORT_AM : + return 'g'; + break; + + case self::HOUR_SHORT : + return 'G'; + break; + + case self::HOUR_AM : + return 'h'; + break; + + case self::HOUR : + return 'H'; + break; + + case self::MINUTE : + return $this->_toComment(str_pad($this->date('i', $this->getUnixTimestamp(), false), 2, '0', STR_PAD_LEFT)); + break; + + case self::SECOND : + return $this->_toComment(str_pad($this->date('s', $this->getUnixTimestamp(), false), 2, '0', STR_PAD_LEFT)); + break; + + case self::MINUTE_SHORT : + return 'i'; + break; + + case self::SECOND_SHORT : + return 's'; + break; + + case self::MILLISECOND : + return $this->_toComment($this->getMilliSecond()); + break; + + case self::TIMEZONE_NAME : + case 'vvvv' : + return 'e'; + break; + + case self::DAYLIGHT : + return 'I'; + break; + + case self::GMT_DIFF : + case 'ZZ' : + case 'ZZZ' : + return 'O'; + break; + + case self::GMT_DIFF_SEP : + return 'P'; + break; + + case self::TIMEZONE : + case 'v' : + case 'zz' : + case 'zzz' : + return 'T'; + break; + + case self::TIMEZONE_SECS : + return 'Z'; + break; + + case self::ISO_8601 : + return 'c'; + break; + + case self::RFC_2822 : + return 'r'; + break; + + case self::TIMESTAMP : + return 'U'; + break; + + case self::ERA : + case 'GG' : + case 'GGG' : + $year = $this->date('Y', $this->getUnixTimestamp(), false); + if ($year < 0) { + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Abbr', '0'))); + } + + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Abbr', '1'))); + break; + + case self::ERA_NARROW : + $year = $this->date('Y', $this->getUnixTimestamp(), false); + if ($year < 0) { + return $this->_toComment(iconv_substr(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Abbr', '0')), 0, 1, 'UTF-8')) . '.'; + } + + return $this->_toComment(iconv_substr(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Abbr', '1')), 0, 1, 'UTF-8')) . '.'; + break; + + case self::ERA_NAME : + $year = $this->date('Y', $this->getUnixTimestamp(), false); + if ($year < 0) { + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Names', '0'))); + } + + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Names', '1'))); + break; + + case self::DATES : + return $this->_toToken(Zend_Locale_Format::getDateFormat($locale), $locale); + break; + + case self::DATE_FULL : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'full')), $locale); + break; + + case self::DATE_LONG : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'long')), $locale); + break; + + case self::DATE_MEDIUM : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'medium')), $locale); + break; + + case self::DATE_SHORT : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'short')), $locale); + break; + + case self::TIMES : + return $this->_toToken(Zend_Locale_Format::getTimeFormat($locale), $locale); + break; + + case self::TIME_FULL : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'time', 'full'), $locale); + break; + + case self::TIME_LONG : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'time', 'long'), $locale); + break; + + case self::TIME_MEDIUM : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'time', 'medium'), $locale); + break; + + case self::TIME_SHORT : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'time', 'short'), $locale); + break; + + case self::DATETIME : + return $this->_toToken(Zend_Locale_Format::getDateTimeFormat($locale), $locale); + break; + + case self::DATETIME_FULL : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'full')), $locale); + break; + + case self::DATETIME_LONG : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'long')), $locale); + break; + + case self::DATETIME_MEDIUM : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'medium')), $locale); + break; + + case self::DATETIME_SHORT : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'short')), $locale); + break; + + case self::ATOM : + return 'Y\-m\-d\TH\:i\:sP'; + break; + + case self::COOKIE : + return 'l\, d\-M\-y H\:i\:s e'; + break; + + case self::RFC_822 : + return 'D\, d M y H\:i\:s O'; + break; + + case self::RFC_850 : + return 'l\, d\-M\-y H\:i\:s e'; + break; + + case self::RFC_1036 : + return 'D\, d M y H\:i\:s O'; + break; + + case self::RFC_1123 : + return 'D\, d M Y H\:i\:s O'; + break; + + case self::RFC_3339 : + return 'Y\-m\-d\TH\:i\:sP'; + break; + + case self::RSS : + return 'D\, d M Y H\:i\:s O'; + break; + + case self::W3C : + return 'Y\-m\-d\TH\:i\:sP'; + break; + } + + if ($token == '') { + return ''; + } + + switch ($token[0]) { + case 'y' : + if ((strlen($token) == 4) && (abs($this->getUnixTimestamp()) <= 0x7FFFFFFF)) { + return 'Y'; + } + + $length = iconv_strlen($token, 'UTF-8'); + return $this->_toComment(str_pad($this->date('Y', $this->getUnixTimestamp(), false), $length, '0', STR_PAD_LEFT)); + break; + + case 'Y' : + if ((strlen($token) == 4) && (abs($this->getUnixTimestamp()) <= 0x7FFFFFFF)) { + return 'o'; + } + + $length = iconv_strlen($token, 'UTF-8'); + return $this->_toComment(str_pad($this->date('o', $this->getUnixTimestamp(), false), $length, '0', STR_PAD_LEFT)); + break; + + case 'A' : + $length = iconv_strlen($token, 'UTF-8'); + $result = substr($this->getMilliSecond(), 0, 3); + $result += $this->date('s', $this->getUnixTimestamp(), false) * 1000; + $result += $this->date('i', $this->getUnixTimestamp(), false) * 60000; + $result += $this->date('H', $this->getUnixTimestamp(), false) * 3600000; + + return $this->_toComment(str_pad($result, $length, '0', STR_PAD_LEFT)); + break; + } + + return $this->_toComment($token); + } + + /** + * Private function to make a comment of a token + * + * @param string $token + * @return string + */ + private function _toComment($token) + { + $token = str_split($token); + $result = ''; + foreach ($token as $tok) { + $result .= '\\' . $tok; + } + + return $result; + } + + /** + * Return digit from standard names (english) + * Faster implementation than locale aware searching + * + * @param string $name + * @return integer Number of this month + * @throws Zend_Date_Exception + */ + private function _getDigitFromName($name) + { + switch($name) { + case "Jan": + return 1; + + case "Feb": + return 2; + + case "Mar": + return 3; + + case "Apr": + return 4; + + case "May": + return 5; + + case "Jun": + return 6; + + case "Jul": + return 7; + + case "Aug": + return 8; + + case "Sep": + return 9; + + case "Oct": + return 10; + + case "Nov": + return 11; + + case "Dec": + return 12; + + default: + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('Month ($name) is not a known month'); + } + } + + /** + * Counts the exact year number + * < 70 - 2000 added, >70 < 100 - 1900, others just returned + * + * @param integer $value year number + * @return integer Number of year + */ + public static function getFullYear($value) + { + if ($value >= 0) { + if ($value < 70) { + $value += 2000; + } else if ($value < 100) { + $value += 1900; + } + } + return $value; + } + + /** + * Sets the given date as new date or a given datepart as new datepart returning the new datepart + * This could be for example a localized dayname, the date without time, + * the month or only the seconds. There are about 50 different supported date parts. + * For a complete list of supported datepart values look into the docu + * + * @param string|integer|array|Zend_Date $date Date or datepart to set + * @param string $part OPTIONAL Part of the date to set, if null the timestamp is set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function set($date, $part = null, $locale = null) + { + if (self::$_options['format_type'] == 'php') { + $part = Zend_Locale_Format::convertPhpToIsoFormat($part); + } + + $zone = $this->getTimezoneFromString($date); + $this->setTimezone($zone); + + $this->_calculate('set', $date, $part, $locale); + return $this; + } + + /** + * Adds a date or datepart to the existing date, by extracting $part from $date, + * and modifying this object by adding that part. The $part is then extracted from + * this object and returned as an integer or numeric string (for large values, or $part's + * corresponding to pre-defined formatted date strings). + * This could be for example a ISO 8601 date, the hour the monthname or only the minute. + * There are about 50 different supported date parts. + * For a complete list of supported datepart values look into the docu. + * + * @param string|integer|array|Zend_Date $date Date or datepart to add + * @param string $part OPTIONAL Part of the date to add, if null the timestamp is added + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function add($date, $part = self::TIMESTAMP, $locale = null) + { + if (self::$_options['format_type'] == 'php') { + $part = Zend_Locale_Format::convertPhpToIsoFormat($part); + } + + $this->_calculate('add', $date, $part, $locale); + return $this; + } + + /** + * Subtracts a date from another date. + * This could be for example a RFC2822 date, the time, + * the year or only the timestamp. There are about 50 different supported date parts. + * For a complete list of supported datepart values look into the docu + * Be aware: Adding -2 Months is not equal to Subtracting 2 Months !!! + * + * @param string|integer|array|Zend_Date $date Date or datepart to subtract + * @param string $part OPTIONAL Part of the date to sub, if null the timestamp is subtracted + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function sub($date, $part = self::TIMESTAMP, $locale = null) + { + if (self::$_options['format_type'] == 'php') { + $part = Zend_Locale_Format::convertPhpToIsoFormat($part); + } + + $this->_calculate('sub', $date, $part, $locale); + return $this; + } + + /** + * Compares a date or datepart with the existing one. + * Returns -1 if earlier, 0 if equal and 1 if later. + * + * @param string|integer|array|Zend_Date $date Date or datepart to compare with the date object + * @param string $part OPTIONAL Part of the date to compare, if null the timestamp is subtracted + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compare($date, $part = self::TIMESTAMP, $locale = null) + { + if (self::$_options['format_type'] == 'php') { + $part = Zend_Locale_Format::convertPhpToIsoFormat($part); + } + + $compare = $this->_calculate('cmp', $date, $part, $locale); + + if ($compare > 0) { + return 1; + } else if ($compare < 0) { + return -1; + } + return 0; + } + + /** + * Returns a new instance of Zend_Date with the selected part copied. + * To make an exact copy, use PHP's clone keyword. + * For a complete list of supported date part values look into the docu. + * If a date part is copied, all other date parts are set to standard values. + * For example: If only YEAR is copied, the returned date object is equal to + * 01-01-YEAR 00:00:00 (01-01-1970 00:00:00 is equal to timestamp 0) + * If only HOUR is copied, the returned date object is equal to + * 01-01-1970 HOUR:00:00 (so $this contains a timestamp equal to a timestamp of 0 plus HOUR). + * + * @param string $part Part of the date to compare, if null the timestamp is subtracted + * @param string|Zend_Locale $locale OPTIONAL New object's locale. No adjustments to timezone are made. + * @return Zend_Date New clone with requested part + */ + public function copyPart($part, $locale = null) + { + $clone = clone $this; // copy all instance variables + $clone->setUnixTimestamp(0); // except the timestamp + if ($locale != null) { + $clone->setLocale($locale); // set an other locale if selected + } + $clone->set($this, $part); + return $clone; + } + + /** + * Internal function, returns the offset of a given timezone + * + * @param string $zone + * @return integer + */ + public function getTimezoneFromString($zone) + { + if (is_array($zone)) { + return $this->getTimezone(); + } + + if ($zone instanceof Zend_Date) { + return $zone->getTimezone(); + } + + $match = array(); + preg_match('/\dZ$/', $zone, $match); + if (!empty($match)) { + return "Etc/UTC"; + } + + preg_match('/([+-]\d{2}):{0,1}\d{2}/', $zone, $match); + if (!empty($match) and ($match[count($match) - 1] <= 12) and ($match[count($match) - 1] >= -12)) { + $zone = "Etc/GMT"; + $zone .= ($match[count($match) - 1] < 0) ? "+" : "-"; + $zone .= (int) abs($match[count($match) - 1]); + return $zone; + } + + preg_match('/([[:alpha:]\/]{3,30})(?!.*([[:alpha:]\/]{3,30}))/', $zone, $match); + try { + if (!empty($match) and (!is_int($match[count($match) - 1]))) { + $oldzone = $this->getTimezone(); + $this->setTimezone($match[count($match) - 1]); + $result = $this->getTimezone(); + $this->setTimezone($oldzone); + if ($result !== $oldzone) { + return $match[count($match) - 1]; + } + } + } catch (Exception $e) { + // fall through + } + + return $this->getTimezone(); + } + + /** + * Calculates the date or object + * + * @param string $calc Calculation to make + * @param string|integer $date Date for calculation + * @param string|integer $comp Second date for calculation + * @param boolean|integer $dst Use dst correction if option is set + * @return integer|string|Zend_Date new timestamp or Zend_Date depending on calculation + */ + private function _assign($calc, $date, $comp = 0, $dst = false) + { + switch ($calc) { + case 'set' : + if (!empty($comp)) { + $this->setUnixTimestamp(call_user_func(Zend_Locale_Math::$sub, $this->getUnixTimestamp(), $comp)); + } + $this->setUnixTimestamp(call_user_func(Zend_Locale_Math::$add, $this->getUnixTimestamp(), $date)); + $value = $this->getUnixTimestamp(); + break; + case 'add' : + $this->setUnixTimestamp(call_user_func(Zend_Locale_Math::$add, $this->getUnixTimestamp(), $date)); + $value = $this->getUnixTimestamp(); + break; + case 'sub' : + $this->setUnixTimestamp(call_user_func(Zend_Locale_Math::$sub, $this->getUnixTimestamp(), $date)); + $value = $this->getUnixTimestamp(); + break; + default : + // cmp - compare + return call_user_func(Zend_Locale_Math::$comp, $comp, $date); + break; + } + + // dst-correction if 'fix_dst' = true and dst !== false but only for non UTC and non GMT + if ((self::$_options['fix_dst'] === true) and ($dst !== false) and ($this->_dst === true)) { + $hour = $this->toString(self::HOUR, 'iso'); + if ($hour != $dst) { + if (($dst == ($hour + 1)) or ($dst == ($hour - 23))) { + $value += 3600; + } else if (($dst == ($hour - 1)) or ($dst == ($hour + 23))) { + $value -= 3600; + } + $this->setUnixTimestamp($value); + } + } + return $this->getUnixTimestamp(); + } + + + /** + * Calculates the date or object + * + * @param string $calc Calculation to make, one of: 'add'|'sub'|'cmp'|'copy'|'set' + * @param string|integer|array|Zend_Date $date Date or datepart to calculate with + * @param string $part Part of the date to calculate, if null the timestamp is used + * @param string|Zend_Locale $locale Locale for parsing input + * @return integer|string|Zend_Date new timestamp + * @throws Zend_Date_Exception + */ + private function _calculate($calc, $date, $part, $locale) + { + if ($date === null) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('parameter $date must be set, null is not allowed'); + } + + if (($part !== null) && (strlen($part) !== 2) && (Zend_Locale::isLocale($part, null, false))) { + $locale = $part; + $part = null; + } + + if ($locale === null) { + $locale = $this->getLocale(); + } + + $locale = (string) $locale; + + // Create date parts + $year = $this->toString(self::YEAR, 'iso'); + $month = $this->toString(self::MONTH_SHORT, 'iso'); + $day = $this->toString(self::DAY_SHORT, 'iso'); + $hour = $this->toString(self::HOUR_SHORT, 'iso'); + $minute = $this->toString(self::MINUTE_SHORT, 'iso'); + $second = $this->toString(self::SECOND_SHORT, 'iso'); + // If object extract value + if ($date instanceof Zend_Date) { + $date = $date->toString($part, 'iso', $locale); + } + + if (is_array($date) === true) { + if (empty($part) === false) { + switch($part) { + // Fall through + case self::DAY: + case self::DAY_SHORT: + if (isset($date['day']) === true) { + $date = $date['day']; + } + break; + // Fall through + case self::WEEKDAY_SHORT: + case self::WEEKDAY: + case self::WEEKDAY_8601: + case self::WEEKDAY_DIGIT: + case self::WEEKDAY_NARROW: + case self::WEEKDAY_NAME: + if (isset($date['weekday']) === true) { + $date = $date['weekday']; + $part = self::WEEKDAY_DIGIT; + } + break; + case self::DAY_OF_YEAR: + if (isset($date['day_of_year']) === true) { + $date = $date['day_of_year']; + } + break; + // Fall through + case self::MONTH: + case self::MONTH_SHORT: + case self::MONTH_NAME: + case self::MONTH_NAME_SHORT: + case self::MONTH_NAME_NARROW: + if (isset($date['month']) === true) { + $date = $date['month']; + } + break; + // Fall through + case self::YEAR: + case self::YEAR_SHORT: + case self::YEAR_8601: + case self::YEAR_SHORT_8601: + if (isset($date['year']) === true) { + $date = $date['year']; + } + break; + // Fall through + case self::HOUR: + case self::HOUR_AM: + case self::HOUR_SHORT: + case self::HOUR_SHORT_AM: + if (isset($date['hour']) === true) { + $date = $date['hour']; + } + break; + // Fall through + case self::MINUTE: + case self::MINUTE_SHORT: + if (isset($date['minute']) === true) { + $date = $date['minute']; + } + break; + // Fall through + case self::SECOND: + case self::SECOND_SHORT: + if (isset($date['second']) === true) { + $date = $date['second']; + } + break; + // Fall through + case self::TIMEZONE: + case self::TIMEZONE_NAME: + if (isset($date['timezone']) === true) { + $date = $date['timezone']; + } + break; + case self::TIMESTAMP: + if (isset($date['timestamp']) === true) { + $date = $date['timestamp']; + } + break; + case self::WEEK: + if (isset($date['week']) === true) { + $date = $date['week']; + } + break; + case self::TIMEZONE_SECS: + if (isset($date['gmtsecs']) === true) { + $date = $date['gmtsecs']; + } + break; + default: + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("datepart for part ($part) not found in array"); + break; + } + } else { + $hours = 0; + if (isset($date['hour']) === true) { + $hours = $date['hour']; + } + $minutes = 0; + if (isset($date['minute']) === true) { + $minutes = $date['minute']; + } + $seconds = 0; + if (isset($date['second']) === true) { + $seconds = $date['second']; + } + $months = 0; + if (isset($date['month']) === true) { + $months = $date['month']; + } + $days = 0; + if (isset($date['day']) === true) { + $days = $date['day']; + } + $years = 0; + if (isset($date['year']) === true) { + $years = $date['year']; + } + return $this->_assign($calc, $this->mktime($hours, $minutes, $seconds, $months, $days, $years, true), + $this->mktime($hour, $minute, $second, $month, $day, $year, true), $hour); + } + } + + // $date as object, part of foreign date as own date + switch($part) { + + // day formats + case self::DAY: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + intval($date), 1970, true), + $this->mktime(0, 0, 0, 1, 1 + intval($day), 1970, true), $hour); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, day expected", 0, null, $date); + break; + + case self::WEEKDAY_SHORT: + $daylist = Zend_Locale_Data::getList($locale, 'day'); + $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale); + $cnt = 0; + + foreach ($daylist as $key => $value) { + if (strtoupper(iconv_substr($value, 0, 3, 'UTF-8')) == strtoupper($date)) { + $found = $cnt; + break; + } + ++$cnt; + } + + // Weekday found + if ($cnt < 7) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $found, 1970, true), + $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); + } + + // Weekday not found + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); + break; + + case self::DAY_SHORT: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + intval($date), 1970, true), + $this->mktime(0, 0, 0, 1, 1 + intval($day), 1970, true), $hour); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, day expected", 0, null, $date); + break; + + case self::WEEKDAY: + $daylist = Zend_Locale_Data::getList($locale, 'day'); + $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale); + $cnt = 0; + + foreach ($daylist as $key => $value) { + if (strtoupper($value) == strtoupper($date)) { + $found = $cnt; + break; + } + ++$cnt; + } + + // Weekday found + if ($cnt < 7) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $found, 1970, true), + $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); + } + + // Weekday not found + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); + break; + + case self::WEEKDAY_8601: + $weekday = (int) $this->toString(self::WEEKDAY_8601, 'iso', $locale); + if ((intval($date) > 0) and (intval($date) < 8)) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + intval($date), 1970, true), + $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); + } + + // Weekday not found + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); + break; + + case self::DAY_SUFFIX: + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('day suffix not supported', 0, null, $date); + break; + + case self::WEEKDAY_DIGIT: + $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale); + if (is_numeric($date) and (intval($date) >= 0) and (intval($date) < 7)) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $date, 1970, true), + $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); + } + + // Weekday not found + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); + break; + + case self::DAY_OF_YEAR: + if (is_numeric($date)) { + if (($calc == 'add') || ($calc == 'sub')) { + $year = 1970; + ++$date; + ++$day; + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, $date, $year, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, day expected", 0, null, $date); + break; + + case self::WEEKDAY_NARROW: + $daylist = Zend_Locale_Data::getList($locale, 'day', array('gregorian', 'format', 'abbreviated')); + $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale); + $cnt = 0; + foreach ($daylist as $key => $value) { + if (strtoupper(iconv_substr($value, 0, 1, 'UTF-8')) == strtoupper($date)) { + $found = $cnt; + break; + } + ++$cnt; + } + + // Weekday found + if ($cnt < 7) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $found, 1970, true), + $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); + } + + // Weekday not found + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); + break; + + case self::WEEKDAY_NAME: + $daylist = Zend_Locale_Data::getList($locale, 'day', array('gregorian', 'format', 'abbreviated')); + $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale); + $cnt = 0; + foreach ($daylist as $key => $value) { + if (strtoupper($value) == strtoupper($date)) { + $found = $cnt; + break; + } + ++$cnt; + } + + // Weekday found + if ($cnt < 7) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $found, 1970, true), + $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); + } + + // Weekday not found + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); + break; + + // week formats + case self::WEEK: + if (is_numeric($date)) { + $week = (int) $this->toString(self::WEEK, 'iso', $locale); + return $this->_assign($calc, parent::mktime(0, 0, 0, 1, 1 + ($date * 7), 1970, true), + parent::mktime(0, 0, 0, 1, 1 + ($week * 7), 1970, true), $hour); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, week expected", 0, null, $date); + break; + + // month formats + case self::MONTH_NAME: + $monthlist = Zend_Locale_Data::getList($locale, 'month'); + $cnt = 0; + foreach ($monthlist as $key => $value) { + if (strtoupper($value) == strtoupper($date)) { + $found = $key; + break; + } + ++$cnt; + } + $date = array_search($date, $monthlist); + + // Monthname found + if ($cnt < 12) { + $fixday = 0; + if ($calc == 'add') { + $date += $found; + $calc = 'set'; + if (self::$_options['extend_month'] == false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } else if ($calc == 'sub') { + $date = $month - $found; + $calc = 'set'; + if (self::$_options['extend_month'] == false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } + return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); + } + + // Monthname not found + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date); + break; + + case self::MONTH: + if (is_numeric($date)) { + $fixday = 0; + if ($calc == 'add') { + $date += $month; + $calc = 'set'; + if (self::$_options['extend_month'] == false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } else if ($calc == 'sub') { + $date = $month - $date; + $calc = 'set'; + if (self::$_options['extend_month'] == false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } + return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date); + break; + + case self::MONTH_NAME_SHORT: + $monthlist = Zend_Locale_Data::getList($locale, 'month', array('gregorian', 'format', 'abbreviated')); + $cnt = 0; + foreach ($monthlist as $key => $value) { + if (strtoupper($value) == strtoupper($date)) { + $found = $key; + break; + } + ++$cnt; + } + $date = array_search($date, $monthlist); + + // Monthname found + if ($cnt < 12) { + $fixday = 0; + if ($calc == 'add') { + $date += $found; + $calc = 'set'; + if (self::$_options['extend_month'] === false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } else if ($calc == 'sub') { + $date = $month - $found; + $calc = 'set'; + if (self::$_options['extend_month'] === false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } + return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); + } + + // Monthname not found + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date); + break; + + case self::MONTH_SHORT: + if (is_numeric($date) === true) { + $fixday = 0; + if ($calc === 'add') { + $date += $month; + $calc = 'set'; + if (self::$_options['extend_month'] === false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } else if ($calc === 'sub') { + $date = $month - $date; + $calc = 'set'; + if (self::$_options['extend_month'] === false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date); + break; + + case self::MONTH_DAYS: + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('month days not supported', 0, null, $date); + break; + + case self::MONTH_NAME_NARROW: + $monthlist = Zend_Locale_Data::getList($locale, 'month', array('gregorian', 'stand-alone', 'narrow')); + $cnt = 0; + foreach ($monthlist as $key => $value) { + if (strtoupper($value) === strtoupper($date)) { + $found = $key; + break; + } + ++$cnt; + } + $date = array_search($date, $monthlist); + + // Monthname found + if ($cnt < 12) { + $fixday = 0; + if ($calc === 'add') { + $date += $found; + $calc = 'set'; + if (self::$_options['extend_month'] === false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } else if ($calc === 'sub') { + $date = $month - $found; + $calc = 'set'; + if (self::$_options['extend_month'] === false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } + return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); + } + + // Monthname not found + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date); + break; + + // year formats + case self::LEAPYEAR: + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('leap year not supported', 0, null, $date); + break; + + case self::YEAR_8601: + if (is_numeric($date)) { + if ($calc === 'add') { + $date += $year; + $calc = 'set'; + } else if ($calc === 'sub') { + $date = $year - $date; + $calc = 'set'; + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, $month, $day, intval($date), true), + $this->mktime(0, 0, 0, $month, $day, $year, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, year expected", 0, null, $date); + break; + + case self::YEAR: + if (is_numeric($date)) { + if ($calc === 'add') { + $date += $year; + $calc = 'set'; + } else if ($calc === 'sub') { + $date = $year - $date; + $calc = 'set'; + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, $month, $day, intval($date), true), + $this->mktime(0, 0, 0, $month, $day, $year, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, year expected", 0, null, $date); + break; + + case self::YEAR_SHORT: + if (is_numeric($date)) { + $date = intval($date); + if (($calc == 'set') || ($calc == 'cmp')) { + $date = self::getFullYear($date); + } + if ($calc === 'add') { + $date += $year; + $calc = 'set'; + } else if ($calc === 'sub') { + $date = $year - $date; + $calc = 'set'; + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, $month, $day, $date, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, year expected", 0, null, $date); + break; + + case self::YEAR_SHORT_8601: + if (is_numeric($date)) { + $date = intval($date); + if (($calc === 'set') || ($calc === 'cmp')) { + $date = self::getFullYear($date); + } + if ($calc === 'add') { + $date += $year; + $calc = 'set'; + } else if ($calc === 'sub') { + $date = $year - $date; + $calc = 'set'; + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, $month, $day, $date, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, year expected", 0, null, $date); + break; + + // time formats + case self::MERIDIEM: + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('meridiem not supported', 0, null, $date); + break; + + case self::SWATCH: + if (is_numeric($date)) { + $rest = intval($date); + $hours = floor($rest * 24 / 1000); + $rest = $rest - ($hours * 1000 / 24); + $minutes = floor($rest * 1440 / 1000); + $rest = $rest - ($minutes * 1000 / 1440); + $seconds = floor($rest * 86400 / 1000); + return $this->_assign($calc, $this->mktime($hours, $minutes, $seconds, 1, 1, 1970, true), + $this->mktime($hour, $minute, $second, 1, 1, 1970, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, swatchstamp expected", 0, null, $date); + break; + + case self::HOUR_SHORT_AM: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(intval($date), 0, 0, 1, 1, 1970, true), + $this->mktime($hour, 0, 0, 1, 1, 1970, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, hour expected", 0, null, $date); + break; + + case self::HOUR_SHORT: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(intval($date), 0, 0, 1, 1, 1970, true), + $this->mktime($hour, 0, 0, 1, 1, 1970, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, hour expected", 0, null, $date); + break; + + case self::HOUR_AM: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(intval($date), 0, 0, 1, 1, 1970, true), + $this->mktime($hour, 0, 0, 1, 1, 1970, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, hour expected", 0, null, $date); + break; + + case self::HOUR: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(intval($date), 0, 0, 1, 1, 1970, true), + $this->mktime($hour, 0, 0, 1, 1, 1970, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, hour expected", 0, null, $date); + break; + + case self::MINUTE: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(0, intval($date), 0, 1, 1, 1970, true), + $this->mktime(0, $minute, 0, 1, 1, 1970, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, minute expected", 0, null, $date); + break; + + case self::SECOND: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(0, 0, intval($date), 1, 1, 1970, true), + $this->mktime(0, 0, $second, 1, 1, 1970, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, second expected", 0, null, $date); + break; + + case self::MILLISECOND: + if (is_numeric($date)) { + switch($calc) { + case 'set' : + return $this->setMillisecond($date); + break; + case 'add' : + return $this->addMillisecond($date); + break; + case 'sub' : + return $this->subMillisecond($date); + break; + } + + return $this->compareMillisecond($date); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, milliseconds expected", 0, null, $date); + break; + + case self::MINUTE_SHORT: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(0, intval($date), 0, 1, 1, 1970, true), + $this->mktime(0, $minute, 0, 1, 1, 1970, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, minute expected", 0, null, $date); + break; + + case self::SECOND_SHORT: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(0, 0, intval($date), 1, 1, 1970, true), + $this->mktime(0, 0, $second, 1, 1, 1970, true), false); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, second expected", 0, null, $date); + break; + + // timezone formats + // break intentionally omitted + case self::TIMEZONE_NAME: + case self::TIMEZONE: + case self::TIMEZONE_SECS: + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('timezone not supported', 0, null, $date); + break; + + case self::DAYLIGHT: + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('daylight not supported', 0, null, $date); + break; + + case self::GMT_DIFF: + case self::GMT_DIFF_SEP: + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('gmtdiff not supported', 0, null, $date); + break; + + // date strings + case self::ISO_8601: + // (-)YYYY-MM-dd + preg_match('/^(-{0,1}\d{4})-(\d{2})-(\d{2})/', $date, $datematch); + // (-)YY-MM-dd + if (empty($datematch)) { + preg_match('/^(-{0,1}\d{2})-(\d{2})-(\d{2})/', $date, $datematch); + } + // (-)YYYYMMdd + if (empty($datematch)) { + preg_match('/^(-{0,1}\d{4})(\d{2})(\d{2})/', $date, $datematch); + } + // (-)YYMMdd + if (empty($datematch)) { + preg_match('/^(-{0,1}\d{2})(\d{2})(\d{2})/', $date, $datematch); + } + $tmpdate = $date; + if (!empty($datematch)) { + $dateMatchCharCount = iconv_strlen($datematch[0], 'UTF-8'); + $tmpdate = iconv_substr($date, + $dateMatchCharCount, + iconv_strlen($date, 'UTF-8') - $dateMatchCharCount, + 'UTF-8'); + } + // (T)hh:mm:ss + preg_match('/[T,\s]{0,1}(\d{2}):(\d{2}):(\d{2})/', $tmpdate, $timematch); + if (empty($timematch)) { + preg_match('/[T,\s]{0,1}(\d{2})(\d{2})(\d{2})/', $tmpdate, $timematch); + } + if (empty($datematch) and empty($timematch)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("unsupported ISO8601 format ($date)", 0, null, $date); + } + if (!empty($timematch)) { + $timeMatchCharCount = iconv_strlen($timematch[0], 'UTF-8'); + $tmpdate = iconv_substr($tmpdate, + $timeMatchCharCount, + iconv_strlen($tmpdate, 'UTF-8') - $timeMatchCharCount, + 'UTF-8'); + } + if (empty($datematch)) { + $datematch[1] = 1970; + $datematch[2] = 1; + $datematch[3] = 1; + } else if (iconv_strlen($datematch[1], 'UTF-8') == 2) { + $datematch[1] = self::getFullYear($datematch[1]); + } + if (empty($timematch)) { + $timematch[1] = 0; + $timematch[2] = 0; + $timematch[3] = 0; + } + + if (($calc == 'set') || ($calc == 'cmp')) { + --$datematch[2]; + --$month; + --$datematch[3]; + --$day; + $datematch[1] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($timematch[1], $timematch[2], $timematch[3], 1 + $datematch[2], 1 + $datematch[3], 1970 + $datematch[1], false), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, false), false); + break; + + case self::RFC_2822: + $result = preg_match('/^\w{3},\s(\d{1,2})\s(\w{3})\s(\d{4})\s' + . '(\d{2}):(\d{2}):{0,1}(\d{0,2})\s([+-]' + . '{1}\d{4}|\w{1,20})$/', $date, $match); + + if (!$result) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no RFC 2822 format ($date)", 0, null, $date); + } + + $months = $this->_getDigitFromName($match[2]); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$months; + --$month; + --$match[1]; + --$day; + $match[3] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], false), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, false), false); + break; + + case self::TIMESTAMP: + if (is_numeric($date)) { + return $this->_assign($calc, $date, $this->getUnixTimestamp()); + } + + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, timestamp expected", 0, null, $date); + break; + + // additional formats + // break intentionally omitted + case self::ERA: + case self::ERA_NAME: + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('era not supported', 0, null, $date); + break; + + case self::DATES: + try { + $parsed = Zend_Locale_Format::getDate($date, array('locale' => $locale, 'format_type' => 'iso', 'fix_date' => true)); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATE_FULL: + try { + $format = Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'full')); + $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATE_LONG: + try { + $format = Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'long')); + $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + if (($calc == 'set') || ($calc == 'cmp')){ + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATE_MEDIUM: + try { + $format = Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'medium')); + $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATE_SHORT: + try { + $format = Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'short')); + $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + $parsed['year'] = self::getFullYear($parsed['year']); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::TIMES: + try { + if ($calc != 'set') { + $month = 1; + $day = 1; + $year = 1970; + } + $parsed = Zend_Locale_Format::getTime($date, array('locale' => $locale, 'format_type' => 'iso', 'fix_date' => true)); + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true), + $this->mktime($hour, $minute, $second, $month, $day, $year, true), false); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::TIME_FULL: + try { + $format = Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'full')); + $parsed = Zend_Locale_Format::getTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + if ($calc != 'set') { + $month = 1; + $day = 1; + $year = 1970; + } + + if (!isset($parsed['second'])) { + $parsed['second'] = 0; + } + + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true), + $this->mktime($hour, $minute, $second, $month, $day, $year, true), false); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::TIME_LONG: + try { + $format = Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'long')); + $parsed = Zend_Locale_Format::getTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + if ($calc != 'set') { + $month = 1; + $day = 1; + $year = 1970; + } + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true), + $this->mktime($hour, $minute, $second, $month, $day, $year, true), false); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::TIME_MEDIUM: + try { + $format = Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'medium')); + $parsed = Zend_Locale_Format::getTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + if ($calc != 'set') { + $month = 1; + $day = 1; + $year = 1970; + } + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true), + $this->mktime($hour, $minute, $second, $month, $day, $year, true), false); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::TIME_SHORT: + try { + $format = Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'short')); + $parsed = Zend_Locale_Format::getTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + if ($calc != 'set') { + $month = 1; + $day = 1; + $year = 1970; + } + + if (!isset($parsed['second'])) { + $parsed['second'] = 0; + } + + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true), + $this->mktime($hour, $minute, $second, $month, $day, $year, true), false); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATETIME: + try { + $parsed = Zend_Locale_Format::getDateTime($date, array('locale' => $locale, 'format_type' => 'iso', 'fix_date' => true)); + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATETIME_FULL: + try { + $format = Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'full')); + $parsed = Zend_Locale_Format::getDateTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + + if (!isset($parsed['second'])) { + $parsed['second'] = 0; + } + + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATETIME_LONG: + try { + $format = Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'long')); + $parsed = Zend_Locale_Format::getDateTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + if (($calc == 'set') || ($calc == 'cmp')){ + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATETIME_MEDIUM: + try { + $format = Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'medium')); + $parsed = Zend_Locale_Format::getDateTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATETIME_SHORT: + try { + $format = Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'short')); + $parsed = Zend_Locale_Format::getDateTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + $parsed['year'] = self::getFullYear($parsed['year']); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + + if (!isset($parsed['second'])) { + $parsed['second'] = 0; + } + + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + // ATOM and RFC_3339 are identical + case self::ATOM: + case self::RFC_3339: + $result = preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\d{0,4}([+-]{1}\d{2}:\d{2}|Z)$/', $date, $match); + if (!$result) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, ATOM format expected", 0, null, $date); + } + + if (($calc == 'set') || ($calc == 'cmp')) { + --$match[2]; + --$month; + --$match[3]; + --$day; + $match[1] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $match[2], 1 + $match[3], 1970 + $match[1], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); + break; + + case self::COOKIE: + $result = preg_match("/^\w{6,9},\s(\d{2})-(\w{3})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})\s.{3,20}$/", $date, $match); + if (!$result) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, COOKIE format expected", 0, null, $date); + } + $matchStartPos = iconv_strpos($match[0], ' ', 0, 'UTF-8') + 1; + $match[0] = iconv_substr($match[0], + $matchStartPos, + iconv_strlen($match[0], 'UTF-8') - $matchStartPos, + 'UTF-8'); + + $months = $this->_getDigitFromName($match[2]); + $match[3] = self::getFullYear($match[3]); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$months; + --$month; + --$match[1]; + --$day; + $match[3] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); + break; + + case self::RFC_822: + case self::RFC_1036: + // new RFC 822 format, identical to RFC 1036 standard + $result = preg_match('/^\w{0,3},{0,1}\s{0,1}(\d{1,2})\s(\w{3})\s(\d{2})\s(\d{2}):(\d{2}):{0,1}(\d{0,2})\s([+-]{1}\d{4}|\w{1,20})$/', $date, $match); + if (!$result) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, RFC 822 date format expected", 0, null, $date); + } + + $months = $this->_getDigitFromName($match[2]); + $match[3] = self::getFullYear($match[3]); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$months; + --$month; + --$match[1]; + --$day; + $match[3] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], false), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, false), false); + break; + + case self::RFC_850: + $result = preg_match('/^\w{6,9},\s(\d{2})-(\w{3})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})\s.{3,21}$/', $date, $match); + if (!$result) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, RFC 850 date format expected", 0, null, $date); + } + + $months = $this->_getDigitFromName($match[2]); + $match[3] = self::getFullYear($match[3]); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$months; + --$month; + --$match[1]; + --$day; + $match[3] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); + break; + + case self::RFC_1123: + $result = preg_match('/^\w{0,3},{0,1}\s{0,1}(\d{1,2})\s(\w{3})\s(\d{2,4})\s(\d{2}):(\d{2}):{0,1}(\d{0,2})\s([+-]{1}\d{4}|\w{1,20})$/', $date, $match); + if (!$result) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, RFC 1123 date format expected", 0, null, $date); + } + + $months = $this->_getDigitFromName($match[2]); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$months; + --$month; + --$match[1]; + --$day; + $match[3] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); + break; + + case self::RSS: + $result = preg_match('/^\w{3},\s(\d{2})\s(\w{3})\s(\d{2,4})\s(\d{1,2}):(\d{2}):(\d{2})\s.{1,21}$/', $date, $match); + if (!$result) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, RSS date format expected", 0, null, $date); + } + + $months = $this->_getDigitFromName($match[2]); + $match[3] = self::getFullYear($match[3]); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$months; + --$month; + --$match[1]; + --$day; + $match[3] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); + break; + + case self::W3C: + $result = preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})[+-]{1}\d{2}:\d{2}$/', $date, $match); + if (!$result) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, W3C date format expected", 0, null, $date); + } + + if (($calc == 'set') || ($calc == 'cmp')) { + --$match[2]; + --$month; + --$match[3]; + --$day; + $match[1] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $match[2], 1 + $match[3], 1970 + $match[1], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); + break; + + default: + if (!is_numeric($date) || !empty($part)) { + try { + if (empty($part)) { + $part = Zend_Locale_Format::getDateFormat($locale) . " "; + $part .= Zend_Locale_Format::getTimeFormat($locale); + } + + $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $part, 'locale' => $locale, 'fix_date' => true, 'format_type' => 'iso')); + if ((strpos(strtoupper($part), 'YY') !== false) and (strpos(strtoupper($part), 'YYYY') === false)) { + $parsed['year'] = self::getFullYear($parsed['year']); + } + + if (($calc == 'set') || ($calc == 'cmp')) { + if (isset($parsed['month'])) { + --$parsed['month']; + } else { + $parsed['month'] = 0; + } + + if (isset($parsed['day'])) { + --$parsed['day']; + } else { + $parsed['day'] = 0; + } + + if (isset($parsed['year'])) { + $parsed['year'] -= 1970; + } else { + $parsed['year'] = 0; + } + } + + return $this->_assign($calc, $this->mktime( + isset($parsed['hour']) ? $parsed['hour'] : 0, + isset($parsed['minute']) ? $parsed['minute'] : 0, + isset($parsed['second']) ? $parsed['second'] : 0, + isset($parsed['month']) ? (1 + $parsed['month']) : 1, + isset($parsed['day']) ? (1 + $parsed['day']) : 1, + isset($parsed['year']) ? (1970 + $parsed['year']) : 1970, + false), $this->getUnixTimestamp(), false); + } catch (Zend_Locale_Exception $e) { + if (!is_numeric($date)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + } + } + + return $this->_assign($calc, $date, $this->getUnixTimestamp(), false); + break; + } + } + + /** + * Returns true when both date objects or date parts are equal. + * For example: + * 15.May.2000 <-> 15.June.2000 Equals only for Day or Year... all other will return false + * + * @param string|integer|array|Zend_Date $date Date or datepart to equal with + * @param string $part OPTIONAL Part of the date to compare, if null the timestamp is used + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return boolean + * @throws Zend_Date_Exception + */ + public function equals($date, $part = self::TIMESTAMP, $locale = null) + { + $result = $this->compare($date, $part, $locale); + + if ($result == 0) { + return true; + } + + return false; + } + + /** + * Returns if the given date or datepart is earlier + * For example: + * 15.May.2000 <-> 13.June.1999 will return true for day, year and date, but not for month + * + * @param string|integer|array|Zend_Date $date Date or datepart to compare with + * @param string $part OPTIONAL Part of the date to compare, if null the timestamp is used + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return boolean + * @throws Zend_Date_Exception + */ + public function isEarlier($date, $part = null, $locale = null) + { + $result = $this->compare($date, $part, $locale); + + if ($result == -1) { + return true; + } + + return false; + } + + /** + * Returns if the given date or datepart is later + * For example: + * 15.May.2000 <-> 13.June.1999 will return true for month but false for day, year and date + * Returns if the given date is later + * + * @param string|integer|array|Zend_Date $date Date or datepart to compare with + * @param string $part OPTIONAL Part of the date to compare, if null the timestamp is used + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return boolean + * @throws Zend_Date_Exception + */ + public function isLater($date, $part = null, $locale = null) + { + $result = $this->compare($date, $part, $locale); + + if ($result == 1) { + return true; + } + + return false; + } + + /** + * Returns only the time of the date as new Zend_Date object + * For example: + * 15.May.2000 10:11:23 will return a dateobject equal to 01.Jan.1970 10:11:23 + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getTime($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'H:i:s'; + } else { + $format = self::TIME_MEDIUM; + } + + return $this->copyPart($format, $locale); + } + + /** + * Returns the calculated time + * + * @param string $calc Calculation to make + * @param string|integer|array|Zend_Date $time Time to calculate with, if null the actual time is taken + * @param string $format Timeformat for parsing input + * @param string|Zend_Locale $locale Locale for parsing input + * @return integer|Zend_Date new time + * @throws Zend_Date_Exception + */ + private function _time($calc, $time, $format, $locale) + { + if ($time === null) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('parameter $time must be set, null is not allowed'); + } + + if ($time instanceof Zend_Date) { + // extract time from object + $time = $time->toString('HH:mm:ss', 'iso'); + } else { + if (is_array($time)) { + if ((isset($time['hour']) === true) or (isset($time['minute']) === true) or + (isset($time['second']) === true)) { + $parsed = $time; + } else { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no hour, minute or second given in array"); + } + } else { + if (self::$_options['format_type'] == 'php') { + $format = Zend_Locale_Format::convertPhpToIsoFormat($format); + } + try { + if ($locale === null) { + $locale = $this->getLocale(); + } + + $parsed = Zend_Locale_Format::getTime($time, array('date_format' => $format, 'locale' => $locale, 'format_type' => 'iso')); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e); + } + } + + if (!array_key_exists('hour', $parsed)) { + $parsed['hour'] = 0; + } + + if (!array_key_exists('minute', $parsed)) { + $parsed['minute'] = 0; + } + + if (!array_key_exists('second', $parsed)) { + $parsed['second'] = 0; + } + + $time = str_pad($parsed['hour'], 2, '0', STR_PAD_LEFT) . ":"; + $time .= str_pad($parsed['minute'], 2, '0', STR_PAD_LEFT) . ":"; + $time .= str_pad($parsed['second'], 2, '0', STR_PAD_LEFT); + } + + $return = $this->_calcdetail($calc, $time, self::TIMES, 'de'); + if ($calc != 'cmp') { + return $this; + } + + return $return; + } + + + /** + * Sets a new time for the date object. Format defines how to parse the time string. + * Also a complete date can be given, but only the time is used for setting. + * For example: dd.MMMM.yyTHH:mm' and 'ss sec'-> 10.May.07T25:11 and 44 sec => 1h11min44sec + 1 day + * Returned is the new date object and the existing date is left as it was before + * + * @param string|integer|array|Zend_Date $time Time to set + * @param string $format OPTIONAL Timeformat for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setTime($time, $format = null, $locale = null) + { + return $this->_time('set', $time, $format, $locale); + } + + + /** + * Adds a time to the existing date. Format defines how to parse the time string. + * If only parts are given the other parts are set to 0. + * If no format is given, the standardformat of this locale is used. + * For example: HH:mm:ss -> 10 -> +10 hours + * + * @param string|integer|array|Zend_Date $time Time to add + * @param string $format OPTIONAL Timeformat for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addTime($time, $format = null, $locale = null) + { + return $this->_time('add', $time, $format, $locale); + } + + + /** + * Subtracts a time from the existing date. Format defines how to parse the time string. + * If only parts are given the other parts are set to 0. + * If no format is given, the standardformat of this locale is used. + * For example: HH:mm:ss -> 10 -> -10 hours + * + * @param string|integer|array|Zend_Date $time Time to sub + * @param string $format OPTIONAL Timeformat for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid inteface + * @throws Zend_Date_Exception + */ + public function subTime($time, $format = null, $locale = null) + { + return $this->_time('sub', $time, $format, $locale); + } + + + /** + * Compares the time from the existing date. Format defines how to parse the time string. + * If only parts are given the other parts are set to default. + * If no format us given, the standardformat of this locale is used. + * For example: HH:mm:ss -> 10 -> 10 hours + * + * @param string|integer|array|Zend_Date $time Time to compare + * @param string $format OPTIONAL Timeformat for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareTime($time, $format = null, $locale = null) + { + return $this->_time('cmp', $time, $format, $locale); + } + + /** + * Returns a clone of $this, with the time part set to 00:00:00. + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getDate($locale = null) + { + $orig = self::$_options['format_type']; + if (self::$_options['format_type'] == 'php') { + self::$_options['format_type'] = 'iso'; + } + + $date = $this->copyPart(self::DATE_MEDIUM, $locale); + $date->addTimestamp($this->getGmtOffset()); + self::$_options['format_type'] = $orig; + + return $date; + } + + /** + * Returns the calculated date + * + * @param string $calc Calculation to make + * @param string|integer|array|Zend_Date $date Date to calculate with, if null the actual date is taken + * @param string $format Date format for parsing + * @param string|Zend_Locale $locale Locale for parsing input + * @return integer|Zend_Date new date + * @throws Zend_Date_Exception + */ + private function _date($calc, $date, $format, $locale) + { + if ($date === null) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('parameter $date must be set, null is not allowed'); + } + + if ($date instanceof Zend_Date) { + // extract date from object + $date = $date->toString('d.M.y', 'iso'); + } else { + if (is_array($date)) { + if ((isset($date['year']) === true) or (isset($date['month']) === true) or + (isset($date['day']) === true)) { + $parsed = $date; + } else { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no day,month or year given in array"); + } + } else { + if ((self::$_options['format_type'] == 'php') && !defined($format)) { + $format = Zend_Locale_Format::convertPhpToIsoFormat($format); + } + try { + if ($locale === null) { + $locale = $this->getLocale(); + } + + $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'locale' => $locale, 'format_type' => 'iso')); + if ((strpos(strtoupper($format), 'YY') !== false) and (strpos(strtoupper($format), 'YYYY') === false)) { + $parsed['year'] = self::getFullYear($parsed['year']); + } + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e); + } + } + + if (!array_key_exists('day', $parsed)) { + $parsed['day'] = 1; + } + + if (!array_key_exists('month', $parsed)) { + $parsed['month'] = 1; + } + + if (!array_key_exists('year', $parsed)) { + $parsed['year'] = 0; + } + + $date = $parsed['day'] . "." . $parsed['month'] . "." . $parsed['year']; + } + + $return = $this->_calcdetail($calc, $date, self::DATE_MEDIUM, 'de'); + if ($calc != 'cmp') { + return $this; + } + return $return; + } + + + /** + * Sets a new date for the date object. Format defines how to parse the date string. + * Also a complete date with time can be given, but only the date is used for setting. + * For example: MMMM.yy HH:mm-> May.07 22:11 => 01.May.07 00:00 + * Returned is the new date object and the existing time is left as it was before + * + * @param string|integer|array|Zend_Date $date Date to set + * @param string $format OPTIONAL Date format for parsing + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setDate($date, $format = null, $locale = null) + { + return $this->_date('set', $date, $format, $locale); + } + + + /** + * Adds a date to the existing date object. Format defines how to parse the date string. + * If only parts are given the other parts are set to 0. + * If no format is given, the standardformat of this locale is used. + * For example: MM.dd.YYYY -> 10 -> +10 months + * + * @param string|integer|array|Zend_Date $date Date to add + * @param string $format OPTIONAL Date format for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addDate($date, $format = null, $locale = null) + { + return $this->_date('add', $date, $format, $locale); + } + + + /** + * Subtracts a date from the existing date object. Format defines how to parse the date string. + * If only parts are given the other parts are set to 0. + * If no format is given, the standardformat of this locale is used. + * For example: MM.dd.YYYY -> 10 -> -10 months + * Be aware: Subtracting 2 months is not equal to Adding -2 months !!! + * + * @param string|integer|array|Zend_Date $date Date to sub + * @param string $format OPTIONAL Date format for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subDate($date, $format = null, $locale = null) + { + return $this->_date('sub', $date, $format, $locale); + } + + + /** + * Compares the date from the existing date object, ignoring the time. + * Format defines how to parse the date string. + * If only parts are given the other parts are set to 0. + * If no format is given, the standardformat of this locale is used. + * For example: 10.01.2000 => 10.02.1999 -> false + * + * @param string|integer|array|Zend_Date $date Date to compare + * @param string $format OPTIONAL Date format for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareDate($date, $format = null, $locale = null) + { + return $this->_date('cmp', $date, $format, $locale); + } + + + /** + * Returns the full ISO 8601 date from the date object. + * Always the complete ISO 8601 specifiction is used. If an other ISO date is needed + * (ISO 8601 defines several formats) use toString() instead. + * This function does not return the ISO date as object. Use copy() instead. + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return string + */ + public function getIso($locale = null) + { + return $this->toString(self::ISO_8601, 'iso', $locale); + } + + + /** + * Sets a new date for the date object. Not given parts are set to default. + * Only supported ISO 8601 formats are accepted. + * For example: 050901 -> 01.Sept.2005 00:00:00, 20050201T10:00:30 -> 01.Feb.2005 10h00m30s + * Returned is the new date object + * + * @param string|integer|Zend_Date $date ISO Date to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setIso($date, $locale = null) + { + return $this->_calcvalue('set', $date, 'iso', self::ISO_8601, $locale); + } + + + /** + * Adds a ISO date to the date object. Not given parts are set to default. + * Only supported ISO 8601 formats are accepted. + * For example: 050901 -> + 01.Sept.2005 00:00:00, 10:00:00 -> +10h + * Returned is the new date object + * + * @param string|integer|Zend_Date $date ISO Date to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addIso($date, $locale = null) + { + return $this->_calcvalue('add', $date, 'iso', self::ISO_8601, $locale); + } + + + /** + * Subtracts a ISO date from the date object. Not given parts are set to default. + * Only supported ISO 8601 formats are accepted. + * For example: 050901 -> - 01.Sept.2005 00:00:00, 10:00:00 -> -10h + * Returned is the new date object + * + * @param string|integer|Zend_Date $date ISO Date to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subIso($date, $locale = null) + { + return $this->_calcvalue('sub', $date, 'iso', self::ISO_8601, $locale); + } + + + /** + * Compares a ISO date with the date object. Not given parts are set to default. + * Only supported ISO 8601 formats are accepted. + * For example: 050901 -> - 01.Sept.2005 00:00:00, 10:00:00 -> -10h + * Returns if equal, earlier or later + * + * @param string|integer|Zend_Date $date ISO Date to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareIso($date, $locale = null) + { + return $this->_calcvalue('cmp', $date, 'iso', self::ISO_8601, $locale); + } + + + /** + * Returns a RFC 822 compilant datestring from the date object. + * This function does not return the RFC date as object. Use copy() instead. + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return string + */ + public function getArpa($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'D\, d M y H\:i\:s O'; + } else { + $format = self::RFC_822; + } + + return $this->toString($format, 'iso', $locale); + } + + + /** + * Sets a RFC 822 date as new date for the date object. + * Only RFC 822 compilant date strings are accepted. + * For example: Sat, 14 Feb 09 00:31:30 +0100 + * Returned is the new date object + * + * @param string|integer|Zend_Date $date RFC 822 to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setArpa($date, $locale = null) + { + return $this->_calcvalue('set', $date, 'arpa', self::RFC_822, $locale); + } + + + /** + * Adds a RFC 822 date to the date object. + * ARPA messages are used in emails or HTTP Headers. + * Only RFC 822 compilant date strings are accepted. + * For example: Sat, 14 Feb 09 00:31:30 +0100 + * Returned is the new date object + * + * @param string|integer|Zend_Date $date RFC 822 Date to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addArpa($date, $locale = null) + { + return $this->_calcvalue('add', $date, 'arpa', self::RFC_822, $locale); + } + + + /** + * Subtracts a RFC 822 date from the date object. + * ARPA messages are used in emails or HTTP Headers. + * Only RFC 822 compilant date strings are accepted. + * For example: Sat, 14 Feb 09 00:31:30 +0100 + * Returned is the new date object + * + * @param string|integer|Zend_Date $date RFC 822 Date to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subArpa($date, $locale = null) + { + return $this->_calcvalue('sub', $date, 'arpa', self::RFC_822, $locale); + } + + + /** + * Compares a RFC 822 compilant date with the date object. + * ARPA messages are used in emails or HTTP Headers. + * Only RFC 822 compilant date strings are accepted. + * For example: Sat, 14 Feb 09 00:31:30 +0100 + * Returns if equal, earlier or later + * + * @param string|integer|Zend_Date $date RFC 822 Date to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareArpa($date, $locale = null) + { + return $this->_calcvalue('cmp', $date, 'arpa', self::RFC_822, $locale); + } + + + /** + * Check if location is supported + * + * @param array $location locations array + * @return $horizon float + */ + private function _checkLocation($location) + { + if (!isset($location['longitude']) or !isset($location['latitude'])) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('Location must include \'longitude\' and \'latitude\'', 0, null, $location); + } + if (($location['longitude'] > 180) or ($location['longitude'] < -180)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('Longitude must be between -180 and 180', 0, null, $location); + } + if (($location['latitude'] > 90) or ($location['latitude'] < -90)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('Latitude must be between -90 and 90', 0, null, $location); + } + + if (!isset($location['horizon'])){ + $location['horizon'] = 'effective'; + } + + switch ($location['horizon']) { + case 'civil' : + return -0.104528; + break; + case 'nautic' : + return -0.207912; + break; + case 'astronomic' : + return -0.309017; + break; + default : + return -0.0145439; + break; + } + } + + + /** + * Returns the time of sunrise for this date and a given location as new date object + * For a list of cities and correct locations use the class Zend_Date_Cities + * + * @param array $location location of sunrise + * ['horizon'] -> civil, nautic, astronomical, effective (default) + * ['longitude'] -> longitude of location + * ['latitude'] -> latitude of location + * @return Zend_Date + * @throws Zend_Date_Exception + */ + public function getSunrise($location) + { + $horizon = $this->_checkLocation($location); + $result = clone $this; + $result->set($this->calcSun($location, $horizon, true), self::TIMESTAMP); + return $result; + } + + + /** + * Returns the time of sunset for this date and a given location as new date object + * For a list of cities and correct locations use the class Zend_Date_Cities + * + * @param array $location location of sunset + * ['horizon'] -> civil, nautic, astronomical, effective (default) + * ['longitude'] -> longitude of location + * ['latitude'] -> latitude of location + * @return Zend_Date + * @throws Zend_Date_Exception + */ + public function getSunset($location) + { + $horizon = $this->_checkLocation($location); + $result = clone $this; + $result->set($this->calcSun($location, $horizon, false), self::TIMESTAMP); + return $result; + } + + + /** + * Returns an array with the sunset and sunrise dates for all horizon types + * For a list of cities and correct locations use the class Zend_Date_Cities + * + * @param array $location location of suninfo + * ['horizon'] -> civil, nautic, astronomical, effective (default) + * ['longitude'] -> longitude of location + * ['latitude'] -> latitude of location + * @return array - [sunset|sunrise][effective|civil|nautic|astronomic] + * @throws Zend_Date_Exception + */ + public function getSunInfo($location) + { + $suninfo = array(); + for ($i = 0; $i < 4; ++$i) { + switch ($i) { + case 0 : + $location['horizon'] = 'effective'; + break; + case 1 : + $location['horizon'] = 'civil'; + break; + case 2 : + $location['horizon'] = 'nautic'; + break; + case 3 : + $location['horizon'] = 'astronomic'; + break; + } + $horizon = $this->_checkLocation($location); + $result = clone $this; + $result->set($this->calcSun($location, $horizon, true), self::TIMESTAMP); + $suninfo['sunrise'][$location['horizon']] = $result; + $result = clone $this; + $result->set($this->calcSun($location, $horizon, false), self::TIMESTAMP); + $suninfo['sunset'][$location['horizon']] = $result; + } + return $suninfo; + } + + + /** + * Check a given year for leap year. + * + * @param integer|array|Zend_Date $year Year to check + * @return boolean + */ + public static function checkLeapYear($year) + { + if ($year instanceof Zend_Date) { + $year = (int) $year->toString(self::YEAR, 'iso'); + } + + if (is_array($year)) { + if (isset($year['year']) === true) { + $year = $year['year']; + } else { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no year given in array"); + } + } + + if (!is_numeric($year)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("year ($year) has to be integer for checkLeapYear()", 0, null, $year); + } + + return (bool) parent::isYearLeapYear($year); + } + + + /** + * Returns true, if the year is a leap year. + * + * @return boolean + */ + public function isLeapYear() + { + return self::checkLeapYear($this); + } + + + /** + * Returns if the set date is todays date + * + * @return boolean + */ + public function isToday() + { + $today = $this->date('Ymd', $this->_getTime()); + $day = $this->date('Ymd', $this->getUnixTimestamp()); + return ($today == $day); + } + + + /** + * Returns if the set date is yesterdays date + * + * @return boolean + */ + public function isYesterday() + { + list($year, $month, $day) = explode('-', $this->date('Y-m-d', $this->_getTime())); + // adjusts for leap days and DST changes that are timezone specific + $yesterday = $this->date('Ymd', $this->mktime(0, 0, 0, $month, $day -1, $year)); + $day = $this->date('Ymd', $this->getUnixTimestamp()); + return $day == $yesterday; + } + + + /** + * Returns if the set date is tomorrows date + * + * @return boolean + */ + public function isTomorrow() + { + list($year, $month, $day) = explode('-', $this->date('Y-m-d', $this->_getTime())); + // adjusts for leap days and DST changes that are timezone specific + $tomorrow = $this->date('Ymd', $this->mktime(0, 0, 0, $month, $day +1, $year)); + $day = $this->date('Ymd', $this->getUnixTimestamp()); + return $day == $tomorrow; + } + + /** + * Returns the actual date as new date object + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public static function now($locale = null) + { + return new Zend_Date(time(), self::TIMESTAMP, $locale); + } + + /** + * Calculate date details + * + * @param string $calc Calculation to make + * @param string|integer|array|Zend_Date $date Date or Part to calculate + * @param string $part Datepart for Calculation + * @param string|Zend_Locale $locale Locale for parsing input + * @return integer|string new date + * @throws Zend_Date_Exception + */ + private function _calcdetail($calc, $date, $type, $locale) + { + $old = false; + if (self::$_options['format_type'] == 'php') { + self::$_options['format_type'] = 'iso'; + $old = true; + } + + switch($calc) { + case 'set' : + $return = $this->set($date, $type, $locale); + break; + case 'add' : + $return = $this->add($date, $type, $locale); + break; + case 'sub' : + $return = $this->sub($date, $type, $locale); + break; + default : + $return = $this->compare($date, $type, $locale); + break; + } + + if ($old) { + self::$_options['format_type'] = 'php'; + } + + return $return; + } + + /** + * Internal calculation, returns the requested date type + * + * @param string $calc Calculation to make + * @param string|integer|Zend_Date $value Datevalue to calculate with, if null the actual value is taken + * @param string|Zend_Locale $locale Locale for parsing input + * @return integer|Zend_Date new date + * @throws Zend_Date_Exception + */ + private function _calcvalue($calc, $value, $type, $parameter, $locale) + { + if ($value === null) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("parameter $type must be set, null is not allowed"); + } + + if ($locale === null) { + $locale = $this->getLocale(); + } + + if ($value instanceof Zend_Date) { + // extract value from object + $value = $value->toString($parameter, 'iso', $locale); + } else if (!is_array($value) && !is_numeric($value) && ($type != 'iso') && ($type != 'arpa')) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid $type ($value) operand", 0, null, $value); + } + + $return = $this->_calcdetail($calc, $value, $parameter, $locale); + if ($calc != 'cmp') { + return $this; + } + return $return; + } + + + /** + * Returns only the year from the date object as new object. + * For example: 10.May.2000 10:30:00 -> 01.Jan.2000 00:00:00 + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getYear($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'Y'; + } else { + $format = self::YEAR; + } + + return $this->copyPart($format, $locale); + } + + + /** + * Sets a new year + * If the year is between 0 and 69, 2000 will be set (2000-2069) + * If the year if between 70 and 99, 1999 will be set (1970-1999) + * 3 or 4 digit years are set as expected. If you need to set year 0-99 + * use set() instead. + * Returned is the new date object + * + * @param string|integer|array|Zend_Date $date Year to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setYear($year, $locale = null) + { + return $this->_calcvalue('set', $year, 'year', self::YEAR, $locale); + } + + + /** + * Adds the year to the existing date object + * If the year is between 0 and 69, 2000 will be added (2000-2069) + * If the year if between 70 and 99, 1999 will be added (1970-1999) + * 3 or 4 digit years are added as expected. If you need to add years from 0-99 + * use add() instead. + * Returned is the new date object + * + * @param string|integer|array|Zend_Date $date Year to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addYear($year, $locale = null) + { + return $this->_calcvalue('add', $year, 'year', self::YEAR, $locale); + } + + + /** + * Subs the year from the existing date object + * If the year is between 0 and 69, 2000 will be subtracted (2000-2069) + * If the year if between 70 and 99, 1999 will be subtracted (1970-1999) + * 3 or 4 digit years are subtracted as expected. If you need to subtract years from 0-99 + * use sub() instead. + * Returned is the new date object + * + * @param string|integer|array|Zend_Date $date Year to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subYear($year, $locale = null) + { + return $this->_calcvalue('sub', $year, 'year', self::YEAR, $locale); + } + + + /** + * Compares the year with the existing date object, ignoring other date parts. + * For example: 10.03.2000 -> 15.02.2000 -> true + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $year Year to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareYear($year, $locale = null) + { + return $this->_calcvalue('cmp', $year, 'year', self::YEAR, $locale); + } + + + /** + * Returns only the month from the date object as new object. + * For example: 10.May.2000 10:30:00 -> 01.May.1970 00:00:00 + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getMonth($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'm'; + } else { + $format = self::MONTH; + } + + return $this->copyPart($format, $locale); + } + + + /** + * Returns the calculated month + * + * @param string $calc Calculation to make + * @param string|integer|array|Zend_Date $month Month to calculate with, if null the actual month is taken + * @param string|Zend_Locale $locale Locale for parsing input + * @return integer|Zend_Date new time + * @throws Zend_Date_Exception + */ + private function _month($calc, $month, $locale) + { + if ($month === null) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('parameter $month must be set, null is not allowed'); + } + + if ($locale === null) { + $locale = $this->getLocale(); + } + + if ($month instanceof Zend_Date) { + // extract month from object + $found = $month->toString(self::MONTH_SHORT, 'iso', $locale); + } else { + if (is_numeric($month)) { + $found = $month; + } else if (is_array($month)) { + if (isset($month['month']) === true) { + $month = $month['month']; + } else { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no month given in array"); + } + } else { + $monthlist = Zend_Locale_Data::getList($locale, 'month'); + $monthlist2 = Zend_Locale_Data::getList($locale, 'month', array('gregorian', 'format', 'abbreviated')); + + $monthlist = array_merge($monthlist, $monthlist2); + $found = 0; + $cnt = 0; + foreach ($monthlist as $key => $value) { + if (strtoupper($value) == strtoupper($month)) { + $found = ($key % 12) + 1; + break; + } + ++$cnt; + } + if ($found == 0) { + foreach ($monthlist2 as $key => $value) { + if (strtoupper(iconv_substr($value, 0, 1, 'UTF-8')) == strtoupper($month)) { + $found = $key + 1; + break; + } + ++$cnt; + } + } + if ($found == 0) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("unknown month name ($month)", 0, null, $month); + } + } + } + $return = $this->_calcdetail($calc, $found, self::MONTH_SHORT, $locale); + if ($calc != 'cmp') { + return $this; + } + return $return; + } + + + /** + * Sets a new month + * The month can be a number or a string. Setting months lower then 0 and greater then 12 + * will result in adding or subtracting the relevant year. (12 months equal one year) + * If a localized monthname is given it will be parsed with the default locale or the optional + * set locale. + * Returned is the new date object + * + * @param string|integer|array|Zend_Date $month Month to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setMonth($month, $locale = null) + { + return $this->_month('set', $month, $locale); + } + + + /** + * Adds months to the existing date object. + * The month can be a number or a string. Adding months lower then 0 and greater then 12 + * will result in adding or subtracting the relevant year. (12 months equal one year) + * If a localized monthname is given it will be parsed with the default locale or the optional + * set locale. + * Returned is the new date object + * + * @param string|integer|array|Zend_Date $month Month to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addMonth($month, $locale = null) + { + return $this->_month('add', $month, $locale); + } + + + /** + * Subtracts months from the existing date object. + * The month can be a number or a string. Subtracting months lower then 0 and greater then 12 + * will result in adding or subtracting the relevant year. (12 months equal one year) + * If a localized monthname is given it will be parsed with the default locale or the optional + * set locale. + * Returned is the new date object + * + * @param string|integer|array|Zend_Date $month Month to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subMonth($month, $locale = null) + { + return $this->_month('sub', $month, $locale); + } + + + /** + * Compares the month with the existing date object, ignoring other date parts. + * For example: 10.03.2000 -> 15.03.1950 -> true + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $month Month to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareMonth($month, $locale = null) + { + return $this->_month('cmp', $month, $locale); + } + + + /** + * Returns the day as new date object + * Example: 20.May.1986 -> 20.Jan.1970 00:00:00 + * + * @param Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getDay($locale = null) + { + return $this->copyPart(self::DAY_SHORT, $locale); + } + + + /** + * Returns the calculated day + * + * @param string $calc Type of calculation to make + * @param Zend_Date $day Day to calculate, when null the actual day is calculated + * @param Zend_Locale $locale Locale for parsing input + * @return Zend_Date|integer + */ + private function _day($calc, $day, $locale) + { + if ($day === null) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('parameter $day must be set, null is not allowed'); + } + + if ($locale === null) { + $locale = $this->getLocale(); + } + + if ($day instanceof Zend_Date) { + $day = $day->toString(self::DAY_SHORT, 'iso', $locale); + } + + if (is_numeric($day)) { + $type = self::DAY_SHORT; + } else if (is_array($day)) { + if (isset($day['day']) === true) { + $day = $day['day']; + $type = self::WEEKDAY; + } else { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no day given in array"); + } + } else { + switch (iconv_strlen($day, 'UTF-8')) { + case 1 : + $type = self::WEEKDAY_NARROW; + break; + case 2: + $type = self::WEEKDAY_NAME; + break; + case 3: + $type = self::WEEKDAY_SHORT; + break; + default: + $type = self::WEEKDAY; + break; + } + } + $return = $this->_calcdetail($calc, $day, $type, $locale); + if ($calc != 'cmp') { + return $this; + } + return $return; + } + + + /** + * Sets a new day + * The day can be a number or a string. Setting days lower then 0 or greater than the number of this months days + * will result in adding or subtracting the relevant month. + * If a localized dayname is given it will be parsed with the default locale or the optional + * set locale. + * Returned is the new date object + * Example: setDay('Montag', 'de_AT'); will set the monday of this week as day. + * + * @param string|integer|array|Zend_Date $month Day to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setDay($day, $locale = null) + { + return $this->_day('set', $day, $locale); + } + + + /** + * Adds days to the existing date object. + * The day can be a number or a string. Adding days lower then 0 or greater than the number of this months days + * will result in adding or subtracting the relevant month. + * If a localized dayname is given it will be parsed with the default locale or the optional + * set locale. + * + * @param string|integer|array|Zend_Date $month Day to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addDay($day, $locale = null) + { + return $this->_day('add', $day, $locale); + } + + + /** + * Subtracts days from the existing date object. + * The day can be a number or a string. Subtracting days lower then 0 or greater than the number of this months days + * will result in adding or subtracting the relevant month. + * If a localized dayname is given it will be parsed with the default locale or the optional + * set locale. + * + * @param string|integer|array|Zend_Date $month Day to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subDay($day, $locale = null) + { + return $this->_day('sub', $day, $locale); + } + + + /** + * Compares the day with the existing date object, ignoring other date parts. + * For example: 'Monday', 'en' -> 08.Jan.2007 -> 0 + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $day Day to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareDay($day, $locale = null) + { + return $this->_day('cmp', $day, $locale); + } + + + /** + * Returns the weekday as new date object + * Weekday is always from 1-7 + * Example: 09-Jan-2007 -> 2 = Tuesday -> 02-Jan-1970 (when 02.01.1970 is also Tuesday) + * + * @param Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getWeekday($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'l'; + } else { + $format = self::WEEKDAY; + } + + return $this->copyPart($format, $locale); + } + + + /** + * Returns the calculated weekday + * + * @param string $calc Type of calculation to make + * @param Zend_Date $weekday Weekday to calculate, when null the actual weekday is calculated + * @param Zend_Locale $locale Locale for parsing input + * @return Zend_Date|integer + * @throws Zend_Date_Exception + */ + private function _weekday($calc, $weekday, $locale) + { + if ($weekday === null) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('parameter $weekday must be set, null is not allowed'); + } + + if ($locale === null) { + $locale = $this->getLocale(); + } + + if ($weekday instanceof Zend_Date) { + $weekday = $weekday->toString(self::WEEKDAY_8601, 'iso', $locale); + } + + if (is_numeric($weekday)) { + $type = self::WEEKDAY_8601; + } else if (is_array($weekday)) { + if (isset($weekday['weekday']) === true) { + $weekday = $weekday['weekday']; + $type = self::WEEKDAY; + } else { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no weekday given in array"); + } + } else { + switch(iconv_strlen($weekday, 'UTF-8')) { + case 1: + $type = self::WEEKDAY_NARROW; + break; + case 2: + $type = self::WEEKDAY_NAME; + break; + case 3: + $type = self::WEEKDAY_SHORT; + break; + default: + $type = self::WEEKDAY; + break; + } + } + $return = $this->_calcdetail($calc, $weekday, $type, $locale); + if ($calc != 'cmp') { + return $this; + } + return $return; + } + + + /** + * Sets a new weekday + * The weekday can be a number or a string. If a localized weekday name is given, + * then it will be parsed as a date in $locale (defaults to the same locale as $this). + * Returned is the new date object. + * Example: setWeekday(3); will set the wednesday of this week as day. + * + * @param string|integer|array|Zend_Date $month Weekday to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setWeekday($weekday, $locale = null) + { + return $this->_weekday('set', $weekday, $locale); + } + + + /** + * Adds weekdays to the existing date object. + * The weekday can be a number or a string. + * If a localized dayname is given it will be parsed with the default locale or the optional + * set locale. + * Returned is the new date object + * Example: addWeekday(3); will add the difference of days from the begining of the month until + * wednesday. + * + * @param string|integer|array|Zend_Date $month Weekday to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addWeekday($weekday, $locale = null) + { + return $this->_weekday('add', $weekday, $locale); + } + + + /** + * Subtracts weekdays from the existing date object. + * The weekday can be a number or a string. + * If a localized dayname is given it will be parsed with the default locale or the optional + * set locale. + * Returned is the new date object + * Example: subWeekday(3); will subtract the difference of days from the begining of the month until + * wednesday. + * + * @param string|integer|array|Zend_Date $month Weekday to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subWeekday($weekday, $locale = null) + { + return $this->_weekday('sub', $weekday, $locale); + } + + + /** + * Compares the weekday with the existing date object, ignoring other date parts. + * For example: 'Monday', 'en' -> 08.Jan.2007 -> 0 + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $weekday Weekday to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareWeekday($weekday, $locale = null) + { + return $this->_weekday('cmp', $weekday, $locale); + } + + + /** + * Returns the day of year as new date object + * Example: 02.Feb.1986 10:00:00 -> 02.Feb.1970 00:00:00 + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getDayOfYear($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'D'; + } else { + $format = self::DAY_OF_YEAR; + } + + return $this->copyPart($format, $locale); + } + + + /** + * Sets a new day of year + * The day of year is always a number. + * Returned is the new date object + * Example: 04.May.2004 -> setDayOfYear(10) -> 10.Jan.2004 + * + * @param string|integer|array|Zend_Date $day Day of Year to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setDayOfYear($day, $locale = null) + { + return $this->_calcvalue('set', $day, 'day of year', self::DAY_OF_YEAR, $locale); + } + + + /** + * Adds a day of year to the existing date object. + * The day of year is always a number. + * Returned is the new date object + * Example: addDayOfYear(10); will add 10 days to the existing date object. + * + * @param string|integer|array|Zend_Date $day Day of Year to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addDayOfYear($day, $locale = null) + { + return $this->_calcvalue('add', $day, 'day of year', self::DAY_OF_YEAR, $locale); + } + + + /** + * Subtracts a day of year from the existing date object. + * The day of year is always a number. + * Returned is the new date object + * Example: subDayOfYear(10); will subtract 10 days from the existing date object. + * + * @param string|integer|array|Zend_Date $day Day of Year to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subDayOfYear($day, $locale = null) + { + return $this->_calcvalue('sub', $day, 'day of year', self::DAY_OF_YEAR, $locale); + } + + + /** + * Compares the day of year with the existing date object. + * For example: compareDayOfYear(33) -> 02.Feb.2007 -> 0 + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $day Day of Year to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareDayOfYear($day, $locale = null) + { + return $this->_calcvalue('cmp', $day, 'day of year', self::DAY_OF_YEAR, $locale); + } + + + /** + * Returns the hour as new date object + * Example: 02.Feb.1986 10:30:25 -> 01.Jan.1970 10:00:00 + * + * @param Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getHour($locale = null) + { + return $this->copyPart(self::HOUR, $locale); + } + + + /** + * Sets a new hour + * The hour is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> setHour(7); -> 04.May.1993 07:07:25 + * + * @param string|integer|array|Zend_Date $hour Hour to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setHour($hour, $locale = null) + { + return $this->_calcvalue('set', $hour, 'hour', self::HOUR_SHORT, $locale); + } + + + /** + * Adds hours to the existing date object. + * The hour is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> addHour(12); -> 05.May.1993 01:07:25 + * + * @param string|integer|array|Zend_Date $hour Hour to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addHour($hour, $locale = null) + { + return $this->_calcvalue('add', $hour, 'hour', self::HOUR_SHORT, $locale); + } + + + /** + * Subtracts hours from the existing date object. + * The hour is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> subHour(6); -> 05.May.1993 07:07:25 + * + * @param string|integer|array|Zend_Date $hour Hour to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subHour($hour, $locale = null) + { + return $this->_calcvalue('sub', $hour, 'hour', self::HOUR_SHORT, $locale); + } + + + /** + * Compares the hour with the existing date object. + * For example: 10:30:25 -> compareHour(10) -> 0 + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $hour Hour to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareHour($hour, $locale = null) + { + return $this->_calcvalue('cmp', $hour, 'hour', self::HOUR_SHORT, $locale); + } + + + /** + * Returns the minute as new date object + * Example: 02.Feb.1986 10:30:25 -> 01.Jan.1970 00:30:00 + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getMinute($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'i'; + } else { + $format = self::MINUTE; + } + + return $this->copyPart($format, $locale); + } + + + /** + * Sets a new minute + * The minute is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> setMinute(29); -> 04.May.1993 13:29:25 + * + * @param string|integer|array|Zend_Date $minute Minute to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setMinute($minute, $locale = null) + { + return $this->_calcvalue('set', $minute, 'minute', self::MINUTE_SHORT, $locale); + } + + + /** + * Adds minutes to the existing date object. + * The minute is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> addMinute(65); -> 04.May.1993 13:12:25 + * + * @param string|integer|array|Zend_Date $minute Minute to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addMinute($minute, $locale = null) + { + return $this->_calcvalue('add', $minute, 'minute', self::MINUTE_SHORT, $locale); + } + + + /** + * Subtracts minutes from the existing date object. + * The minute is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> subMinute(9); -> 04.May.1993 12:58:25 + * + * @param string|integer|array|Zend_Date $minute Minute to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subMinute($minute, $locale = null) + { + return $this->_calcvalue('sub', $minute, 'minute', self::MINUTE_SHORT, $locale); + } + + + /** + * Compares the minute with the existing date object. + * For example: 10:30:25 -> compareMinute(30) -> 0 + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $minute Hour to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareMinute($minute, $locale = null) + { + return $this->_calcvalue('cmp', $minute, 'minute', self::MINUTE_SHORT, $locale); + } + + + /** + * Returns the second as new date object + * Example: 02.Feb.1986 10:30:25 -> 01.Jan.1970 00:00:25 + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getSecond($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 's'; + } else { + $format = self::SECOND; + } + + return $this->copyPart($format, $locale); + } + + + /** + * Sets new seconds to the existing date object. + * The second is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> setSecond(100); -> 04.May.1993 13:08:40 + * + * @param string|integer|array|Zend_Date $second Second to set + * @param string|Zend_Locale $locale (Optional) Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setSecond($second, $locale = null) + { + return $this->_calcvalue('set', $second, 'second', self::SECOND_SHORT, $locale); + } + + + /** + * Adds seconds to the existing date object. + * The second is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> addSecond(65); -> 04.May.1993 13:08:30 + * + * @param string|integer|array|Zend_Date $second Second to add + * @param string|Zend_Locale $locale (Optional) Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addSecond($second, $locale = null) + { + return $this->_calcvalue('add', $second, 'second', self::SECOND_SHORT, $locale); + } + + + /** + * Subtracts seconds from the existing date object. + * The second is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> subSecond(10); -> 04.May.1993 13:07:15 + * + * @param string|integer|array|Zend_Date $second Second to sub + * @param string|Zend_Locale $locale (Optional) Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subSecond($second, $locale = null) + { + return $this->_calcvalue('sub', $second, 'second', self::SECOND_SHORT, $locale); + } + + + /** + * Compares the second with the existing date object. + * For example: 10:30:25 -> compareSecond(25) -> 0 + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $second Second to compare + * @param string|Zend_Locale $locale (Optional) Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareSecond($second, $locale = null) + { + return $this->_calcvalue('cmp', $second, 'second', self::SECOND_SHORT, $locale); + } + + + /** + * Returns the precision for fractional seconds + * + * @return integer + */ + public function getFractionalPrecision() + { + return $this->_precision; + } + + + /** + * Sets a new precision for fractional seconds + * + * @param integer $precision Precision for the fractional datepart 3 = milliseconds + * @throws Zend_Date_Exception + * @return Zend_Date Provides fluid interface + */ + public function setFractionalPrecision($precision) + { + if (!intval($precision) or ($precision < 0) or ($precision > 9)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("precision ($precision) must be a positive integer less than 10", 0, null, $precision); + } + + $this->_precision = (int) $precision; + if ($this->_precision < strlen($this->_fractional)) { + $this->_fractional = substr($this->_fractional, 0, $this->_precision); + } else { + $this->_fractional = str_pad($this->_fractional, $this->_precision, '0', STR_PAD_RIGHT); + } + + return $this; + } + + + /** + * Returns the milliseconds of the date object + * + * @return string + */ + public function getMilliSecond() + { + return $this->_fractional; + } + + + /** + * Sets new milliseconds for the date object + * Example: setMilliSecond(550, 2) -> equals +5 Sec +50 MilliSec + * + * @param integer|Zend_Date $milli (Optional) Millisecond to set, when null the actual millisecond is set + * @param integer $precision (Optional) Fraction precision of the given milliseconds + * @return Zend_Date Provides fluid interface + */ + public function setMilliSecond($milli = null, $precision = null) + { + if ($milli === null) { + list($milli, $time) = explode(" ", microtime()); + $milli = intval($milli); + $precision = 6; + } else if (!is_numeric($milli)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid milli second ($milli) operand", 0, null, $milli); + } + + if ($precision === null) { + $precision = $this->_precision; + } + + if (!is_int($precision) || $precision < 1 || $precision > 9) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("precision ($precision) must be a positive integer less than 10", 0, null, $precision); + } + + $this->_fractional = 0; + $this->addMilliSecond($milli, $precision); + return $this; + } + + + /** + * Adds milliseconds to the date object + * + * @param integer|Zend_Date $milli (Optional) Millisecond to add, when null the actual millisecond is added + * @param integer $precision (Optional) Fractional precision for the given milliseconds + * @return Zend_Date Provides fluid interface + */ + public function addMilliSecond($milli = null, $precision = null) + { + if ($milli === null) { + list($milli, $time) = explode(" ", microtime()); + $milli = intval($milli); + } else if (!is_numeric($milli)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid milli second ($milli) operand", 0, null, $milli); + } + + if ($precision === null) { + $precision = strlen($milli); + if ($milli < 0) { + --$precision; + } + } + + if (!is_int($precision) || $precision < 1 || $precision > 9) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("precision ($precision) must be a positive integer less than 10", 0, null, $precision); + } + + $this->_fractional += $milli; + + // Add/sub milliseconds + add/sub seconds + $max = pow(10, $this->_precision); + // Milli includes seconds + if ($this->_fractional >= $max) { + while ($this->_fractional >= $max) { + $this->addSecond(1); + $this->_fractional -= $max; + } + } + + if ($this->_fractional < 0) { + while ($this->_fractional < 0) { + $this->subSecond(1); + $this->_fractional += $max; + } + } + + if ($this->_precision > strlen($this->_fractional)) { + $this->_fractional = str_pad($this->_fractional, $this->_precision, '0', STR_PAD_LEFT); + } + + return $this; + } + + + /** + * Subtracts a millisecond + * + * @param integer|Zend_Date $milli (Optional) Millisecond to sub, when null the actual millisecond is subtracted + * @param integer $precision (Optional) Fractional precision for the given milliseconds + * @return Zend_Date Provides fluid interface + */ + public function subMilliSecond($milli = null, $precision = null) + { + $this->addMilliSecond(0 - $milli, $precision); + return $this; + } + + /** + * Compares only the millisecond part, returning the difference + * + * @param integer|Zend_Date $milli OPTIONAL Millisecond to compare, when null the actual millisecond is compared + * @param integer $precision OPTIONAL Fractional precision for the given milliseconds + * @throws Zend_Date_Exception On invalid input + * @return integer 0 = equal, 1 = later, -1 = earlier + */ + public function compareMilliSecond($milli = null, $precision = null) + { + if ($milli === null) { + list($milli, $time) = explode(" ", microtime()); + $milli = intval($milli); + } else if (is_numeric($milli) === false) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid milli second ($milli) operand", 0, null, $milli); + } + + if ($precision === null) { + $precision = strlen($milli); + } else if (!is_int($precision) || $precision < 1 || $precision > 9) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("precision ($precision) must be a positive integer less than 10", 0, null, $precision); + } + + if ($precision === 0) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('precision is 0'); + } + + if ($precision != $this->_precision) { + if ($precision > $this->_precision) { + $diff = $precision - $this->_precision; + $milli = (int) ($milli / (10 * $diff)); + } else { + $diff = $this->_precision - $precision; + $milli = (int) ($milli * (10 * $diff)); + } + } + + $comp = $this->_fractional - $milli; + if ($comp < 0) { + return -1; + } else if ($comp > 0) { + return 1; + } + return 0; + } + + /** + * Returns the week as new date object using monday as begining of the week + * Example: 12.Jan.2007 -> 08.Jan.1970 00:00:00 + * + * @param Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getWeek($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'W'; + } else { + $format = self::WEEK; + } + + return $this->copyPart($format, $locale); + } + + /** + * Sets a new week. The week is always a number. The day of week is not changed. + * Returned is the new date object + * Example: 09.Jan.2007 13:07:25 -> setWeek(1); -> 02.Jan.2007 13:07:25 + * + * @param string|integer|array|Zend_Date $week Week to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setWeek($week, $locale = null) + { + return $this->_calcvalue('set', $week, 'week', self::WEEK, $locale); + } + + /** + * Adds a week. The week is always a number. The day of week is not changed. + * Returned is the new date object + * Example: 09.Jan.2007 13:07:25 -> addWeek(1); -> 16.Jan.2007 13:07:25 + * + * @param string|integer|array|Zend_Date $week Week to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addWeek($week, $locale = null) + { + return $this->_calcvalue('add', $week, 'week', self::WEEK, $locale); + } + + /** + * Subtracts a week. The week is always a number. The day of week is not changed. + * Returned is the new date object + * Example: 09.Jan.2007 13:07:25 -> subWeek(1); -> 02.Jan.2007 13:07:25 + * + * @param string|integer|array|Zend_Date $week Week to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subWeek($week, $locale = null) + { + return $this->_calcvalue('sub', $week, 'week', self::WEEK, $locale); + } + + /** + * Compares only the week part, returning the difference + * Returned is the new date object + * Returns if equal, earlier or later + * Example: 09.Jan.2007 13:07:25 -> compareWeek(2); -> 0 + * + * @param string|integer|array|Zend_Date $week Week to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + */ + public function compareWeek($week, $locale = null) + { + return $this->_calcvalue('cmp', $week, 'week', self::WEEK, $locale); + } + + /** + * Sets a new standard locale for the date object. + * This locale will be used for all functions + * Returned is the really set locale. + * Example: 'de_XX' will be set to 'de' because 'de_XX' does not exist + * 'xx_YY' will be set to 'root' because 'xx' does not exist + * + * @param string|Zend_Locale $locale (Optional) Locale for parsing input + * @throws Zend_Date_Exception When the given locale does not exist + * @return Zend_Date Provides fluent interface + */ + public function setLocale($locale = null) + { + try { + $this->_locale = Zend_Locale::findLocale($locale); + } catch (Zend_Locale_Exception $e) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e); + } + + return $this; + } + + /** + * Returns the actual set locale + * + * @return string + */ + public function getLocale() + { + return $this->_locale; + } + + /** + * Checks if the given date is a real date or datepart. + * Returns false if a expected datepart is missing or a datepart exceeds its possible border. + * But the check will only be done for the expected dateparts which are given by format. + * If no format is given the standard dateformat for the actual locale is used. + * f.e. 30.February.2007 will return false if format is 'dd.MMMM.YYYY' + * + * @param string|array|Zend_Date $date Date to parse for correctness + * @param string $format (Optional) Format for parsing the date string + * @param string|Zend_Locale $locale (Optional) Locale for parsing date parts + * @return boolean True when all date parts are correct + */ + public static function isDate($date, $format = null, $locale = null) + { + if (!is_string($date) && !is_numeric($date) && !($date instanceof Zend_Date) && + !is_array($date)) { + return false; + } + + if (($format !== null) && ($format != 'ee') && ($format != 'ss') && ($format != 'GG') && ($format != 'MM') && ($format != 'EE') && ($format != 'TT') + && (Zend_Locale::isLocale($format, null, false))) { + $locale = $format; + $format = null; + } + + $locale = Zend_Locale::findLocale($locale); + + if ($format === null) { + $format = Zend_Locale_Format::getDateFormat($locale); + } else if ((self::$_options['format_type'] == 'php') && !defined($format)) { + $format = Zend_Locale_Format::convertPhpToIsoFormat($format); + } + + $format = self::_getLocalizedToken($format, $locale); + if (!is_array($date)) { + try { + $parsed = Zend_Locale_Format::getDate($date, array('locale' => $locale, + 'date_format' => $format, 'format_type' => 'iso', + 'fix_date' => false)); + } catch (Zend_Locale_Exception $e) { + // Date can not be parsed + return false; + } + } else { + $parsed = $date; + } + + if (((strpos($format, 'Y') !== false) or (strpos($format, 'y') !== false)) and + (!isset($parsed['year']))) { + // Year expected but not found + return false; + } + + if ((strpos($format, 'M') !== false) and (!isset($parsed['month']))) { + // Month expected but not found + return false; + } + + if ((strpos($format, 'd') !== false) and (!isset($parsed['day']))) { + // Day expected but not found + return false; + } + + if (((strpos($format, 'H') !== false) or (strpos($format, 'h') !== false)) and + (!isset($parsed['hour']))) { + // Hour expected but not found + return false; + } + + if ((strpos($format, 'm') !== false) and (!isset($parsed['minute']))) { + // Minute expected but not found + return false; + } + + if ((strpos($format, 's') !== false) and (!isset($parsed['second']))) { + // Second expected but not found + return false; + } + + // Set not given dateparts + if (isset($parsed['hour']) === false) { + $parsed['hour'] = 12; + } + + if (isset($parsed['minute']) === false) { + $parsed['minute'] = 0; + } + + if (isset($parsed['second']) === false) { + $parsed['second'] = 0; + } + + if (isset($parsed['month']) === false) { + $parsed['month'] = 1; + } + + if (isset($parsed['day']) === false) { + $parsed['day'] = 1; + } + + if (isset($parsed['year']) === false) { + $parsed['year'] = 1970; + } + + if (self::isYearLeapYear($parsed['year'])) { + $parsed['year'] = 1972; + } else { + $parsed['year'] = 1971; + } + + $date = new self($parsed, null, $locale); + $timestamp = $date->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], + $parsed['month'], $parsed['day'], $parsed['year']); + + if ($parsed['year'] != $date->date('Y', $timestamp)) { + // Given year differs from parsed year + return false; + } + + if ($parsed['month'] != $date->date('n', $timestamp)) { + // Given month differs from parsed month + return false; + } + + if ($parsed['day'] != $date->date('j', $timestamp)) { + // Given day differs from parsed day + return false; + } + + if ($parsed['hour'] != $date->date('G', $timestamp)) { + // Given hour differs from parsed hour + return false; + } + + if ($parsed['minute'] != $date->date('i', $timestamp)) { + // Given minute differs from parsed minute + return false; + } + + if ($parsed['second'] != $date->date('s', $timestamp)) { + // Given second differs from parsed second + return false; + } + + return true; + } + + /** + * Returns the ISO Token for all localized constants + * + * @param string $token Token to normalize + * @param string $locale Locale to search + * @return string + */ + protected static function _getLocalizedToken($token, $locale) + { + switch($token) { + case self::ISO_8601 : + return "yyyy-MM-ddThh:mm:ss"; + break; + case self::RFC_2822 : + return "EEE, dd MMM yyyy HH:mm:ss"; + break; + case self::DATES : + return Zend_Locale_Data::getContent($locale, 'date'); + break; + case self::DATE_FULL : + return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'full')); + break; + case self::DATE_LONG : + return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'long')); + break; + case self::DATE_MEDIUM : + return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'medium')); + break; + case self::DATE_SHORT : + return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'short')); + break; + case self::TIMES : + return Zend_Locale_Data::getContent($locale, 'time'); + break; + case self::TIME_FULL : + return Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'full')); + break; + case self::TIME_LONG : + return Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'long')); + break; + case self::TIME_MEDIUM : + return Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'medium')); + break; + case self::TIME_SHORT : + return Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'short')); + break; + case self::DATETIME : + return Zend_Locale_Data::getContent($locale, 'datetime'); + break; + case self::DATETIME_FULL : + return Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'full')); + break; + case self::DATETIME_LONG : + return Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'long')); + break; + case self::DATETIME_MEDIUM : + return Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'medium')); + break; + case self::DATETIME_SHORT : + return Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'short')); + break; + case self::ATOM : + case self::RFC_3339 : + case self::W3C : + return "yyyy-MM-DD HH:mm:ss"; + break; + case self::COOKIE : + case self::RFC_850 : + return "EEEE, dd-MM-yyyy HH:mm:ss"; + break; + case self::RFC_822 : + case self::RFC_1036 : + case self::RFC_1123 : + case self::RSS : + return "EEE, dd MM yyyy HH:mm:ss"; + break; + } + + return $token; + } +} diff --git a/library/Zend/Date/Cities.php b/library/Zend/Date/Cities.php new file mode 100644 index 00000000..bf71bceb --- /dev/null +++ b/library/Zend/Date/Cities.php @@ -0,0 +1,322 @@ + array('latitude' => 5.3411111, 'longitude' => -4.0280556), + 'Abu Dhabi' => array('latitude' => 24.4666667, 'longitude' => 54.3666667), + 'Abuja' => array('latitude' => 9.1758333, 'longitude' => 7.1808333), + 'Accra' => array('latitude' => 5.55, 'longitude' => -0.2166667), + 'Adamstown' => array('latitude' => -25.0666667, 'longitude' => -130.0833333), + 'Addis Ababa' => array('latitude' => 9.0333333, 'longitude' => 38.7), + 'Adelaide' => array('latitude' => -34.9333333, 'longitude' => 138.6), + 'Algiers' => array('latitude' => 36.7630556, 'longitude' => 3.0505556), + 'Alofi' => array('latitude' => -19.0166667, 'longitude' => -169.9166667), + 'Amman' => array('latitude' => 31.95, 'longitude' => 35.9333333), + 'Amsterdam' => array('latitude' => 52.35, 'longitude' => 4.9166667), + 'Andorra la Vella' => array('latitude' => 42.5, 'longitude' => 1.5166667), + 'Ankara' => array('latitude' => 39.9272222, 'longitude' => 32.8644444), + 'Antananarivo' => array('latitude' => -18.9166667, 'longitude' => 47.5166667), + 'Apia' => array('latitude' => -13.8333333, 'longitude' => -171.7333333), + 'Ashgabat' => array('latitude' => 37.95, 'longitude' => 58.3833333), + 'Asmara' => array('latitude' => 15.3333333, 'longitude' => 38.9333333), + 'Astana' => array('latitude' => 51.1811111, 'longitude' => 71.4277778), + 'Asunción' => array('latitude' => -25.2666667, 'longitude' => -57.6666667), + 'Athens' => array('latitude' => 37.9833333, 'longitude' => 23.7333333), + 'Auckland' => array('latitude' => -36.8666667, 'longitude' => 174.7666667), + 'Avarua' => array('latitude' => -21.2, 'longitude' => -159.7666667), + 'Baghdad' => array('latitude' => 33.3386111, 'longitude' => 44.3938889), + 'Baku' => array('latitude' => 40.3952778, 'longitude' => 49.8822222), + 'Bamako' => array('latitude' => 12.65, 'longitude' => -8), + 'Bandar Seri Begawan' => array('latitude' => 4.8833333, 'longitude' => 114.9333333), + 'Bankok' => array('latitude' => 13.5833333, 'longitude' => 100.2166667), + 'Bangui' => array('latitude' => 4.3666667, 'longitude' => 18.5833333), + 'Banjul' => array('latitude' => 13.4530556, 'longitude' => -16.5775), + 'Basel' => array('latitude' => 47.5666667, 'longitude' => 7.6), + 'Basseterre' => array('latitude' => 17.3, 'longitude' => -62.7166667), + 'Beijing' => array('latitude' => 39.9288889, 'longitude' => 116.3883333), + 'Beirut' => array('latitude' => 33.8719444, 'longitude' => 35.5097222), + 'Belgrade' => array('latitude' => 44.8186111, 'longitude' => 20.4680556), + 'Belmopan' => array('latitude' => 17.25, 'longitude' => -88.7666667), + 'Berlin' => array('latitude' => 52.5166667, 'longitude' => 13.4), + 'Bern' => array('latitude' => 46.9166667, 'longitude' => 7.4666667), + 'Bishkek' => array('latitude' => 42.8730556, 'longitude' => 74.6002778), + 'Bissau' => array('latitude' => 11.85, 'longitude' => -15.5833333), + 'Bloemfontein' => array('latitude' => -29.1333333, 'longitude' => 26.2), + 'Bogotá' => array('latitude' => 4.6, 'longitude' => -74.0833333), + 'Brasilia' => array('latitude' => -15.7833333, 'longitude' => -47.9166667), + 'Bratislava' => array('latitude' => 48.15, 'longitude' => 17.1166667), + 'Brazzaville' => array('latitude' => -4.2591667, 'longitude' => 15.2847222), + 'Bridgetown' => array('latitude' => 13.1, 'longitude' => -59.6166667), + 'Brisbane' => array('latitude' => -27.5, 'longitude' => 153.0166667), + 'Brussels' => array('latitude' => 50.8333333, 'longitude' => 4.3333333), + 'Bucharest' => array('latitude' => 44.4333333, 'longitude' => 26.1), + 'Budapest' => array('latitude' => 47.5, 'longitude' => 19.0833333), + 'Buenos Aires' => array('latitude' => -34.5875, 'longitude' => -58.6725), + 'Bujumbura' => array('latitude' => -3.3761111, 'longitude' => 29.36), + 'Cairo' => array('latitude' => 30.05, 'longitude' => 31.25), + 'Calgary' => array('latitude' => 51.0833333, 'longitude' => -114.0833333), + 'Canberra' => array('latitude' => -35.2833333, 'longitude' => 149.2166667), + 'Cape Town' => array('latitude' => -33.9166667, 'longitude' => 18.4166667), + 'Caracas' => array('latitude' => 10.5, 'longitude' => -66.9166667), + 'Castries' => array('latitude' => 14, 'longitude' => -61), + 'Charlotte Amalie' => array('latitude' => 18.34389, 'longitude' => -64.93111), + 'Chicago' => array('latitude' => 41.85, 'longitude' => -87.65), + 'Chisinau' => array('latitude' => 47.055556, 'longitude' => 28.8575), + 'Cockburn Town' => array('latitude' => 21.4666667, 'longitude' => -71.1333333), + 'Colombo' => array('latitude' => 6.9319444, 'longitude' => 79.8477778), + 'Conakry' => array('latitude' => 9.5091667, 'longitude' => -13.7122222), + 'Copenhagen' => array('latitude' => 55.6666667, 'longitude' => 12.5833333), + 'Cotonou' => array('latitude' => 6.35, 'longitude' => 2.4333333), + 'Dakar' => array('latitude' => 14.6708333, 'longitude' => -17.4380556), + 'Damascus' => array('latitude' => 33.5, 'longitude' => 36.3), + 'Dar es Salaam' => array('latitude' => -6.8, 'longitude' => 39.2833333), + 'Dhaka' => array('latitude' => 23.7230556, 'longitude' => 90.4086111), + 'Dili' => array('latitude' => -8.5586111, 'longitude' => 125.5736111), + 'Djibouti' => array('latitude' => 11.595, 'longitude' => 43.1480556), + 'Dodoma' => array('latitude' => -6.1833333, 'longitude' => 35.75), + 'Doha' => array('latitude' => 25.2866667, 'longitude' => 51.5333333), + 'Dubai' => array('latitude' => 25.2522222, 'longitude' => 55.28), + 'Dublin' => array('latitude' => 53.3330556, 'longitude' => -6.2488889), + 'Dushanbe' => array('latitude' => 38.56, 'longitude' => 68.7738889 ), + 'Fagatogo' => array('latitude' => -14.2825, 'longitude' => -170.69), + 'Fongafale' => array('latitude' => -8.5166667, 'longitude' => 179.2166667), + 'Freetown' => array('latitude' => 8.49, 'longitude' => -13.2341667), + 'Gaborone' => array('latitude' => -24.6463889, 'longitude' => 25.9119444), + 'Geneva' => array('latitude' => 46.2, 'longitude' => 6.1666667), + 'George Town' => array('latitude' => 19.3, 'longitude' => -81.3833333), + 'Georgetown' => array('latitude' => 6.8, 'longitude' => -58.1666667), + 'Gibraltar' => array('latitude' => 36.1333333, 'longitude' => -5.35), + 'Glasgow' => array('latitude' => 55.8333333, 'longitude' => -4.25), + 'Guatemala la Nueva' => array('latitude' => 14.6211111, 'longitude' => -90.5269444), + 'Hagatna' => array('latitude' => 13.47417, 'longitude' => 144.74778), + 'The Hague' => array('latitude' => 52.0833333, 'longitude' => 4.3), + 'Hamilton' => array('latitude' => 32.2941667, 'longitude' => -64.7838889), + 'Hanoi' => array('latitude' => 21.0333333, 'longitude' => 105.85), + 'Harare' => array('latitude' => -17.8177778, 'longitude' => 31.0447222), + 'Havana' => array('latitude' => 23.1319444, 'longitude' => -82.3641667), + 'Helsinki' => array('latitude' => 60.1755556, 'longitude' => 24.9341667), + 'Honiara' => array('latitude' => -9.4333333, 'longitude' => 159.95), + 'Islamabad' => array('latitude' => 30.8486111, 'longitude' => 72.4944444), + 'Istanbul' => array('latitude' => 41.0186111, 'longitude' => 28.9647222), + 'Jakarta' => array('latitude' => -6.1744444, 'longitude' => 106.8294444), + 'Jamestown' => array('latitude' => -15.9333333, 'longitude' => -5.7166667), + 'Jerusalem' => array('latitude' => 31.7666667, 'longitude' => 35.2333333), + 'Johannesburg' => array('latitude' => -26.2, 'longitude' => 28.0833333), + 'Kabul' => array('latitude' => 34.5166667, 'longitude' => 69.1833333), + 'Kampala' => array('latitude' => 0.3155556, 'longitude' => 32.5655556), + 'Kathmandu' => array('latitude' => 27.7166667, 'longitude' => 85.3166667), + 'Khartoum' => array('latitude' => 15.5880556, 'longitude' => 32.5341667), + 'Kigali' => array('latitude' => -1.9536111, 'longitude' => 30.0605556), + 'Kingston' => array('latitude' => -29.05, 'longitude' => 167.95), + 'Kingstown' => array('latitude' => 13.1333333, 'longitude' => -61.2166667), + 'Kinshasa' => array('latitude' => -4.3, 'longitude' => 15.3), + 'Kolkata' => array('latitude' => 22.5697222, 'longitude' => 88.3697222), + 'Kuala Lumpur' => array('latitude' => 3.1666667, 'longitude' => 101.7), + 'Kuwait City' => array('latitude' => 29.3697222, 'longitude' => 47.9783333), + 'Kiev' => array('latitude' => 50.4333333, 'longitude' => 30.5166667), + 'La Paz' => array('latitude' => -16.5, 'longitude' => -68.15), + 'Libreville' => array('latitude' => 0.3833333, 'longitude' => 9.45), + 'Lilongwe' => array('latitude' => -13.9833333, 'longitude' => 33.7833333), + 'Lima' => array('latitude' => -12.05, 'longitude' => -77.05), + 'Lisbon' => array('latitude' => 38.7166667, 'longitude' => -9.1333333), + 'Ljubljana' => array('latitude' => 46.0552778, 'longitude' => 14.5144444), + 'Lobamba' => array('latitude' => -26.4666667, 'longitude' => 31.2), + 'Lomé' => array('latitude' => 9.7166667, 'longitude' => 38.3), + 'London' => array('latitude' => 51.5, 'longitude' => -0.1166667), + 'Los Angeles' => array('latitude' => 34.05222, 'longitude' => -118.24278), + 'Luanda' => array('latitude' => -8.8383333, 'longitude' => 13.2344444), + 'Lusaka' => array('latitude' => -15.4166667, 'longitude' => 28.2833333), + 'Luxembourg' => array('latitude' => 49.6116667, 'longitude' => 6.13), + 'Madrid' => array('latitude' => 40.4, 'longitude' => -3.6833333), + 'Majuro' => array('latitude' => 7.1, 'longitude' => 171.3833333), + 'Malabo' => array('latitude' => 3.75, 'longitude' => 8.7833333), + 'Managua' => array('latitude' => 12.1508333, 'longitude' => -86.2683333), + 'Manama' => array('latitude' => 26.2361111, 'longitude' => 50.5830556), + 'Manila' => array('latitude' => 14.6041667, 'longitude' => 120.9822222), + 'Maputo' => array('latitude' => -25.9652778, 'longitude' => 32.5891667), + 'Maseru' => array('latitude' => -29.3166667, 'longitude' => 27.4833333), + 'Mbabane' => array('latitude' => -26.3166667, 'longitude' => 31.1333333), + 'Melbourne' => array('latitude' => -37.8166667, 'longitude' => 144.9666667), + 'Melekeok' => array('latitude' => 7.4933333, 'longitude' => 134.6341667), + 'Mexiko City' => array('latitude' => 19.4341667, 'longitude' => -99.1386111), + 'Minsk' => array('latitude' => 53.9, 'longitude' => 27.5666667), + 'Mogadishu' => array('latitude' => 2.0666667, 'longitude' => 45.3666667), + 'Monaco' => array('latitude' => 43.7333333, 'longitude' => 7.4166667), + 'Monrovia' => array('latitude' => 6.3105556, 'longitude' => -10.8047222), + 'Montevideo' => array('latitude' => -34.8580556, 'longitude' => -56.1708333), + 'Montreal' => array('latitude' => 45.5, 'longitude' => -73.5833333), + 'Moroni' => array('latitude' => -11.7041667, 'longitude' => 43.2402778), + 'Moscow' => array('latitude' => 55.7522222, 'longitude' => 37.6155556), + 'Muscat' => array('latitude' => 23.6133333, 'longitude' => 58.5933333), + 'Nairobi' => array('latitude' => -1.3166667, 'longitude' => 36.8333333), + 'Nassau' => array('latitude' => 25.0833333, 'longitude' => -77.35), + 'N´Djamena' => array('latitude' => 12.1130556, 'longitude' => 15.0491667), + 'New Dehli' => array('latitude' => 28.6, 'longitude' => 77.2), + 'New York' => array('latitude' => 40.71417, 'longitude' => -74.00639), + 'Newcastle' => array('latitude' => -32.9166667, 'longitude' => 151.75), + 'Niamey' => array('latitude' => 13.6666667, 'longitude' => 1.7833333), + 'Nicosia' => array('latitude' => 35.1666667, 'longitude' => 33.3666667), + 'Nouakchott' => array('latitude' => 18.0863889, 'longitude' => -15.9752778), + 'Noumea' => array('latitude' => -22.2666667, 'longitude' => 166.45), + 'Nuku´alofa' => array('latitude' => -21.1333333, 'longitude' => -175.2), + 'Nuuk' => array('latitude' => 64.1833333, 'longitude' => -51.75), + 'Oranjestad' => array('latitude' => 12.5166667, 'longitude' => -70.0333333), + 'Oslo' => array('latitude' => 59.9166667, 'longitude' => 10.75), + 'Ouagadougou' => array('latitude' => 12.3702778, 'longitude' => -1.5247222), + 'Palikir' => array('latitude' => 6.9166667, 'longitude' => 158.15), + 'Panama City' => array('latitude' => 8.9666667, 'longitude' => -79.5333333), + 'Papeete' => array('latitude' => -17.5333333, 'longitude' => -149.5666667), + 'Paramaribo' => array('latitude' => 5.8333333, 'longitude' => -55.1666667), + 'Paris' => array('latitude' => 48.8666667, 'longitude' => 2.3333333), + 'Perth' => array('latitude' => -31.9333333, 'longitude' => 115.8333333), + 'Phnom Penh' => array('latitude' => 11.55, 'longitude' => 104.9166667), + 'Podgorica' => array('latitude' => 43.7752778, 'longitude' => 19.6827778), + 'Port Louis' => array('latitude' => -20.1666667, 'longitude' => 57.5), + 'Port Moresby' => array('latitude' => -9.4647222, 'longitude' => 147.1925), + 'Port-au-Prince' => array('latitude' => 18.5391667, 'longitude' => -72.335), + 'Port of Spain' => array('latitude' => 10.6666667, 'longitude' => -61.5), + 'Porto-Novo' => array('latitude' => 6.4833333, 'longitude' => 2.6166667), + 'Prague' => array('latitude' => 50.0833333, 'longitude' => 14.4666667), + 'Praia' => array('latitude' => 14.9166667, 'longitude' => -23.5166667), + 'Pretoria' => array('latitude' => -25.7069444, 'longitude' => 28.2294444), + 'Pyongyang' => array('latitude' => 39.0194444, 'longitude' => 125.7547222), + 'Quito' => array('latitude' => -0.2166667, 'longitude' => -78.5), + 'Rabat' => array('latitude' => 34.0252778, 'longitude' => -6.8361111), + 'Reykjavik' => array('latitude' => 64.15, 'longitude' => -21.95), + 'Riga' => array('latitude' => 56.95, 'longitude' => 24.1), + 'Rio de Janero' => array('latitude' => -22.9, 'longitude' => -43.2333333), + 'Road Town' => array('latitude' => 18.4166667, 'longitude' => -64.6166667), + 'Rome' => array('latitude' => 41.9, 'longitude' => 12.4833333), + 'Roseau' => array('latitude' => 15.3, 'longitude' => -61.4), + 'Rotterdam' => array('latitude' => 51.9166667, 'longitude' => 4.5), + 'Salvador' => array('latitude' => -12.9833333, 'longitude' => -38.5166667), + 'San José' => array('latitude' => 9.9333333, 'longitude' => -84.0833333), + 'San Juan' => array('latitude' => 18.46833, 'longitude' => -66.10611), + 'San Marino' => array('latitude' => 43.5333333, 'longitude' => 12.9666667), + 'San Salvador' => array('latitude' => 13.7086111, 'longitude' => -89.2030556), + 'Sanaá' => array('latitude' => 15.3547222, 'longitude' => 44.2066667), + 'Santa Cruz' => array('latitude' => -17.8, 'longitude' => -63.1666667), + 'Santiago' => array('latitude' => -33.45, 'longitude' => -70.6666667), + 'Santo Domingo' => array('latitude' => 18.4666667, 'longitude' => -69.9), + 'Sao Paulo' => array('latitude' => -23.5333333, 'longitude' => -46.6166667), + 'Sarajevo' => array('latitude' => 43.85, 'longitude' => 18.3833333), + 'Seoul' => array('latitude' => 37.5663889, 'longitude' => 126.9997222), + 'Shanghai' => array('latitude' => 31.2222222, 'longitude' => 121.4580556), + 'Sydney' => array('latitude' => -33.8833333, 'longitude' => 151.2166667), + 'Singapore' => array('latitude' => 1.2930556, 'longitude' => 103.8558333), + 'Skopje' => array('latitude' => 42, 'longitude' => 21.4333333), + 'Sofia' => array('latitude' => 42.6833333, 'longitude' => 23.3166667), + 'St. George´s' => array('latitude' => 12.05, 'longitude' => -61.75), + 'St. John´s' => array('latitude' => 17.1166667, 'longitude' => -61.85), + 'Stanley' => array('latitude' => -51.7, 'longitude' => -57.85), + 'Stockholm' => array('latitude' => 59.3333333, 'longitude' => 18.05), + 'Suva' => array('latitude' => -18.1333333, 'longitude' => 178.4166667), + 'Taipei' => array('latitude' => 25.0166667, 'longitude' => 121.45), + 'Tallinn' => array('latitude' => 59.4338889, 'longitude' => 24.7280556), + 'Tashkent' => array('latitude' => 41.3166667, 'longitude' => 69.25), + 'Tbilisi' => array('latitude' => 41.725, 'longitude' => 44.7908333), + 'Tegucigalpa' => array('latitude' => 14.1, 'longitude' => -87.2166667), + 'Tehran' => array('latitude' => 35.6719444, 'longitude' => 51.4244444), + 'The Hague' => array('latitude' => 52.0833333, 'longitude' => 4.3), + 'Thimphu' => array('latitude' => 27.4833333, 'longitude' => 89.6), + 'Tirana' => array('latitude' => 41.3275, 'longitude' => 19.8188889), + 'Tiraspol' => array('latitude' => 46.8402778, 'longitude' => 29.6433333), + 'Tokyo' => array('latitude' => 35.685, 'longitude' => 139.7513889), + 'Toronto' => array('latitude' => 43.6666667, 'longitude' => -79.4166667), + 'Tórshavn' => array('latitude' => 62.0166667, 'longitude' => -6.7666667), + 'Tripoli' => array('latitude' => 32.8925, 'longitude' => 13.18), + 'Tunis' => array('latitude' => 36.8027778, 'longitude' => 10.1797222), + 'Ulaanbaatar' => array('latitude' => 47.9166667, 'longitude' => 106.9166667), + 'Vaduz' => array('latitude' => 47.1333333, 'longitude' => 9.5166667), + 'Valletta' => array('latitude' => 35.8997222, 'longitude' => 14.5147222), + 'Valparaiso' => array('latitude' => -33.0477778, 'longitude' => -71.6011111), + 'Vancouver' => array('latitude' => 49.25, 'longitude' => -123.1333333), + 'Vatican City' => array('latitude' => 41.9, 'longitude' => 12.4833333), + 'Victoria' => array('latitude' => -4.6166667, 'longitude' => 55.45), + 'Vienna' => array('latitude' => 48.2, 'longitude' => 16.3666667), + 'Vientaine' => array('latitude' => 17.9666667, 'longitude' => 102.6), + 'Vilnius' => array('latitude' => 54.6833333, 'longitude' => 25.3166667), + 'Warsaw' => array('latitude' => 52.25, 'longitude' => 21), + 'Washington dc' => array('latitude' => 38.895, 'longitude' => -77.03667), + 'Wellington' => array('latitude' => -41.3, 'longitude' => 174.7833333), + 'Willemstad' => array('latitude' => 12.1, 'longitude' => -68.9166667), + 'Windhoek' => array('latitude' => -22.57, 'longitude' => 17.0836111), + 'Yamoussoukro' => array('latitude' => 6.8166667, 'longitude' => -5.2833333), + 'Yaoundé' => array('latitude' => 3.8666667, 'longitude' => 11.5166667), + 'Yerevan' => array('latitude' => 40.1811111, 'longitude' => 44.5136111), + 'Zürich' => array('latitude' => 47.3666667, 'longitude' => 8.55), + 'Zagreb' => array('latitude' => 45.8, 'longitude' => 16) + ); + + /** + * Returns the location from the selected city + * + * @param string $city City to get location for + * @param string $horizon Horizon to use : + * default: effective + * others are civil, nautic, astronomic + * @return array + * @throws Zend_Date_Exception When city is unknown + */ + public static function City($city, $horizon = false) + { + foreach (self::$cities as $key => $value) { + if (strtolower($key) === strtolower($city)) { + $return = $value; + $return['horizon'] = $horizon; + return $return; + } + } + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('unknown city'); + } + + /** + * Return a list with all known cities + * + * @return array + */ + public static function getCityList() + { + return array_keys(self::$cities); + } +} diff --git a/library/Zend/Date/DateObject.php b/library/Zend/Date/DateObject.php new file mode 100644 index 00000000..2fc9033f --- /dev/null +++ b/library/Zend/Date/DateObject.php @@ -0,0 +1,1089 @@ + 0, 1960 => -315619200, 1950 => -631152000, + 1940 => -946771200, 1930 => -1262304000, 1920 => -1577923200, + 1910 => -1893456000, 1900 => -2208988800, 1890 => -2524521600, + 1880 => -2840140800, 1870 => -3155673600, 1860 => -3471292800, + 1850 => -3786825600, 1840 => -4102444800, 1830 => -4417977600, + 1820 => -4733596800, 1810 => -5049129600, 1800 => -5364662400, + 1790 => -5680195200, 1780 => -5995814400, 1770 => -6311347200, + 1760 => -6626966400, 1750 => -6942499200, 1740 => -7258118400, + 1730 => -7573651200, 1720 => -7889270400, 1710 => -8204803200, + 1700 => -8520336000, 1690 => -8835868800, 1680 => -9151488000, + 1670 => -9467020800, 1660 => -9782640000, 1650 => -10098172800, + 1640 => -10413792000, 1630 => -10729324800, 1620 => -11044944000, + 1610 => -11360476800, 1600 => -11676096000); + + /** + * Set this object to have a new UNIX timestamp. + * + * @param string|integer $timestamp OPTIONAL timestamp; defaults to local time using time() + * @return string|integer old timestamp + * @throws Zend_Date_Exception + */ + protected function setUnixTimestamp($timestamp = null) + { + $old = $this->_unixTimestamp; + + if (is_numeric($timestamp)) { + $this->_unixTimestamp = $timestamp; + } else if ($timestamp === null) { + $this->_unixTimestamp = time(); + } else { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('\'' . $timestamp . '\' is not a valid UNIX timestamp', 0, null, $timestamp); + } + + return $old; + } + + /** + * Returns this object's UNIX timestamp + * A timestamp greater then the integer range will be returned as string + * This function does not return the timestamp as object. Use copy() instead. + * + * @return integer|string timestamp + */ + protected function getUnixTimestamp() + { + if ($this->_unixTimestamp === intval($this->_unixTimestamp)) { + return (int) $this->_unixTimestamp; + } else { + return (string) $this->_unixTimestamp; + } + } + + /** + * Internal function. + * Returns time(). This method exists to allow unit tests to work-around methods that might otherwise + * be hard-coded to use time(). For example, this makes it possible to test isYesterday() in Date.php. + * + * @param integer $sync OPTIONAL time syncronisation value + * @return integer timestamp + */ + protected function _getTime($sync = null) + { + if ($sync !== null) { + $this->_syncronised = round($sync); + } + return (time() + $this->_syncronised); + } + + /** + * Internal mktime function used by Zend_Date. + * The timestamp returned by mktime() can exceed the precision of traditional UNIX timestamps, + * by allowing PHP to auto-convert to using a float value. + * + * Returns a timestamp relative to 1970/01/01 00:00:00 GMT/UTC. + * DST (Summer/Winter) is depriciated since php 5.1.0. + * Year has to be 4 digits otherwise it would be recognised as + * year 70 AD instead of 1970 AD as expected !! + * + * @param integer $hour + * @param integer $minute + * @param integer $second + * @param integer $month + * @param integer $day + * @param integer $year + * @param boolean $gmt OPTIONAL true = other arguments are for UTC time, false = arguments are for local time/date + * @return integer|float timestamp (number of seconds elapsed relative to 1970/01/01 00:00:00 GMT/UTC) + */ + protected function mktime($hour, $minute, $second, $month, $day, $year, $gmt = false) + { + // complete date but in 32bit timestamp - use PHP internal + if ((1901 < $year) and ($year < 2038)) { + + $oldzone = @date_default_timezone_get(); + // Timezone also includes DST settings, therefor substracting the GMT offset is not enough + // We have to set the correct timezone to get the right value + if (($this->_timezone != $oldzone) and ($gmt === false)) { + date_default_timezone_set($this->_timezone); + } + $result = ($gmt) ? @gmmktime($hour, $minute, $second, $month, $day, $year) + : @mktime($hour, $minute, $second, $month, $day, $year); + date_default_timezone_set($oldzone); + + return $result; + } + + if ($gmt !== true) { + $second += $this->_offset; + } + + if (isset(self::$_cache)) { + $id = strtr('Zend_DateObject_mkTime_' . $this->_offset . '_' . $year.$month.$day.'_'.$hour.$minute.$second . '_'.(int)$gmt, '-','_'); + if ($result = self::$_cache->load($id)) { + return unserialize($result); + } + } + + // date to integer + $day = intval($day); + $month = intval($month); + $year = intval($year); + + // correct months > 12 and months < 1 + if ($month > 12) { + $overlap = floor($month / 12); + $year += $overlap; + $month -= $overlap * 12; + } else { + $overlap = ceil((1 - $month) / 12); + $year -= $overlap; + $month += $overlap * 12; + } + + $date = 0; + if ($year >= 1970) { + + // Date is after UNIX epoch + // go through leapyears + // add months from latest given year + for ($count = 1970; $count <= $year; $count++) { + + $leapyear = self::isYearLeapYear($count); + if ($count < $year) { + + $date += 365; + if ($leapyear === true) { + $date++; + } + + } else { + + for ($mcount = 0; $mcount < ($month - 1); $mcount++) { + $date += self::$_monthTable[$mcount]; + if (($leapyear === true) and ($mcount == 1)) { + $date++; + } + + } + } + } + + $date += $day - 1; + $date = (($date * 86400) + ($hour * 3600) + ($minute * 60) + $second); + } else { + + // Date is before UNIX epoch + // go through leapyears + // add months from latest given year + for ($count = 1969; $count >= $year; $count--) { + + $leapyear = self::isYearLeapYear($count); + if ($count > $year) + { + $date += 365; + if ($leapyear === true) + $date++; + } else { + + for ($mcount = 11; $mcount > ($month - 1); $mcount--) { + $date += self::$_monthTable[$mcount]; + if (($leapyear === true) and ($mcount == 2)) { + $date++; + } + + } + } + } + + $date += (self::$_monthTable[$month - 1] - $day); + $date = -(($date * 86400) + (86400 - (($hour * 3600) + ($minute * 60) + $second))); + + // gregorian correction for 5.Oct.1582 + if ($date < -12220185600) { + $date += 864000; + } else if ($date < -12219321600) { + $date = -12219321600; + } + } + + if (isset(self::$_cache)) { + if (self::$_cacheTags) { + self::$_cache->save( serialize($date), $id, array('Zend_Date')); + } else { + self::$_cache->save( serialize($date), $id); + } + } + + return $date; + } + + /** + * Returns true, if given $year is a leap year. + * + * @param integer $year + * @return boolean true, if year is leap year + */ + protected static function isYearLeapYear($year) + { + // all leapyears can be divided through 4 + if (($year % 4) != 0) { + return false; + } + + // all leapyears can be divided through 400 + if ($year % 400 == 0) { + return true; + } else if (($year > 1582) and ($year % 100 == 0)) { + return false; + } + + return true; + } + + /** + * Internal mktime function used by Zend_Date for handling 64bit timestamps. + * + * Returns a formatted date for a given timestamp. + * + * @param string $format format for output + * @param mixed $timestamp + * @param boolean $gmt OPTIONAL true = other arguments are for UTC time, false = arguments are for local time/date + * @return string + */ + protected function date($format, $timestamp = null, $gmt = false) + { + $oldzone = @date_default_timezone_get(); + if ($this->_timezone != $oldzone) { + date_default_timezone_set($this->_timezone); + } + + if ($timestamp === null) { + $result = ($gmt) ? @gmdate($format) : @date($format); + date_default_timezone_set($oldzone); + return $result; + } + + if (abs($timestamp) <= 0x7FFFFFFF) { + $result = ($gmt) ? @gmdate($format, $timestamp) : @date($format, $timestamp); + date_default_timezone_set($oldzone); + return $result; + } + + $jump = false; + $origstamp = $timestamp; + if (isset(self::$_cache)) { + $idstamp = strtr('Zend_DateObject_date_' . $this->_offset . '_'. $timestamp . '_'.(int)$gmt, '-','_'); + if ($result2 = self::$_cache->load($idstamp)) { + $timestamp = unserialize($result2); + $jump = true; + } + } + + // check on false or null alone fails + if (empty($gmt) and empty($jump)) { + $tempstamp = $timestamp; + if ($tempstamp > 0) { + while (abs($tempstamp) > 0x7FFFFFFF) { + $tempstamp -= (86400 * 23376); + } + + $dst = date("I", $tempstamp); + if ($dst === 1) { + $timestamp += 3600; + } + + $temp = date('Z', $tempstamp); + $timestamp += $temp; + } + + if (isset(self::$_cache)) { + if (self::$_cacheTags) { + self::$_cache->save( serialize($timestamp), $idstamp, array('Zend_Date')); + } else { + self::$_cache->save( serialize($timestamp), $idstamp); + } + } + } + + if (($timestamp < 0) and ($gmt !== true)) { + $timestamp -= $this->_offset; + } + + date_default_timezone_set($oldzone); + $date = $this->getDateParts($timestamp, true); + $length = strlen($format); + $output = ''; + + for ($i = 0; $i < $length; $i++) { + switch($format[$i]) { + // day formats + case 'd': // day of month, 2 digits, with leading zero, 01 - 31 + $output .= (($date['mday'] < 10) ? '0' . $date['mday'] : $date['mday']); + break; + + case 'D': // day of week, 3 letters, Mon - Sun + $output .= date('D', 86400 * (3 + self::dayOfWeek($date['year'], $date['mon'], $date['mday']))); + break; + + case 'j': // day of month, without leading zero, 1 - 31 + $output .= $date['mday']; + break; + + case 'l': // day of week, full string name, Sunday - Saturday + $output .= date('l', 86400 * (3 + self::dayOfWeek($date['year'], $date['mon'], $date['mday']))); + break; + + case 'N': // ISO 8601 numeric day of week, 1 - 7 + $day = self::dayOfWeek($date['year'], $date['mon'], $date['mday']); + if ($day == 0) { + $day = 7; + } + $output .= $day; + break; + + case 'S': // english suffix for day of month, st nd rd th + if (($date['mday'] % 10) == 1) { + $output .= 'st'; + } else if ((($date['mday'] % 10) == 2) and ($date['mday'] != 12)) { + $output .= 'nd'; + } else if (($date['mday'] % 10) == 3) { + $output .= 'rd'; + } else { + $output .= 'th'; + } + break; + + case 'w': // numeric day of week, 0 - 6 + $output .= self::dayOfWeek($date['year'], $date['mon'], $date['mday']); + break; + + case 'z': // day of year, 0 - 365 + $output .= $date['yday']; + break; + + + // week formats + case 'W': // ISO 8601, week number of year + $output .= $this->weekNumber($date['year'], $date['mon'], $date['mday']); + break; + + + // month formats + case 'F': // string month name, january - december + $output .= date('F', mktime(0, 0, 0, $date['mon'], 2, 1971)); + break; + + case 'm': // number of month, with leading zeros, 01 - 12 + $output .= (($date['mon'] < 10) ? '0' . $date['mon'] : $date['mon']); + break; + + case 'M': // 3 letter month name, Jan - Dec + $output .= date('M',mktime(0, 0, 0, $date['mon'], 2, 1971)); + break; + + case 'n': // number of month, without leading zeros, 1 - 12 + $output .= $date['mon']; + break; + + case 't': // number of day in month + $output .= self::$_monthTable[$date['mon'] - 1]; + break; + + + // year formats + case 'L': // is leap year ? + $output .= (self::isYearLeapYear($date['year'])) ? '1' : '0'; + break; + + case 'o': // ISO 8601 year number + $week = $this->weekNumber($date['year'], $date['mon'], $date['mday']); + if (($week > 50) and ($date['mon'] == 1)) { + $output .= ($date['year'] - 1); + } else { + $output .= $date['year']; + } + break; + + case 'Y': // year number, 4 digits + $output .= $date['year']; + break; + + case 'y': // year number, 2 digits + $output .= substr($date['year'], strlen($date['year']) - 2, 2); + break; + + + // time formats + case 'a': // lower case am/pm + $output .= (($date['hours'] >= 12) ? 'pm' : 'am'); + break; + + case 'A': // upper case am/pm + $output .= (($date['hours'] >= 12) ? 'PM' : 'AM'); + break; + + case 'B': // swatch internet time + $dayseconds = ($date['hours'] * 3600) + ($date['minutes'] * 60) + $date['seconds']; + if ($gmt === true) { + $dayseconds += 3600; + } + $output .= (int) (($dayseconds % 86400) / 86.4); + break; + + case 'g': // hours without leading zeros, 12h format + if ($date['hours'] > 12) { + $hour = $date['hours'] - 12; + } else { + if ($date['hours'] == 0) { + $hour = '12'; + } else { + $hour = $date['hours']; + } + } + $output .= $hour; + break; + + case 'G': // hours without leading zeros, 24h format + $output .= $date['hours']; + break; + + case 'h': // hours with leading zeros, 12h format + if ($date['hours'] > 12) { + $hour = $date['hours'] - 12; + } else { + if ($date['hours'] == 0) { + $hour = '12'; + } else { + $hour = $date['hours']; + } + } + $output .= (($hour < 10) ? '0'.$hour : $hour); + break; + + case 'H': // hours with leading zeros, 24h format + $output .= (($date['hours'] < 10) ? '0' . $date['hours'] : $date['hours']); + break; + + case 'i': // minutes with leading zeros + $output .= (($date['minutes'] < 10) ? '0' . $date['minutes'] : $date['minutes']); + break; + + case 's': // seconds with leading zeros + $output .= (($date['seconds'] < 10) ? '0' . $date['seconds'] : $date['seconds']); + break; + + + // timezone formats + case 'e': // timezone identifier + if ($gmt === true) { + $output .= gmdate('e', mktime($date['hours'], $date['minutes'], $date['seconds'], + $date['mon'], $date['mday'], 2000)); + } else { + $output .= date('e', mktime($date['hours'], $date['minutes'], $date['seconds'], + $date['mon'], $date['mday'], 2000)); + } + break; + + case 'I': // daylight saving time or not + if ($gmt === true) { + $output .= gmdate('I', mktime($date['hours'], $date['minutes'], $date['seconds'], + $date['mon'], $date['mday'], 2000)); + } else { + $output .= date('I', mktime($date['hours'], $date['minutes'], $date['seconds'], + $date['mon'], $date['mday'], 2000)); + } + break; + + case 'O': // difference to GMT in hours + $gmtstr = ($gmt === true) ? 0 : $this->getGmtOffset(); + $output .= sprintf('%s%04d', ($gmtstr <= 0) ? '+' : '-', abs($gmtstr) / 36); + break; + + case 'P': // difference to GMT with colon + $gmtstr = ($gmt === true) ? 0 : $this->getGmtOffset(); + $gmtstr = sprintf('%s%04d', ($gmtstr <= 0) ? '+' : '-', abs($gmtstr) / 36); + $output = $output . substr($gmtstr, 0, 3) . ':' . substr($gmtstr, 3); + break; + + case 'T': // timezone settings + if ($gmt === true) { + $output .= gmdate('T', mktime($date['hours'], $date['minutes'], $date['seconds'], + $date['mon'], $date['mday'], 2000)); + } else { + $output .= date('T', mktime($date['hours'], $date['minutes'], $date['seconds'], + $date['mon'], $date['mday'], 2000)); + } + break; + + case 'Z': // timezone offset in seconds + $output .= ($gmt === true) ? 0 : -$this->getGmtOffset(); + break; + + + // complete time formats + case 'c': // ISO 8601 date format + $difference = $this->getGmtOffset(); + $difference = sprintf('%s%04d', ($difference <= 0) ? '+' : '-', abs($difference) / 36); + $difference = substr($difference, 0, 3) . ':' . substr($difference, 3); + $output .= $date['year'] . '-' + . (($date['mon'] < 10) ? '0' . $date['mon'] : $date['mon']) . '-' + . (($date['mday'] < 10) ? '0' . $date['mday'] : $date['mday']) . 'T' + . (($date['hours'] < 10) ? '0' . $date['hours'] : $date['hours']) . ':' + . (($date['minutes'] < 10) ? '0' . $date['minutes'] : $date['minutes']) . ':' + . (($date['seconds'] < 10) ? '0' . $date['seconds'] : $date['seconds']) + . $difference; + break; + + case 'r': // RFC 2822 date format + $difference = $this->getGmtOffset(); + $difference = sprintf('%s%04d', ($difference <= 0) ? '+' : '-', abs($difference) / 36); + $output .= gmdate('D', 86400 * (3 + self::dayOfWeek($date['year'], $date['mon'], $date['mday']))) . ', ' + . (($date['mday'] < 10) ? '0' . $date['mday'] : $date['mday']) . ' ' + . date('M', mktime(0, 0, 0, $date['mon'], 2, 1971)) . ' ' + . $date['year'] . ' ' + . (($date['hours'] < 10) ? '0' . $date['hours'] : $date['hours']) . ':' + . (($date['minutes'] < 10) ? '0' . $date['minutes'] : $date['minutes']) . ':' + . (($date['seconds'] < 10) ? '0' . $date['seconds'] : $date['seconds']) . ' ' + . $difference; + break; + + case 'U': // Unix timestamp + $output .= $origstamp; + break; + + + // special formats + case "\\": // next letter to print with no format + $i++; + if ($i < $length) { + $output .= $format[$i]; + } + break; + + default: // letter is no format so add it direct + $output .= $format[$i]; + break; + } + } + + return (string) $output; + } + + /** + * Returns the day of week for a Gregorian calendar date. + * 0 = sunday, 6 = saturday + * + * @param integer $year + * @param integer $month + * @param integer $day + * @return integer dayOfWeek + */ + protected static function dayOfWeek($year, $month, $day) + { + if ((1901 < $year) and ($year < 2038)) { + return (int) date('w', mktime(0, 0, 0, $month, $day, $year)); + } + + // gregorian correction + $correction = 0; + if (($year < 1582) or (($year == 1582) and (($month < 10) or (($month == 10) && ($day < 15))))) { + $correction = 3; + } + + if ($month > 2) { + $month -= 2; + } else { + $month += 10; + $year--; + } + + $day = floor((13 * $month - 1) / 5) + $day + ($year % 100) + floor(($year % 100) / 4); + $day += floor(($year / 100) / 4) - 2 * floor($year / 100) + 77 + $correction; + + return (int) ($day - 7 * floor($day / 7)); + } + + /** + * Internal getDateParts function for handling 64bit timestamps, similar to: + * http://www.php.net/getdate + * + * Returns an array of date parts for $timestamp, relative to 1970/01/01 00:00:00 GMT/UTC. + * + * $fast specifies ALL date parts should be returned (slower) + * Default is false, and excludes $dayofweek, weekday, month and timestamp from parts returned. + * + * @param mixed $timestamp + * @param boolean $fast OPTIONAL defaults to fast (false), resulting in fewer date parts + * @return array + */ + protected function getDateParts($timestamp = null, $fast = null) + { + + // actual timestamp + if (!is_numeric($timestamp)) { + return getdate(); + } + + // 32bit timestamp + if (abs($timestamp) <= 0x7FFFFFFF) { + return @getdate((int) $timestamp); + } + + if (isset(self::$_cache)) { + $id = strtr('Zend_DateObject_getDateParts_' . $timestamp.'_'.(int)$fast, '-','_'); + if ($result = self::$_cache->load($id)) { + return unserialize($result); + } + } + + $otimestamp = $timestamp; + $numday = 0; + $month = 0; + // gregorian correction + if ($timestamp < -12219321600) { + $timestamp -= 864000; + } + + // timestamp lower 0 + if ($timestamp < 0) { + $sec = 0; + $act = 1970; + + // iterate through 10 years table, increasing speed + foreach(self::$_yearTable as $year => $seconds) { + if ($timestamp >= $seconds) { + $i = $act; + break; + } + $sec = $seconds; + $act = $year; + } + + $timestamp -= $sec; + if (!isset($i)) { + $i = $act; + } + + // iterate the max last 10 years + do { + --$i; + $day = $timestamp; + + $timestamp += 31536000; + $leapyear = self::isYearLeapYear($i); + if ($leapyear === true) { + $timestamp += 86400; + } + + if ($timestamp >= 0) { + $year = $i; + break; + } + } while ($timestamp < 0); + + $secondsPerYear = 86400 * ($leapyear ? 366 : 365) + $day; + + $timestamp = $day; + // iterate through months + for ($i = 12; --$i >= 0;) { + $day = $timestamp; + + $timestamp += self::$_monthTable[$i] * 86400; + if (($leapyear === true) and ($i == 1)) { + $timestamp += 86400; + } + + if ($timestamp >= 0) { + $month = $i; + $numday = self::$_monthTable[$i]; + if (($leapyear === true) and ($i == 1)) { + ++$numday; + } + break; + } + } + + $timestamp = $day; + $numberdays = $numday + ceil(($timestamp + 1) / 86400); + + $timestamp += ($numday - $numberdays + 1) * 86400; + $hours = floor($timestamp / 3600); + } else { + + // iterate through years + for ($i = 1970;;$i++) { + $day = $timestamp; + + $timestamp -= 31536000; + $leapyear = self::isYearLeapYear($i); + if ($leapyear === true) { + $timestamp -= 86400; + } + + if ($timestamp < 0) { + $year = $i; + break; + } + } + + $secondsPerYear = $day; + + $timestamp = $day; + // iterate through months + for ($i = 0; $i <= 11; $i++) { + $day = $timestamp; + $timestamp -= self::$_monthTable[$i] * 86400; + + if (($leapyear === true) and ($i == 1)) { + $timestamp -= 86400; + } + + if ($timestamp < 0) { + $month = $i; + $numday = self::$_monthTable[$i]; + if (($leapyear === true) and ($i == 1)) { + ++$numday; + } + break; + } + } + + $timestamp = $day; + $numberdays = ceil(($timestamp + 1) / 86400); + $timestamp = $timestamp - ($numberdays - 1) * 86400; + $hours = floor($timestamp / 3600); + } + + $timestamp -= $hours * 3600; + + $month += 1; + $minutes = floor($timestamp / 60); + $seconds = $timestamp - $minutes * 60; + + if ($fast === true) { + $array = array( + 'seconds' => $seconds, + 'minutes' => $minutes, + 'hours' => $hours, + 'mday' => $numberdays, + 'mon' => $month, + 'year' => $year, + 'yday' => floor($secondsPerYear / 86400), + ); + } else { + + $dayofweek = self::dayOfWeek($year, $month, $numberdays); + $array = array( + 'seconds' => $seconds, + 'minutes' => $minutes, + 'hours' => $hours, + 'mday' => $numberdays, + 'wday' => $dayofweek, + 'mon' => $month, + 'year' => $year, + 'yday' => floor($secondsPerYear / 86400), + 'weekday' => gmdate('l', 86400 * (3 + $dayofweek)), + 'month' => gmdate('F', mktime(0, 0, 0, $month, 1, 1971)), + 0 => $otimestamp + ); + } + + if (isset(self::$_cache)) { + if (self::$_cacheTags) { + self::$_cache->save( serialize($array), $id, array('Zend_Date')); + } else { + self::$_cache->save( serialize($array), $id); + } + } + + return $array; + } + + /** + * Internal getWeekNumber function for handling 64bit timestamps + * + * Returns the ISO 8601 week number of a given date + * + * @param integer $year + * @param integer $month + * @param integer $day + * @return integer + */ + protected function weekNumber($year, $month, $day) + { + if ((1901 < $year) and ($year < 2038)) { + return (int) date('W', mktime(0, 0, 0, $month, $day, $year)); + } + + $dayofweek = self::dayOfWeek($year, $month, $day); + $firstday = self::dayOfWeek($year, 1, 1); + if (($month == 1) and (($firstday < 1) or ($firstday > 4)) and ($day < 4)) { + $firstday = self::dayOfWeek($year - 1, 1, 1); + $month = 12; + $day = 31; + + } else if (($month == 12) and ((self::dayOfWeek($year + 1, 1, 1) < 5) and + (self::dayOfWeek($year + 1, 1, 1) > 0))) { + return 1; + } + + return intval (((self::dayOfWeek($year, 1, 1) < 5) and (self::dayOfWeek($year, 1, 1) > 0)) + + 4 * ($month - 1) + (2 * ($month - 1) + ($day - 1) + $firstday - $dayofweek + 6) * 36 / 256); + } + + /** + * Internal _range function + * Sets the value $a to be in the range of [0, $b] + * + * @param float $a - value to correct + * @param float $b - maximum range to set + */ + private function _range($a, $b) { + while ($a < 0) { + $a += $b; + } + while ($a >= $b) { + $a -= $b; + } + return $a; + } + + /** + * Calculates the sunrise or sunset based on a location + * + * @param array $location Location for calculation MUST include 'latitude', 'longitude', 'horizon' + * @param bool $horizon true: sunrise; false: sunset + * @return mixed - false: midnight sun, integer: + */ + protected function calcSun($location, $horizon, $rise = false) + { + // timestamp within 32bit + if (abs($this->_unixTimestamp) <= 0x7FFFFFFF) { + if ($rise === false) { + return date_sunset($this->_unixTimestamp, SUNFUNCS_RET_TIMESTAMP, $location['latitude'], + $location['longitude'], 90 + $horizon, $this->getGmtOffset() / 3600); + } + return date_sunrise($this->_unixTimestamp, SUNFUNCS_RET_TIMESTAMP, $location['latitude'], + $location['longitude'], 90 + $horizon, $this->getGmtOffset() / 3600); + } + + // self calculation - timestamp bigger than 32bit + // fix circle values + $quarterCircle = 0.5 * M_PI; + $halfCircle = M_PI; + $threeQuarterCircle = 1.5 * M_PI; + $fullCircle = 2 * M_PI; + + // radiant conversion for coordinates + $radLatitude = $location['latitude'] * $halfCircle / 180; + $radLongitude = $location['longitude'] * $halfCircle / 180; + + // get solar coordinates + $tmpRise = $rise ? $quarterCircle : $threeQuarterCircle; + $radDay = $this->date('z',$this->_unixTimestamp) + ($tmpRise - $radLongitude) / $fullCircle; + + // solar anomoly and longitude + $solAnomoly = $radDay * 0.017202 - 0.0574039; + $solLongitude = $solAnomoly + 0.0334405 * sin($solAnomoly); + $solLongitude += 4.93289 + 3.49066E-4 * sin(2 * $solAnomoly); + + // get quadrant + $solLongitude = $this->_range($solLongitude, $fullCircle); + + if (($solLongitude / $quarterCircle) - intval($solLongitude / $quarterCircle) == 0) { + $solLongitude += 4.84814E-6; + } + + // solar ascension + $solAscension = sin($solLongitude) / cos($solLongitude); + $solAscension = atan2(0.91746 * $solAscension, 1); + + // adjust quadrant + if ($solLongitude > $threeQuarterCircle) { + $solAscension += $fullCircle; + } else if ($solLongitude > $quarterCircle) { + $solAscension += $halfCircle; + } + + // solar declination + $solDeclination = 0.39782 * sin($solLongitude); + $solDeclination /= sqrt(-$solDeclination * $solDeclination + 1); + $solDeclination = atan2($solDeclination, 1); + + $solHorizon = $horizon - sin($solDeclination) * sin($radLatitude); + $solHorizon /= cos($solDeclination) * cos($radLatitude); + + // midnight sun, always night + if (abs($solHorizon) > 1) { + return false; + } + + $solHorizon /= sqrt(-$solHorizon * $solHorizon + 1); + $solHorizon = $quarterCircle - atan2($solHorizon, 1); + + if ($rise) { + $solHorizon = $fullCircle - $solHorizon; + } + + // time calculation + $localTime = $solHorizon + $solAscension - 0.0172028 * $radDay - 1.73364; + $universalTime = $localTime - $radLongitude; + + // determinate quadrant + $universalTime = $this->_range($universalTime, $fullCircle); + + // radiant to hours + $universalTime *= 24 / $fullCircle; + + // convert to time + $hour = intval($universalTime); + $universalTime = ($universalTime - $hour) * 60; + $min = intval($universalTime); + $universalTime = ($universalTime - $min) * 60; + $sec = intval($universalTime); + + return $this->mktime($hour, $min, $sec, $this->date('m', $this->_unixTimestamp), + $this->date('j', $this->_unixTimestamp), $this->date('Y', $this->_unixTimestamp), + -1, true); + } + + /** + * Sets a new timezone for calculation of $this object's gmt offset. + * For a list of supported timezones look here: http://php.net/timezones + * If no timezone can be detected or the given timezone is wrong UTC will be set. + * + * @param string $zone OPTIONAL timezone for date calculation; defaults to date_default_timezone_get() + * @return Zend_Date_DateObject Provides fluent interface + * @throws Zend_Date_Exception + */ + public function setTimezone($zone = null) + { + $oldzone = @date_default_timezone_get(); + if ($zone === null) { + $zone = $oldzone; + } + + // throw an error on false input, but only if the new date extension is available + if (function_exists('timezone_open')) { + if (!@timezone_open($zone)) { + require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("timezone ($zone) is not a known timezone", 0, null, $zone); + } + } + // this can generate an error if the date extension is not available and a false timezone is given + $result = @date_default_timezone_set($zone); + if ($result === true) { + $this->_offset = mktime(0, 0, 0, 1, 2, 1970) - gmmktime(0, 0, 0, 1, 2, 1970); + $this->_timezone = $zone; + } + date_default_timezone_set($oldzone); + + if (($zone == 'UTC') or ($zone == 'GMT')) { + $this->_dst = false; + } else { + $this->_dst = true; + } + + return $this; + } + + /** + * Return the timezone of $this object. + * The timezone is initially set when the object is instantiated. + * + * @return string actual set timezone string + */ + public function getTimezone() + { + return $this->_timezone; + } + + /** + * Return the offset to GMT of $this object's timezone. + * The offset to GMT is initially set when the object is instantiated using the currently, + * in effect, default timezone for PHP functions. + * + * @return integer seconds difference between GMT timezone and timezone when object was instantiated + */ + public function getGmtOffset() + { + $date = $this->getDateParts($this->getUnixTimestamp(), true); + $zone = @date_default_timezone_get(); + $result = @date_default_timezone_set($this->_timezone); + if ($result === true) { + $offset = $this->mktime($date['hours'], $date['minutes'], $date['seconds'], + $date['mon'], $date['mday'], $date['year'], false) + - $this->mktime($date['hours'], $date['minutes'], $date['seconds'], + $date['mon'], $date['mday'], $date['year'], true); + } + date_default_timezone_set($zone); + + return $offset; + } + + /** + * Internal method to check if the given cache supports tags + * + * @param Zend_Cache $cache + */ + protected static function _getTagSupportForCache() + { + $backend = self::$_cache->getBackend(); + if ($backend instanceof Zend_Cache_Backend_ExtendedInterface) { + $cacheOptions = $backend->getCapabilities(); + self::$_cacheTags = $cacheOptions['tags']; + } else { + self::$_cacheTags = false; + } + + return self::$_cacheTags; + } +} diff --git a/library/Zend/Date/Exception.php b/library/Zend/Date/Exception.php new file mode 100644 index 00000000..1f7e4d98 --- /dev/null +++ b/library/Zend/Date/Exception.php @@ -0,0 +1,49 @@ +operand = $op; + parent::__construct($message, $code, $e); + } + + public function getOperand() + { + return $this->operand; + } +} diff --git a/library/Zend/Db.php b/library/Zend/Db.php new file mode 100644 index 00000000..f0c0eb91 --- /dev/null +++ b/library/Zend/Db.php @@ -0,0 +1,273 @@ +toArray(); + } + + /* + * Convert Zend_Config argument to plain string + * adapter name and separate config object. + */ + if ($adapter instanceof Zend_Config) { + if (isset($adapter->params)) { + $config = $adapter->params->toArray(); + } + if (isset($adapter->adapter)) { + $adapter = (string) $adapter->adapter; + } else { + $adapter = null; + } + } + + /* + * Verify that adapter parameters are in an array. + */ + if (!is_array($config)) { + /** + * @see Zend_Db_Exception + */ + require_once 'Zend/Db/Exception.php'; + throw new Zend_Db_Exception('Adapter parameters must be in an array or a Zend_Config object'); + } + + /* + * Verify that an adapter name has been specified. + */ + if (!is_string($adapter) || empty($adapter)) { + /** + * @see Zend_Db_Exception + */ + require_once 'Zend/Db/Exception.php'; + throw new Zend_Db_Exception('Adapter name must be specified in a string'); + } + + /* + * Form full adapter class name + */ + $adapterNamespace = 'Zend_Db_Adapter'; + if (isset($config['adapterNamespace'])) { + if ($config['adapterNamespace'] != '') { + $adapterNamespace = $config['adapterNamespace']; + } + unset($config['adapterNamespace']); + } + + // Adapter no longer normalized- see http://framework.zend.com/issues/browse/ZF-5606 + $adapterName = $adapterNamespace . '_'; + $adapterName .= str_replace(' ', '_', ucwords(str_replace('_', ' ', strtolower($adapter)))); + + /* + * Load the adapter class. This throws an exception + * if the specified class cannot be loaded. + */ + if (!class_exists($adapterName)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($adapterName); + } + + /* + * Create an instance of the adapter class. + * Pass the config to the adapter class constructor. + */ + $dbAdapter = new $adapterName($config); + + /* + * Verify that the object created is a descendent of the abstract adapter type. + */ + if (! $dbAdapter instanceof Zend_Db_Adapter_Abstract) { + /** + * @see Zend_Db_Exception + */ + require_once 'Zend/Db/Exception.php'; + throw new Zend_Db_Exception("Adapter class '$adapterName' does not extend Zend_Db_Adapter_Abstract"); + } + + return $dbAdapter; + } + +} diff --git a/library/Zend/Db/Adapter/Abstract.php b/library/Zend/Db/Adapter/Abstract.php new file mode 100644 index 00000000..f0340d69 --- /dev/null +++ b/library/Zend/Db/Adapter/Abstract.php @@ -0,0 +1,1280 @@ + Zend_Db::INT_TYPE, + Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE, + Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE + ); + + /** Weither or not that object can get serialized + * + * @var bool + */ + protected $_allowSerialization = true; + + /** + * Weither or not the database should be reconnected + * to that adapter when waking up + * + * @var bool + */ + protected $_autoReconnectOnUnserialize = false; + + /** + * Constructor. + * + * $config is an array of key/value pairs or an instance of Zend_Config + * containing configuration options. These options are common to most adapters: + * + * dbname => (string) The name of the database to user + * username => (string) Connect to the database as this username. + * password => (string) Password associated with the username. + * host => (string) What host to connect to, defaults to localhost + * + * Some options are used on a case-by-case basis by adapters: + * + * port => (string) The port of the database + * persistent => (boolean) Whether to use a persistent connection or not, defaults to false + * protocol => (string) The network protocol, defaults to TCPIP + * caseFolding => (int) style of case-alteration used for identifiers + * + * @param array|Zend_Config $config An array or instance of Zend_Config having configuration data + * @throws Zend_Db_Adapter_Exception + */ + public function __construct($config) + { + /* + * Verify that adapter parameters are in an array. + */ + if (!is_array($config)) { + /* + * Convert Zend_Config argument to a plain array. + */ + if ($config instanceof Zend_Config) { + $config = $config->toArray(); + } else { + /** + * @see Zend_Db_Adapter_Exception + */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception('Adapter parameters must be in an array or a Zend_Config object'); + } + } + + $this->_checkRequiredOptions($config); + + $options = array( + Zend_Db::CASE_FOLDING => $this->_caseFolding, + Zend_Db::AUTO_QUOTE_IDENTIFIERS => $this->_autoQuoteIdentifiers, + Zend_Db::FETCH_MODE => $this->_fetchMode, + ); + $driverOptions = array(); + + /* + * normalize the config and merge it with the defaults + */ + if (array_key_exists('options', $config)) { + // can't use array_merge() because keys might be integers + foreach ((array) $config['options'] as $key => $value) { + $options[$key] = $value; + } + } + if (array_key_exists('driver_options', $config)) { + if (!empty($config['driver_options'])) { + // can't use array_merge() because keys might be integers + foreach ((array) $config['driver_options'] as $key => $value) { + $driverOptions[$key] = $value; + } + } + } + + if (!isset($config['charset'])) { + $config['charset'] = null; + } + + if (!isset($config['persistent'])) { + $config['persistent'] = false; + } + + $this->_config = array_merge($this->_config, $config); + $this->_config['options'] = $options; + $this->_config['driver_options'] = $driverOptions; + + + // obtain the case setting, if there is one + if (array_key_exists(Zend_Db::CASE_FOLDING, $options)) { + $case = (int) $options[Zend_Db::CASE_FOLDING]; + switch ($case) { + case Zend_Db::CASE_LOWER: + case Zend_Db::CASE_UPPER: + case Zend_Db::CASE_NATURAL: + $this->_caseFolding = $case; + break; + default: + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception('Case must be one of the following constants: ' + . 'Zend_Db::CASE_NATURAL, Zend_Db::CASE_LOWER, Zend_Db::CASE_UPPER'); + } + } + + if (array_key_exists(Zend_Db::FETCH_MODE, $options)) { + if (is_string($options[Zend_Db::FETCH_MODE])) { + $constant = 'Zend_Db::FETCH_' . strtoupper($options[Zend_Db::FETCH_MODE]); + if(defined($constant)) { + $options[Zend_Db::FETCH_MODE] = constant($constant); + } + } + $this->setFetchMode((int) $options[Zend_Db::FETCH_MODE]); + } + + // obtain quoting property if there is one + if (array_key_exists(Zend_Db::AUTO_QUOTE_IDENTIFIERS, $options)) { + $this->_autoQuoteIdentifiers = (bool) $options[Zend_Db::AUTO_QUOTE_IDENTIFIERS]; + } + + // obtain allow serialization property if there is one + if (array_key_exists(Zend_Db::ALLOW_SERIALIZATION, $options)) { + $this->_allowSerialization = (bool) $options[Zend_Db::ALLOW_SERIALIZATION]; + } + + // obtain auto reconnect on unserialize property if there is one + if (array_key_exists(Zend_Db::AUTO_RECONNECT_ON_UNSERIALIZE, $options)) { + $this->_autoReconnectOnUnserialize = (bool) $options[Zend_Db::AUTO_RECONNECT_ON_UNSERIALIZE]; + } + + // create a profiler object + $profiler = false; + if (array_key_exists(Zend_Db::PROFILER, $this->_config)) { + $profiler = $this->_config[Zend_Db::PROFILER]; + unset($this->_config[Zend_Db::PROFILER]); + } + $this->setProfiler($profiler); + } + + /** + * 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)) { + /** + * @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 (! 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"); + } + } + + /** + * Returns the underlying database connection object or resource. + * If not presently connected, this initiates the connection. + * + * @return object|resource|null + */ + public function getConnection() + { + $this->_connect(); + return $this->_connection; + } + + /** + * Returns the configuration variables in this adapter. + * + * @return array + */ + public function getConfig() + { + return $this->_config; + } + + /** + * Set the adapter's profiler object. + * + * The argument may be a boolean, an associative array, an instance of + * Zend_Db_Profiler, or an instance of Zend_Config. + * + * A boolean argument sets the profiler to enabled if true, or disabled if + * false. The profiler class is the adapter's default profiler class, + * Zend_Db_Profiler. + * + * An instance of Zend_Db_Profiler sets the adapter's instance to that + * object. The profiler is enabled and disabled separately. + * + * An associative array argument may contain any of the keys 'enabled', + * 'class', and 'instance'. The 'enabled' and 'instance' keys correspond to the + * boolean and object types documented above. The 'class' key is used to name a + * class to use for a custom profiler. The class must be Zend_Db_Profiler or a + * subclass. The class is instantiated with no constructor arguments. The 'class' + * option is ignored when the 'instance' option is supplied. + * + * An object of type Zend_Config may contain the properties 'enabled', 'class', and + * 'instance', just as if an associative array had been passed instead. + * + * @param Zend_Db_Profiler|Zend_Config|array|boolean $profiler + * @return Zend_Db_Adapter_Abstract Provides a fluent interface + * @throws Zend_Db_Profiler_Exception if the object instance or class specified + * is not Zend_Db_Profiler or an extension of that class. + */ + public function setProfiler($profiler) + { + $enabled = null; + $profilerClass = $this->_defaultProfilerClass; + $profilerInstance = null; + + if ($profilerIsObject = is_object($profiler)) { + if ($profiler instanceof Zend_Db_Profiler) { + $profilerInstance = $profiler; + } else if ($profiler instanceof Zend_Config) { + $profiler = $profiler->toArray(); + } else { + /** + * @see Zend_Db_Profiler_Exception + */ + require_once 'Zend/Db/Profiler/Exception.php'; + throw new Zend_Db_Profiler_Exception('Profiler argument must be an instance of either Zend_Db_Profiler' + . ' or Zend_Config when provided as an object'); + } + } + + if (is_array($profiler)) { + if (isset($profiler['enabled'])) { + $enabled = (bool) $profiler['enabled']; + } + if (isset($profiler['class'])) { + $profilerClass = $profiler['class']; + } + if (isset($profiler['instance'])) { + $profilerInstance = $profiler['instance']; + } + } else if (!$profilerIsObject) { + $enabled = (bool) $profiler; + } + + if ($profilerInstance === null) { + if (!class_exists($profilerClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($profilerClass); + } + $profilerInstance = new $profilerClass(); + } + + if (!$profilerInstance instanceof Zend_Db_Profiler) { + /** @see Zend_Db_Profiler_Exception */ + require_once 'Zend/Db/Profiler/Exception.php'; + throw new Zend_Db_Profiler_Exception('Class ' . get_class($profilerInstance) . ' does not extend ' + . 'Zend_Db_Profiler'); + } + + if (null !== $enabled) { + $profilerInstance->setEnabled($enabled); + } + + $this->_profiler = $profilerInstance; + + return $this; + } + + + /** + * Returns the profiler for this adapter. + * + * @return Zend_Db_Profiler + */ + public function getProfiler() + { + return $this->_profiler; + } + + /** + * Get the default statement class. + * + * @return string + */ + public function getStatementClass() + { + return $this->_defaultStmtClass; + } + + /** + * Set the default statement class. + * + * @return Zend_Db_Adapter_Abstract Fluent interface + */ + public function setStatementClass($class) + { + $this->_defaultStmtClass = $class; + return $this; + } + + /** + * Prepares and executes an SQL statement with bound data. + * + * @param mixed $sql The SQL statement with placeholders. + * May be a string or Zend_Db_Select. + * @param mixed $bind An array of data to bind to the placeholders. + * @return Zend_Db_Statement_Interface + */ + public function query($sql, $bind = array()) + { + // connect to the database if needed + $this->_connect(); + + // is the $sql a Zend_Db_Select object? + if ($sql instanceof Zend_Db_Select) { + if (empty($bind)) { + $bind = $sql->getBind(); + } + + $sql = $sql->assemble(); + } + + // make sure $bind to an array; + // don't use (array) typecasting because + // because $bind may be a Zend_Db_Expr object + if (!is_array($bind)) { + $bind = array($bind); + } + + // prepare and execute the statement with profiling + $stmt = $this->prepare($sql); + $stmt->execute($bind); + + // return the results embedded in the prepared statement object + $stmt->setFetchMode($this->_fetchMode); + return $stmt; + } + + /** + * Leave autocommit mode and begin a transaction. + * + * @return Zend_Db_Adapter_Abstract + */ + public function beginTransaction() + { + $this->_connect(); + $q = $this->_profiler->queryStart('begin', Zend_Db_Profiler::TRANSACTION); + $this->_beginTransaction(); + $this->_profiler->queryEnd($q); + return $this; + } + + /** + * Commit a transaction and return to autocommit mode. + * + * @return Zend_Db_Adapter_Abstract + */ + public function commit() + { + $this->_connect(); + $q = $this->_profiler->queryStart('commit', Zend_Db_Profiler::TRANSACTION); + $this->_commit(); + $this->_profiler->queryEnd($q); + return $this; + } + + /** + * Roll back a transaction and return to autocommit mode. + * + * @return Zend_Db_Adapter_Abstract + */ + public function rollBack() + { + $this->_connect(); + $q = $this->_profiler->queryStart('rollback', Zend_Db_Profiler::TRANSACTION); + $this->_rollBack(); + $this->_profiler->queryEnd($q); + return $this; + } + + /** + * 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. + * @throws Zend_Db_Adapter_Exception + */ + public function insert($table, array $bind) + { + // extract and quote col names from the array keys + $cols = array(); + $vals = array(); + $i = 0; + foreach ($bind as $col => $val) { + $cols[] = $this->quoteIdentifier($col, true); + if ($val instanceof Zend_Db_Expr) { + $vals[] = $val->__toString(); + unset($bind[$col]); + } else { + if ($this->supportsParameters('positional')) { + $vals[] = '?'; + } else { + if ($this->supportsParameters('named')) { + unset($bind[$col]); + $bind[':col'.$i] = $val; + $vals[] = ':col'.$i; + $i++; + } else { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception(get_class($this) ." doesn't support positional or named binding"); + } + } + } + } + + // build the statement + $sql = "INSERT INTO " + . $this->quoteIdentifier($table, true) + . ' (' . implode(', ', $cols) . ') ' + . 'VALUES (' . implode(', ', $vals) . ')'; + + // execute the statement and return the number of affected rows + if ($this->supportsParameters('positional')) { + $bind = array_values($bind); + } + $stmt = $this->query($sql, $bind); + $result = $stmt->rowCount(); + return $result; + } + + /** + * Updates table rows with specified data based on a WHERE clause. + * + * @param mixed $table The table to update. + * @param array $bind Column-value pairs. + * @param mixed $where UPDATE WHERE clause(s). + * @return int The number of affected rows. + * @throws Zend_Db_Adapter_Exception + */ + public function update($table, array $bind, $where = '') + { + /** + * Build "col = ?" pairs for the statement, + * except for Zend_Db_Expr which is treated literally. + */ + $set = array(); + $i = 0; + foreach ($bind as $col => $val) { + if ($val instanceof Zend_Db_Expr) { + $val = $val->__toString(); + unset($bind[$col]); + } else { + if ($this->supportsParameters('positional')) { + $val = '?'; + } else { + if ($this->supportsParameters('named')) { + unset($bind[$col]); + $bind[':col'.$i] = $val; + $val = ':col'.$i; + $i++; + } else { + /** @see Zend_Db_Adapter_Exception */ + require_once 'Zend/Db/Adapter/Exception.php'; + throw new Zend_Db_Adapter_Exception(get_class($this) ." doesn't support positional or named binding"); + } + } + } + $set[] = $this->quoteIdentifier($col, true) . ' = ' . $val; + } + + $where = $this->_whereExpr($where); + + /** + * Build the UPDATE statement + */ + $sql = "UPDATE " + . $this->quoteIdentifier($table, true) + . ' SET ' . implode(', ', $set) + . (($where) ? " WHERE $where" : ''); + + /** + * Execute the statement and return the number of affected rows + */ + if ($this->supportsParameters('positional')) { + $stmt = $this->query($sql, array_values($bind)); + } else { + $stmt = $this->query($sql, $bind); + } + $result = $stmt->rowCount(); + return $result; + } + + /** + * Deletes table rows based on a WHERE clause. + * + * @param mixed $table The table to update. + * @param mixed $where DELETE WHERE clause(s). + * @return int The number of affected rows. + */ + public function delete($table, $where = '') + { + $where = $this->_whereExpr($where); + + /** + * Build the DELETE statement + */ + $sql = "DELETE FROM " + . $this->quoteIdentifier($table, true) + . (($where) ? " WHERE $where" : ''); + + /** + * Execute the statement and return the number of affected rows + */ + $stmt = $this->query($sql); + $result = $stmt->rowCount(); + return $result; + } + + /** + * Convert an array, string, or Zend_Db_Expr object + * into a string to put in a WHERE clause. + * + * @param mixed $where + * @return string + */ + protected function _whereExpr($where) + { + if (empty($where)) { + return $where; + } + if (!is_array($where)) { + $where = array($where); + } + foreach ($where as $cond => &$term) { + // is $cond an int? (i.e. Not a condition) + if (is_int($cond)) { + // $term is the full condition + if ($term instanceof Zend_Db_Expr) { + $term = $term->__toString(); + } + } else { + // $cond is the condition with placeholder, + // and $term is quoted into the condition + $term = $this->quoteInto($cond, $term); + } + $term = '(' . $term . ')'; + } + + $where = implode(' AND ', $where); + return $where; + } + + /** + * Creates and returns a new Zend_Db_Select object for this adapter. + * + * @return Zend_Db_Select + */ + public function select() + { + return new Zend_Db_Select($this); + } + + /** + * Get the fetch mode. + * + * @return int + */ + public function getFetchMode() + { + return $this->_fetchMode; + } + + /** + * Fetches all SQL result rows as a sequential array. + * Uses the current fetchMode for the adapter. + * + * @param string|Zend_Db_Select $sql An SQL SELECT statement. + * @param mixed $bind Data to bind into SELECT placeholders. + * @param mixed $fetchMode Override current fetch mode. + * @return array + */ + public function fetchAll($sql, $bind = array(), $fetchMode = null) + { + if ($fetchMode === null) { + $fetchMode = $this->_fetchMode; + } + $stmt = $this->query($sql, $bind); + $result = $stmt->fetchAll($fetchMode); + return $result; + } + + /** + * Fetches the first row of the SQL result. + * Uses the current fetchMode for the adapter. + * + * @param string|Zend_Db_Select $sql An SQL SELECT statement. + * @param mixed $bind Data to bind into SELECT placeholders. + * @param mixed $fetchMode Override current fetch mode. + * @return array + */ + public function fetchRow($sql, $bind = array(), $fetchMode = null) + { + if ($fetchMode === null) { + $fetchMode = $this->_fetchMode; + } + $stmt = $this->query($sql, $bind); + $result = $stmt->fetch($fetchMode); + return $result; + } + + /** + * Fetches all SQL result rows as an associative array. + * + * The first column is the key, the entire row array is the + * value. You should construct the query to be sure that + * the first column contains unique values, or else + * rows with duplicate values in the first column will + * overwrite previous data. + * + * @param string|Zend_Db_Select $sql An SQL SELECT statement. + * @param mixed $bind Data to bind into SELECT placeholders. + * @return array + */ + public function fetchAssoc($sql, $bind = array()) + { + $stmt = $this->query($sql, $bind); + $data = array(); + while ($row = $stmt->fetch(Zend_Db::FETCH_ASSOC)) { + $tmp = array_values(array_slice($row, 0, 1)); + $data[$tmp[0]] = $row; + } + return $data; + } + + /** + * Fetches the first column of all SQL result rows as an array. + * + * @param string|Zend_Db_Select $sql An SQL SELECT statement. + * @param mixed $bind Data to bind into SELECT placeholders. + * @return array + */ + public function fetchCol($sql, $bind = array()) + { + $stmt = $this->query($sql, $bind); + $result = $stmt->fetchAll(Zend_Db::FETCH_COLUMN, 0); + return $result; + } + + /** + * Fetches all SQL result rows as an array of key-value pairs. + * + * The first column is the key, the second column is the + * value. + * + * @param string|Zend_Db_Select $sql An SQL SELECT statement. + * @param mixed $bind Data to bind into SELECT placeholders. + * @return array + */ + public function fetchPairs($sql, $bind = array()) + { + $stmt = $this->query($sql, $bind); + $data = array(); + while ($row = $stmt->fetch(Zend_Db::FETCH_NUM)) { + $data[$row[0]] = $row[1]; + } + return $data; + } + + /** + * Fetches the first column of the first row of the SQL result. + * + * @param string|Zend_Db_Select $sql An SQL SELECT statement. + * @param mixed $bind Data to bind into SELECT placeholders. + * @return string + */ + public function fetchOne($sql, $bind = array()) + { + $stmt = $this->query($sql, $bind); + $result = $stmt->fetchColumn(0); + return $result; + } + + /** + * 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 "'" . addcslashes($value, "\000\n\r\\'\"\032") . "'"; + } + + /** + * Safely quotes a value for an SQL statement. + * + * If an array is passed as the value, the array values are quoted + * and then returned as a comma-separated string. + * + * @param mixed $value The value to quote. + * @param mixed $type OPTIONAL the SQL datatype name, or constant, or null. + * @return mixed An SQL-safe quoted value (or string of separated values). + */ + public function quote($value, $type = null) + { + $this->_connect(); + + if ($value instanceof Zend_Db_Select) { + return '(' . $value->assemble() . ')'; + } + + if ($value instanceof Zend_Db_Expr) { + return $value->__toString(); + } + + if (is_array($value)) { + foreach ($value as &$val) { + $val = $this->quote($val, $type); + } + return implode(', ', $value); + } + + if ($type !== null && array_key_exists($type = strtoupper($type), $this->_numericDataTypes)) { + $quotedValue = '0'; + switch ($this->_numericDataTypes[$type]) { + case Zend_Db::INT_TYPE: // 32-bit integer + $quotedValue = (string) intval($value); + break; + case Zend_Db::BIGINT_TYPE: // 64-bit integer + // ANSI SQL-style hex literals (e.g. x'[\dA-F]+') + // are not supported here, because these are string + // literals, not numeric literals. + if (preg_match('/^( + [+-]? # optional sign + (?: + 0[Xx][\da-fA-F]+ # ODBC-style hexadecimal + |\d+ # decimal or octal, or MySQL ZEROFILL decimal + (?:[eE][+-]?\d+)? # optional exponent on decimals or octals + ) + )/x', + (string) $value, $matches)) { + $quotedValue = $matches[1]; + } + break; + case Zend_Db::FLOAT_TYPE: // float or decimal + $quotedValue = sprintf('%F', $value); + } + return $quotedValue; + } + + return $this->_quote($value); + } + + /** + * Quotes a value and places into a piece of text at a placeholder. + * + * The placeholder is a question-mark; all placeholders will be replaced + * with the quoted value. For example: + * + * + * $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 00000000..d8dca9b2 --- /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 00000000..dc43171a --- /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 00000000..4fa36202 --- /dev/null +++ b/library/Zend/Db/Adapter/Mysqli.php @@ -0,0 +1,549 @@ + 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; + } + + $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 + ); + + 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 00000000..95f09f1b --- /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 00000000..6bd64e3f --- /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 00000000..6dcc8168 --- /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 00000000..1769a157 --- /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 00000000..3048c50b --- /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 00000000..a84064da --- /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 00000000..f8d53a81 --- /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 00000000..fecc9e1e --- /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 00000000..aa09f9fb --- /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 00000000..89944c4b --- /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 00000000..7ba54edc --- /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 00000000..ecb04e3d --- /dev/null +++ b/library/Zend/Db/Adapter/Sqlsrv.php @@ -0,0 +1,673 @@ + (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; + $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 00000000..ac447c00 --- /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 00000000..7d1fd3a3 --- /dev/null +++ b/library/Zend/Db/Profiler.php @@ -0,0 +1,471 @@ +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; + } + + /** + * @param integer $queryId + * @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 void + */ + 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 00000000..5afe9323 --- /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 00000000..6f3a9b4c --- /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 00000000..29e8757c --- /dev/null +++ b/library/Zend/Db/Select.php @@ -0,0 +1,1351 @@ + 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); + + $cond1 = $from . '.' . $cond; + $cond2 = $join . '.' . $cond; + $cond = $cond1 . ' = ' . $cond2; + + 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)) { + $c = 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 " AS " and extract the alias name + if (preg_match('/^(.+)\s+' . self::SQL_AS . '\s+(.+)$/i', $col, $m)) { + $col = $m[1]; + $alias = $m[2]; + } + // Check for columns that look like functions and convert to Zend_Db_Expr + if (preg_match('/\(.*\)/', $col)) { + $col = new Zend_Db_Expr($col); + } elseif (preg_match('/(.+)\.(.+)/', $col, $m)) { + $currentCorrelationName = $m[1]; + $col = $m[2]; + } + } + $columnValues[] = array($currentCorrelationName, $col, is_string($alias) ? $alias : null); + } + + if ($columnValues) { + + // should we attempt to prepend or insert these values? + if ($afterCorrelationName === true || is_string($afterCorrelationName)) { + $tmpColumns = $this->_parts[self::COLUMNS]; + $this->_parts[self::COLUMNS] = array(); + } else { + $tmpColumns = array(); + } + + // find the correlation name to insert after + if (is_string($afterCorrelationName)) { + while ($tmpColumns) { + $this->_parts[self::COLUMNS][] = $currentColumn = array_shift($tmpColumns); + if ($currentColumn[0] == $afterCorrelationName) { + break; + } + } + } + + // apply current values to current stack + foreach ($columnValues as $columnValue) { + array_push($this->_parts[self::COLUMNS], $columnValue); + } + + // finish ensuring that all previous values are applied (if they exist) + while ($tmpColumns) { + array_push($this->_parts[self::COLUMNS], array_shift($tmpColumns)); + } + } + } + + /** + * Internal function for creating the where clause + * + * @param string $condition + * @param mixed $value optional + * @param string $type optional + * @param boolean $bool true = AND, false = OR + * @return string clause + */ + protected function _where($condition, $value = null, $type = null, $bool = true) + { + if (count($this->_parts[self::UNION])) { + require_once 'Zend/Db/Select/Exception.php'; + throw new Zend_Db_Select_Exception("Invalid use of where clause with " . self::SQL_UNION); + } + + if ($value !== null) { + $condition = $this->_adapter->quoteInto($condition, $value, $type); + } + + $cond = ""; + if ($this->_parts[self::WHERE]) { + if ($bool === true) { + $cond = self::SQL_AND . ' '; + } else { + $cond = self::SQL_OR . ' '; + } + } + + return $cond . "($condition)"; + } + + /** + * @return array + */ + protected function _getDummyTable() + { + return array(); + } + + /** + * Return a quoted schema name + * + * @param string $schema The schema name OPTIONAL + * @return string|null + */ + protected function _getQuotedSchema($schema = null) + { + if ($schema === null) { + return null; + } + return $this->_adapter->quoteIdentifier($schema, true) . '.'; + } + + /** + * Return a quoted table name + * + * @param string $tableName The table name + * @param string $correlationName The correlation name OPTIONAL + * @return string + */ + protected function _getQuotedTable($tableName, $correlationName = null) + { + return $this->_adapter->quoteTableAs($tableName, $correlationName, true); + } + + /** + * Render DISTINCT clause + * + * @param string $sql SQL query + * @return string + */ + protected function _renderDistinct($sql) + { + if ($this->_parts[self::DISTINCT]) { + $sql .= ' ' . self::SQL_DISTINCT; + } + + return $sql; + } + + /** + * Render DISTINCT clause + * + * @param string $sql SQL query + * @return string|null + */ + protected function _renderColumns($sql) + { + if (!count($this->_parts[self::COLUMNS])) { + return null; + } + + $columns = array(); + foreach ($this->_parts[self::COLUMNS] as $columnEntry) { + list($correlationName, $column, $alias) = $columnEntry; + if ($column instanceof Zend_Db_Expr) { + $columns[] = $this->_adapter->quoteColumnAs($column, $alias, true); + } else { + if ($column == self::SQL_WILDCARD) { + $column = new Zend_Db_Expr(self::SQL_WILDCARD); + $alias = null; + } + if (empty($correlationName)) { + $columns[] = $this->_adapter->quoteColumnAs($column, $alias, true); + } else { + $columns[] = $this->_adapter->quoteColumnAs(array($correlationName, $column), $alias, true); + } + } + } + + return $sql .= ' ' . implode(', ', $columns); + } + + /** + * Render FROM clause + * + * @param string $sql SQL query + * @return string + */ + protected function _renderFrom($sql) + { + /* + * If no table specified, use RDBMS-dependent solution + * for table-less query. e.g. DUAL in Oracle. + */ + if (empty($this->_parts[self::FROM])) { + $this->_parts[self::FROM] = $this->_getDummyTable(); + } + + $from = array(); + + foreach ($this->_parts[self::FROM] as $correlationName => $table) { + $tmp = ''; + + $joinType = ($table['joinType'] == self::FROM) ? self::INNER_JOIN : $table['joinType']; + + // Add join clause (if applicable) + if (! empty($from)) { + $tmp .= ' ' . strtoupper($joinType) . ' '; + } + + $tmp .= $this->_getQuotedSchema($table['schema']); + $tmp .= $this->_getQuotedTable($table['tableName'], $correlationName); + + // Add join conditions (if applicable) + if (!empty($from) && ! empty($table['joinCondition'])) { + $tmp .= ' ' . self::SQL_ON . ' ' . $table['joinCondition']; + } + + // Add the table name and condition add to the list + $from[] = $tmp; + } + + // Add the list of all joins + if (!empty($from)) { + $sql .= ' ' . self::SQL_FROM . ' ' . implode("\n", $from); + } + + return $sql; + } + + /** + * Render UNION query + * + * @param string $sql SQL query + * @return string + */ + protected function _renderUnion($sql) + { + if ($this->_parts[self::UNION]) { + $parts = count($this->_parts[self::UNION]); + foreach ($this->_parts[self::UNION] as $cnt => $union) { + list($target, $type) = $union; + if ($target instanceof Zend_Db_Select) { + $target = $target->assemble(); + } + $sql .= $target; + if ($cnt < $parts - 1) { + $sql .= ' ' . $type . ' '; + } + } + } + + return $sql; + } + + /** + * Render WHERE clause + * + * @param string $sql SQL query + * @return string + */ + protected function _renderWhere($sql) + { + if ($this->_parts[self::FROM] && $this->_parts[self::WHERE]) { + $sql .= ' ' . self::SQL_WHERE . ' ' . implode(' ', $this->_parts[self::WHERE]); + } + + return $sql; + } + + /** + * Render GROUP clause + * + * @param string $sql SQL query + * @return string + */ + protected function _renderGroup($sql) + { + if ($this->_parts[self::FROM] && $this->_parts[self::GROUP]) { + $group = array(); + foreach ($this->_parts[self::GROUP] as $term) { + $group[] = $this->_adapter->quoteIdentifier($term, true); + } + $sql .= ' ' . self::SQL_GROUP_BY . ' ' . implode(",\n\t", $group); + } + + return $sql; + } + + /** + * Render HAVING clause + * + * @param string $sql SQL query + * @return string + */ + protected function _renderHaving($sql) + { + if ($this->_parts[self::FROM] && $this->_parts[self::HAVING]) { + $sql .= ' ' . self::SQL_HAVING . ' ' . implode(' ', $this->_parts[self::HAVING]); + } + + return $sql; + } + + /** + * Render ORDER clause + * + * @param string $sql SQL query + * @return string + */ + protected function _renderOrder($sql) + { + if ($this->_parts[self::ORDER]) { + $order = array(); + foreach ($this->_parts[self::ORDER] as $term) { + if (is_array($term)) { + if(is_numeric($term[0]) && strval(intval($term[0])) == $term[0]) { + $order[] = (int)trim($term[0]) . ' ' . $term[1]; + } else { + $order[] = $this->_adapter->quoteIdentifier($term[0], true) . ' ' . $term[1]; + } + } else if (is_numeric($term) && strval(intval($term)) == $term) { + $order[] = (int)trim($term); + } else { + $order[] = $this->_adapter->quoteIdentifier($term, true); + } + } + $sql .= ' ' . self::SQL_ORDER_BY . ' ' . implode(', ', $order); + } + + return $sql; + } + + /** + * Render LIMIT OFFSET clause + * + * @param string $sql SQL query + * @return string + */ + protected function _renderLimitoffset($sql) + { + $count = 0; + $offset = 0; + + if (!empty($this->_parts[self::LIMIT_OFFSET])) { + $offset = (int) $this->_parts[self::LIMIT_OFFSET]; + $count = PHP_INT_MAX; + } + + if (!empty($this->_parts[self::LIMIT_COUNT])) { + $count = (int) $this->_parts[self::LIMIT_COUNT]; + } + + /* + * Add limits clause + */ + if ($count > 0) { + $sql = trim($this->_adapter->limit($sql, $count, $offset)); + } + + return $sql; + } + + /** + * Render FOR UPDATE clause + * + * @param string $sql SQL query + * @return string + */ + protected function _renderForupdate($sql) + { + if ($this->_parts[self::FOR_UPDATE]) { + $sql .= ' ' . self::SQL_FOR_UPDATE; + } + + return $sql; + } + + /** + * Turn magic function calls into non-magic function calls + * for joinUsing syntax + * + * @param string $method + * @param array $args OPTIONAL Zend_Db_Table_Select query modifier + * @return Zend_Db_Select + * @throws Zend_Db_Select_Exception If an invalid method is called. + */ + public function __call($method, array $args) + { + $matches = array(); + + /** + * Recognize methods for Has-Many cases: + * findParent() + * findParentBy() + * Use the non-greedy pattern repeat modifier e.g. \w+? + */ + if (preg_match('/^join([a-zA-Z]*?)Using$/', $method, $matches)) { + $type = strtolower($matches[1]); + if ($type) { + $type .= ' join'; + if (!in_array($type, self::$_joinTypes)) { + require_once 'Zend/Db/Select/Exception.php'; + throw new Zend_Db_Select_Exception("Unrecognized method '$method()'"); + } + if (in_array($type, array(self::CROSS_JOIN, self::NATURAL_JOIN))) { + require_once 'Zend/Db/Select/Exception.php'; + throw new Zend_Db_Select_Exception("Cannot perform a joinUsing with method '$method()'"); + } + } else { + $type = self::INNER_JOIN; + } + array_unshift($args, $type); + return call_user_func_array(array($this, '_joinUsing'), $args); + } + + require_once 'Zend/Db/Select/Exception.php'; + throw new Zend_Db_Select_Exception("Unrecognized method '$method()'"); + } + + /** + * Implements magic method. + * + * @return string This object as a SELECT string. + */ + public function __toString() + { + try { + $sql = $this->assemble(); + } catch (Exception $e) { + trigger_error($e->getMessage(), E_USER_WARNING); + $sql = ''; + } + return (string)$sql; + } + +} diff --git a/library/Zend/Db/Select/Exception.php b/library/Zend/Db/Select/Exception.php new file mode 100644 index 00000000..89823fbd --- /dev/null +++ b/library/Zend/Db/Select/Exception.php @@ -0,0 +1,39 @@ +_adapter = $adapter; + if ($sql instanceof Zend_Db_Select) { + $sql = $sql->assemble(); + } + $this->_parseParameters($sql); + $this->_prepare($sql); + + $this->_queryId = $this->_adapter->getProfiler()->queryStart($sql); + } + + /** + * Internal method called by abstract statment constructor to setup + * the driver level statement + * + * @return void + */ + protected function _prepare($sql) + { + return; + } + + /** + * @param string $sql + * @return void + */ + protected function _parseParameters($sql) + { + $sql = $this->_stripQuoted($sql); + + // split into text and params + $this->_sqlSplit = preg_split('/(\?|\:[a-zA-Z0-9_]+)/', + $sql, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY); + + // map params + $this->_sqlParam = array(); + foreach ($this->_sqlSplit as $key => $val) { + if ($val == '?') { + if ($this->_adapter->supportsParameters('positional') === false) { + /** + * @see Zend_Db_Statement_Exception + */ + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception("Invalid bind-variable position '$val'"); + } + } else if ($val[0] == ':') { + if ($this->_adapter->supportsParameters('named') === false) { + /** + * @see Zend_Db_Statement_Exception + */ + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception("Invalid bind-variable name '$val'"); + } + } + $this->_sqlParam[] = $val; + } + + // set up for binding + $this->_bindParam = array(); + } + + /** + * Remove parts of a SQL string that contain quoted strings + * of values or identifiers. + * + * @param string $sql + * @return string + */ + protected function _stripQuoted($sql) + { + // get the character for delimited id quotes, + // this is usually " but in MySQL is ` + $d = $this->_adapter->quoteIdentifier('a'); + $d = $d[0]; + + // get the value used as an escaped delimited id quote, + // e.g. \" or "" or \` + $de = $this->_adapter->quoteIdentifier($d); + $de = substr($de, 1, 2); + $de = str_replace('\\', '\\\\', $de); + + // get the character for value quoting + // this should be ' + $q = $this->_adapter->quote('a'); + $q = $q[0]; + + // get the value used as an escaped quote, + // e.g. \' or '' + $qe = $this->_adapter->quote($q); + $qe = substr($qe, 1, 2); + $qe = str_replace('\\', '\\\\', $qe); + + // get a version of the SQL statement with all quoted + // values and delimited identifiers stripped out + // remove "foo\"bar" + $sql = preg_replace("/$q($qe|\\\\{2}|[^$q])*$q/", '', $sql); + // remove 'foo\'bar' + if (!empty($q)) { + $sql = preg_replace("/$q($qe|[^$q])*$q/", '', $sql); + } + + return $sql; + } + + /** + * Bind a column of the statement result set to a PHP variable. + * + * @param string $column Name the column in the result set, either by + * position or by name. + * @param mixed $param Reference to the PHP variable containing the value. + * @param mixed $type OPTIONAL + * @return bool + */ + public function bindColumn($column, &$param, $type = null) + { + $this->_bindColumn[$column] =& $param; + return true; + } + + /** + * Binds a parameter to the specified variable name. + * + * @param mixed $parameter Name the parameter, either integer or string. + * @param mixed $variable Reference to PHP variable containing the value. + * @param mixed $type OPTIONAL Datatype of SQL parameter. + * @param mixed $length OPTIONAL Length of SQL parameter. + * @param mixed $options OPTIONAL Other options. + * @return bool + */ + public function bindParam($parameter, &$variable, $type = null, $length = null, $options = null) + { + if (!is_int($parameter) && !is_string($parameter)) { + /** + * @see Zend_Db_Statement_Exception + */ + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception('Invalid bind-variable position'); + } + + $position = null; + if (($intval = (int) $parameter) > 0 && $this->_adapter->supportsParameters('positional')) { + if ($intval >= 1 || $intval <= count($this->_sqlParam)) { + $position = $intval; + } + } else if ($this->_adapter->supportsParameters('named')) { + if ($parameter[0] != ':') { + $parameter = ':' . $parameter; + } + if (in_array($parameter, $this->_sqlParam) !== false) { + $position = $parameter; + } + } + + if ($position === null) { + /** + * @see Zend_Db_Statement_Exception + */ + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception("Invalid bind-variable position '$parameter'"); + } + + // Finally we are assured that $position is valid + $this->_bindParam[$position] =& $variable; + return $this->_bindParam($position, $variable, $type, $length, $options); + } + + /** + * Binds a value to a parameter. + * + * @param mixed $parameter Name the parameter, either integer or string. + * @param mixed $value Scalar value to bind to the parameter. + * @param mixed $type OPTIONAL Datatype of the parameter. + * @return bool + */ + public function bindValue($parameter, $value, $type = null) + { + return $this->bindParam($parameter, $value, $type); + } + + /** + * Executes a prepared statement. + * + * @param array $params OPTIONAL Values to bind to parameter placeholders. + * @return bool + */ + public function execute(array $params = null) + { + /* + * Simple case - no query profiler to manage. + */ + if ($this->_queryId === null) { + return $this->_execute($params); + } + + /* + * Do the same thing, but with query profiler + * management before and after the execute. + */ + $prof = $this->_adapter->getProfiler(); + $qp = $prof->getQueryProfile($this->_queryId); + if ($qp->hasEnded()) { + $this->_queryId = $prof->queryClone($qp); + $qp = $prof->getQueryProfile($this->_queryId); + } + if ($params !== null) { + $qp->bindParams($params); + } else { + $qp->bindParams($this->_bindParam); + } + $qp->start($this->_queryId); + + $retval = $this->_execute($params); + + $prof->queryEnd($this->_queryId); + + return $retval; + } + + /** + * Returns an array containing all of the result set rows. + * + * @param int $style OPTIONAL Fetch mode. + * @param int $col OPTIONAL Column number, if fetch mode is by column. + * @return array Collection of rows, each in a format by the fetch mode. + */ + public function fetchAll($style = null, $col = null) + { + $data = array(); + if ($style === Zend_Db::FETCH_COLUMN && $col === null) { + $col = 0; + } + if ($col === null) { + while ($row = $this->fetch($style)) { + $data[] = $row; + } + } else { + while (false !== ($val = $this->fetchColumn($col))) { + $data[] = $val; + } + } + return $data; + } + + /** + * Returns a single column from the next row of a result set. + * + * @param int $col OPTIONAL Position of the column to fetch. + * @return string One value from the next row of result set, or false. + */ + public function fetchColumn($col = 0) + { + $data = array(); + $col = (int) $col; + $row = $this->fetch(Zend_Db::FETCH_NUM); + if (!is_array($row)) { + return false; + } + return $row[$col]; + } + + /** + * Fetches the next row and returns it as an object. + * + * @param string $class OPTIONAL Name of the class to create. + * @param array $config OPTIONAL Constructor arguments for the class. + * @return mixed One object instance of the specified class, or false. + */ + public function fetchObject($class = 'stdClass', array $config = array()) + { + $obj = new $class($config); + $row = $this->fetch(Zend_Db::FETCH_ASSOC); + if (!is_array($row)) { + return false; + } + foreach ($row as $key => $val) { + $obj->$key = $val; + } + return $obj; + } + + /** + * Retrieve a statement attribute. + * + * @param string $key Attribute name. + * @return mixed Attribute value. + */ + public function getAttribute($key) + { + if (array_key_exists($key, $this->_attribute)) { + return $this->_attribute[$key]; + } + } + + /** + * Set a statement attribute. + * + * @param string $key Attribute name. + * @param mixed $val Attribute value. + * @return bool + */ + public function setAttribute($key, $val) + { + $this->_attribute[$key] = $val; + } + + /** + * Set the default fetch mode for this statement. + * + * @param int $mode The fetch mode. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function setFetchMode($mode) + { + switch ($mode) { + case Zend_Db::FETCH_NUM: + case Zend_Db::FETCH_ASSOC: + case Zend_Db::FETCH_BOTH: + case Zend_Db::FETCH_OBJ: + $this->_fetchMode = $mode; + break; + case Zend_Db::FETCH_BOUND: + default: + $this->closeCursor(); + /** + * @see Zend_Db_Statement_Exception + */ + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception('invalid fetch mode'); + break; + } + } + + /** + * Helper function to map retrieved row + * to bound column variables + * + * @param array $row + * @return bool True + */ + public function _fetchBound($row) + { + foreach ($row as $key => $value) { + // bindColumn() takes 1-based integer positions + // but fetch() returns 0-based integer indexes + if (is_int($key)) { + $key++; + } + // set results only to variables that were bound previously + if (isset($this->_bindColumn[$key])) { + $this->_bindColumn[$key] = $value; + } + } + return true; + } + + /** + * Gets the Zend_Db_Adapter_Abstract for this + * particular Zend_Db_Statement object. + * + * @return Zend_Db_Adapter_Abstract + */ + public function getAdapter() + { + return $this->_adapter; + } + + /** + * Gets the resource or object setup by the + * _parse + * @return unknown_type + */ + public function getDriverStatement() + { + return $this->_stmt; + } +} diff --git a/library/Zend/Db/Statement/Db2.php b/library/Zend/Db/Statement/Db2.php new file mode 100644 index 00000000..54bd6bba --- /dev/null +++ b/library/Zend/Db/Statement/Db2.php @@ -0,0 +1,360 @@ +_adapter->getConnection(); + + // db2_prepare on i5 emits errors, these need to be + // suppressed so that proper exceptions can be thrown + $this->_stmt = @db2_prepare($connection, $sql); + + if (!$this->_stmt) { + /** + * @see Zend_Db_Statement_Db2_Exception + */ + require_once 'Zend/Db/Statement/Db2/Exception.php'; + throw new Zend_Db_Statement_Db2_Exception( + db2_stmt_errormsg(), + db2_stmt_error() + ); + } + } + + /** + * Binds a parameter to the specified variable name. + * + * @param mixed $parameter Name the parameter, either integer or string. + * @param mixed $variable Reference to PHP variable containing the value. + * @param mixed $type OPTIONAL Datatype of SQL parameter. + * @param mixed $length OPTIONAL Length of SQL parameter. + * @param mixed $options OPTIONAL Other options. + * @return bool + * @throws Zend_Db_Statement_Db2_Exception + */ + public function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null) + { + if ($type === null) { + $type = DB2_PARAM_IN; + } + + if (isset($options['data-type'])) { + $datatype = $options['data-type']; + } else { + $datatype = DB2_CHAR; + } + + if (!db2_bind_param($this->_stmt, $position, "variable", $type, $datatype)) { + /** + * @see Zend_Db_Statement_Db2_Exception + */ + require_once 'Zend/Db/Statement/Db2/Exception.php'; + throw new Zend_Db_Statement_Db2_Exception( + db2_stmt_errormsg(), + db2_stmt_error() + ); + } + + return true; + } + + /** + * Closes the cursor, allowing the statement to be executed again. + * + * @return bool + */ + public function closeCursor() + { + if (!$this->_stmt) { + return false; + } + db2_free_stmt($this->_stmt); + $this->_stmt = false; + return true; + } + + + /** + * Returns the number of columns in the result set. + * Returns null if the statement has no result set metadata. + * + * @return int The number of columns. + */ + public function columnCount() + { + if (!$this->_stmt) { + return false; + } + return db2_num_fields($this->_stmt); + } + + /** + * Retrieves the error code, if any, associated with the last operation on + * the statement handle. + * + * @return string error code. + */ + public function errorCode() + { + if (!$this->_stmt) { + return false; + } + + $error = db2_stmt_error(); + if ($error === '') { + return false; + } + + return $error; + } + + /** + * Retrieves an array of error information, if any, associated with the + * last operation on the statement handle. + * + * @return array + */ + public function errorInfo() + { + $error = $this->errorCode(); + if ($error === false){ + return false; + } + + /* + * Return three-valued array like PDO. But DB2 does not distinguish + * between SQLCODE and native RDBMS error code, so repeat the SQLCODE. + */ + return array( + $error, + $error, + db2_stmt_errormsg() + ); + } + + /** + * Executes a prepared statement. + * + * @param array $params OPTIONAL Values to bind to parameter placeholders. + * @return bool + * @throws Zend_Db_Statement_Db2_Exception + */ + public function _execute(array $params = null) + { + if (!$this->_stmt) { + return false; + } + + $retval = true; + if ($params !== null) { + $retval = @db2_execute($this->_stmt, $params); + } else { + $retval = @db2_execute($this->_stmt); + } + + if ($retval === false) { + /** + * @see Zend_Db_Statement_Db2_Exception + */ + require_once 'Zend/Db/Statement/Db2/Exception.php'; + throw new Zend_Db_Statement_Db2_Exception( + db2_stmt_errormsg(), + db2_stmt_error()); + } + + $this->_keys = array(); + if ($field_num = $this->columnCount()) { + for ($i = 0; $i < $field_num; $i++) { + $name = db2_field_name($this->_stmt, $i); + $this->_keys[] = $name; + } + } + + $this->_values = array(); + if ($this->_keys) { + $this->_values = array_fill(0, count($this->_keys), null); + } + + return $retval; + } + + /** + * Fetches a row from the result set. + * + * @param int $style OPTIONAL Fetch mode for this fetch operation. + * @param int $cursor OPTIONAL Absolute, relative, or other. + * @param int $offset OPTIONAL Number for absolute or relative cursors. + * @return mixed Array, object, or scalar depending on fetch mode. + * @throws Zend_Db_Statement_Db2_Exception + */ + public function fetch($style = null, $cursor = null, $offset = null) + { + if (!$this->_stmt) { + return false; + } + + if ($style === null) { + $style = $this->_fetchMode; + } + + switch ($style) { + case Zend_Db::FETCH_NUM : + $row = db2_fetch_array($this->_stmt); + break; + case Zend_Db::FETCH_ASSOC : + $row = db2_fetch_assoc($this->_stmt); + break; + case Zend_Db::FETCH_BOTH : + $row = db2_fetch_both($this->_stmt); + break; + case Zend_Db::FETCH_OBJ : + $row = db2_fetch_object($this->_stmt); + break; + case Zend_Db::FETCH_BOUND: + $row = db2_fetch_both($this->_stmt); + if ($row !== false) { + return $this->_fetchBound($row); + } + break; + default: + /** + * @see Zend_Db_Statement_Db2_Exception + */ + require_once 'Zend/Db/Statement/Db2/Exception.php'; + throw new Zend_Db_Statement_Db2_Exception("Invalid fetch mode '$style' specified"); + break; + } + + return $row; + } + + /** + * Fetches the next row and returns it as an object. + * + * @param string $class OPTIONAL Name of the class to create. + * @param array $config OPTIONAL Constructor arguments for the class. + * @return mixed One object instance of the specified class. + */ + public function fetchObject($class = 'stdClass', array $config = array()) + { + $obj = $this->fetch(Zend_Db::FETCH_OBJ); + return $obj; + } + + /** + * Retrieves the next rowset (result set) for a SQL statement that has + * multiple result sets. An example is a stored procedure that returns + * the results of multiple queries. + * + * @return bool + * @throws Zend_Db_Statement_Db2_Exception + */ + public function nextRowset() + { + /** + * @see Zend_Db_Statement_Db2_Exception + */ + require_once 'Zend/Db/Statement/Db2/Exception.php'; + throw new Zend_Db_Statement_Db2_Exception(__FUNCTION__ . '() is not implemented'); + } + + /** + * Returns the number of rows affected by the execution of the + * last INSERT, DELETE, or UPDATE statement executed by this + * statement object. + * + * @return int The number of rows affected. + */ + public function rowCount() + { + if (!$this->_stmt) { + return false; + } + + $num = @db2_num_rows($this->_stmt); + + if ($num === false) { + return 0; + } + + return $num; + } + + /** + * Returns an array containing all of the result set rows. + * + * @param int $style OPTIONAL Fetch mode. + * @param int $col OPTIONAL Column number, if fetch mode is by column. + * @return array Collection of rows, each in a format by the fetch mode. + * + * Behaves like parent, but if limit() + * is used, the final result removes the extra column + * 'zend_db_rownum' + */ + public function fetchAll($style = null, $col = null) + { + $data = parent::fetchAll($style, $col); + $results = array(); + $remove = $this->_adapter->foldCase('ZEND_DB_ROWNUM'); + + foreach ($data as $row) { + if (is_array($row) && array_key_exists($remove, $row)) { + unset($row[$remove]); + } + $results[] = $row; + } + return $results; + } +} diff --git a/library/Zend/Db/Statement/Db2/Exception.php b/library/Zend/Db/Statement/Db2/Exception.php new file mode 100644 index 00000000..1b0c372c --- /dev/null +++ b/library/Zend/Db/Statement/Db2/Exception.php @@ -0,0 +1,58 @@ +message = $msg; + $this->code = $state; + } + +} + diff --git a/library/Zend/Db/Statement/Exception.php b/library/Zend/Db/Statement/Exception.php new file mode 100644 index 00000000..ff49a08d --- /dev/null +++ b/library/Zend/Db/Statement/Exception.php @@ -0,0 +1,56 @@ +getPrevious() !== null); + } + + /** + * @return Exception|null + */ + public function getChainedException() + { + return $this->getPrevious(); + } +} diff --git a/library/Zend/Db/Statement/Interface.php b/library/Zend/Db/Statement/Interface.php new file mode 100644 index 00000000..8ae38c37 --- /dev/null +++ b/library/Zend/Db/Statement/Interface.php @@ -0,0 +1,203 @@ +_adapter->getConnection(); + + $this->_stmt = $mysqli->prepare($sql); + + if ($this->_stmt === false || $mysqli->errno) { + /** + * @see Zend_Db_Statement_Mysqli_Exception + */ + require_once 'Zend/Db/Statement/Mysqli/Exception.php'; + throw new Zend_Db_Statement_Mysqli_Exception("Mysqli prepare error: " . $mysqli->error, $mysqli->errno); + } + } + + /** + * Binds a parameter to the specified variable name. + * + * @param mixed $parameter Name the parameter, either integer or string. + * @param mixed $variable Reference to PHP variable containing the value. + * @param mixed $type OPTIONAL Datatype of SQL parameter. + * @param mixed $length OPTIONAL Length of SQL parameter. + * @param mixed $options OPTIONAL Other options. + * @return bool + * @throws Zend_Db_Statement_Mysqli_Exception + */ + protected function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null) + { + return true; + } + + /** + * Closes the cursor and the statement. + * + * @return bool + */ + public function close() + { + if ($this->_stmt) { + $r = $this->_stmt->close(); + $this->_stmt = null; + return $r; + } + return false; + } + + /** + * Closes the cursor, allowing the statement to be executed again. + * + * @return bool + */ + public function closeCursor() + { + if ($stmt = $this->_stmt) { + $mysqli = $this->_adapter->getConnection(); + while ($mysqli->more_results()) { + $mysqli->next_result(); + } + $this->_stmt->free_result(); + return $this->_stmt->reset(); + } + return false; + } + + /** + * Returns the number of columns in the result set. + * Returns null if the statement has no result set metadata. + * + * @return int The number of columns. + */ + public function columnCount() + { + if (isset($this->_meta) && $this->_meta) { + return $this->_meta->field_count; + } + return 0; + } + + /** + * Retrieves the error code, if any, associated with the last operation on + * the statement handle. + * + * @return string error code. + */ + public function errorCode() + { + if (!$this->_stmt) { + return false; + } + return substr($this->_stmt->sqlstate, 0, 5); + } + + /** + * Retrieves an array of error information, if any, associated with the + * last operation on the statement handle. + * + * @return array + */ + public function errorInfo() + { + if (!$this->_stmt) { + return false; + } + return array( + substr($this->_stmt->sqlstate, 0, 5), + $this->_stmt->errno, + $this->_stmt->error, + ); + } + + /** + * Executes a prepared statement. + * + * @param array $params OPTIONAL Values to bind to parameter placeholders. + * @return bool + * @throws Zend_Db_Statement_Mysqli_Exception + */ + public function _execute(array $params = null) + { + if (!$this->_stmt) { + return false; + } + + // if no params were given as an argument to execute(), + // then default to the _bindParam array + if ($params === null) { + $params = $this->_bindParam; + } + // send $params as input parameters to the statement + if ($params) { + array_unshift($params, str_repeat('s', count($params))); + $stmtParams = array(); + foreach ($params as $k => &$value) { + $stmtParams[$k] = &$value; + } + call_user_func_array( + array($this->_stmt, 'bind_param'), + $stmtParams + ); + } + + // execute the statement + $retval = $this->_stmt->execute(); + if ($retval === false) { + /** + * @see Zend_Db_Statement_Mysqli_Exception + */ + require_once 'Zend/Db/Statement/Mysqli/Exception.php'; + throw new Zend_Db_Statement_Mysqli_Exception("Mysqli statement execute error : " . $this->_stmt->error, $this->_stmt->errno); + } + + + // retain metadata + if ($this->_meta === null) { + $this->_meta = $this->_stmt->result_metadata(); + if ($this->_stmt->errno) { + /** + * @see Zend_Db_Statement_Mysqli_Exception + */ + require_once 'Zend/Db/Statement/Mysqli/Exception.php'; + throw new Zend_Db_Statement_Mysqli_Exception("Mysqli statement metadata error: " . $this->_stmt->error, $this->_stmt->errno); + } + } + + // statements that have no result set do not return metadata + if ($this->_meta !== false) { + + // get the column names that will result + $this->_keys = array(); + foreach ($this->_meta->fetch_fields() as $col) { + $this->_keys[] = $this->_adapter->foldCase($col->name); + } + + // set up a binding space for result variables + $this->_values = array_fill(0, count($this->_keys), null); + + // set up references to the result binding space. + // just passing $this->_values in the call_user_func_array() + // below won't work, you need references. + $refs = array(); + foreach ($this->_values as $i => &$f) { + $refs[$i] = &$f; + } + + $this->_stmt->store_result(); + // bind to the result variables + call_user_func_array( + array($this->_stmt, 'bind_result'), + $this->_values + ); + } + return $retval; + } + + + /** + * Fetches a row from the result set. + * + * @param int $style OPTIONAL Fetch mode for this fetch operation. + * @param int $cursor OPTIONAL Absolute, relative, or other. + * @param int $offset OPTIONAL Number for absolute or relative cursors. + * @return mixed Array, object, or scalar depending on fetch mode. + * @throws Zend_Db_Statement_Mysqli_Exception + */ + public function fetch($style = null, $cursor = null, $offset = null) + { + if (!$this->_stmt) { + return false; + } + // fetch the next result + $retval = $this->_stmt->fetch(); + switch ($retval) { + case null: // end of data + case false: // error occurred + $this->_stmt->reset(); + return false; + default: + // fallthrough + } + + // make sure we have a fetch mode + if ($style === null) { + $style = $this->_fetchMode; + } + + // dereference the result values, otherwise things like fetchAll() + // return the same values for every entry (because of the reference). + $values = array(); + foreach ($this->_values as $key => $val) { + $values[] = $val; + } + + $row = false; + switch ($style) { + case Zend_Db::FETCH_NUM: + $row = $values; + break; + case Zend_Db::FETCH_ASSOC: + $row = array_combine($this->_keys, $values); + break; + case Zend_Db::FETCH_BOTH: + $assoc = array_combine($this->_keys, $values); + $row = array_merge($values, $assoc); + break; + case Zend_Db::FETCH_OBJ: + $row = (object) array_combine($this->_keys, $values); + break; + case Zend_Db::FETCH_BOUND: + $assoc = array_combine($this->_keys, $values); + $row = array_merge($values, $assoc); + return $this->_fetchBound($row); + break; + default: + /** + * @see Zend_Db_Statement_Mysqli_Exception + */ + require_once 'Zend/Db/Statement/Mysqli/Exception.php'; + throw new Zend_Db_Statement_Mysqli_Exception("Invalid fetch mode '$style' specified"); + break; + } + return $row; + } + + /** + * Retrieves the next rowset (result set) for a SQL statement that has + * multiple result sets. An example is a stored procedure that returns + * the results of multiple queries. + * + * @return bool + * @throws Zend_Db_Statement_Mysqli_Exception + */ + public function nextRowset() + { + /** + * @see Zend_Db_Statement_Mysqli_Exception + */ + require_once 'Zend/Db/Statement/Mysqli/Exception.php'; + throw new Zend_Db_Statement_Mysqli_Exception(__FUNCTION__.'() is not implemented'); + } + + /** + * Returns the number of rows affected by the execution of the + * last INSERT, DELETE, or UPDATE statement executed by this + * statement object. + * + * @return int The number of rows affected. + */ + public function rowCount() + { + if (!$this->_adapter) { + return false; + } + $mysqli = $this->_adapter->getConnection(); + return $mysqli->affected_rows; + } + +} \ No newline at end of file diff --git a/library/Zend/Db/Statement/Mysqli/Exception.php b/library/Zend/Db/Statement/Mysqli/Exception.php new file mode 100644 index 00000000..2fb13b74 --- /dev/null +++ b/library/Zend/Db/Statement/Mysqli/Exception.php @@ -0,0 +1,38 @@ +_lobAsString = (bool) $lob_as_string; + return $this; + } + + /** + * Return whether or not LOB are returned as string + * + * @return boolean + */ + public function getLobAsString() + { + return $this->_lobAsString; + } + + /** + * Prepares statement handle + * + * @param string $sql + * @return void + * @throws Zend_Db_Statement_Oracle_Exception + */ + protected function _prepare($sql) + { + $connection = $this->_adapter->getConnection(); + $this->_stmt = @oci_parse($connection, $sql); + if (!$this->_stmt) { + /** + * @see Zend_Db_Statement_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception(oci_error($connection)); + } + } + + /** + * Binds a parameter to the specified variable name. + * + * @param mixed $parameter Name the parameter, either integer or string. + * @param mixed $variable Reference to PHP variable containing the value. + * @param mixed $type OPTIONAL Datatype of SQL parameter. + * @param mixed $length OPTIONAL Length of SQL parameter. + * @param mixed $options OPTIONAL Other options. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + protected function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null) + { + // default value + if ($type === NULL) { + $type = SQLT_CHR; + } + + // default value + if ($length === NULL) { + $length = -1; + } + + $retval = @oci_bind_by_name($this->_stmt, $parameter, $variable, $length, $type); + if ($retval === false) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception(oci_error($this->_stmt)); + } + + return true; + } + + /** + * Closes the cursor, allowing the statement to be executed again. + * + * @return bool + */ + public function closeCursor() + { + if (!$this->_stmt) { + return false; + } + + oci_free_statement($this->_stmt); + $this->_stmt = false; + return true; + } + + /** + * Returns the number of columns in the result set. + * Returns null if the statement has no result set metadata. + * + * @return int The number of columns. + */ + public function columnCount() + { + if (!$this->_stmt) { + return false; + } + + return oci_num_fields($this->_stmt); + } + + + /** + * Retrieves the error code, if any, associated with the last operation on + * the statement handle. + * + * @return string error code. + */ + public function errorCode() + { + if (!$this->_stmt) { + return false; + } + + $error = oci_error($this->_stmt); + + if (!$error) { + return false; + } + + return $error['code']; + } + + + /** + * Retrieves an array of error information, if any, associated with the + * last operation on the statement handle. + * + * @return array + */ + public function errorInfo() + { + if (!$this->_stmt) { + return false; + } + + $error = oci_error($this->_stmt); + if (!$error) { + return false; + } + + if (isset($error['sqltext'])) { + return array( + $error['code'], + $error['message'], + $error['offset'], + $error['sqltext'], + ); + } else { + return array( + $error['code'], + $error['message'], + ); + } + } + + + /** + * Executes a prepared statement. + * + * @param array $params OPTIONAL Values to bind to parameter placeholders. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function _execute(array $params = null) + { + $connection = $this->_adapter->getConnection(); + + if (!$this->_stmt) { + return false; + } + + if ($params !== null) { + if (!is_array($params)) { + $params = array($params); + } + $error = false; + foreach (array_keys($params) as $name) { + if (!@oci_bind_by_name($this->_stmt, $name, $params[$name], -1)) { + $error = true; + break; + } + } + if ($error) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception(oci_error($this->_stmt)); + } + } + + $retval = @oci_execute($this->_stmt, $this->_adapter->_getExecuteMode()); + if ($retval === false) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception(oci_error($this->_stmt)); + } + + $this->_keys = Array(); + if ($field_num = oci_num_fields($this->_stmt)) { + for ($i = 1; $i <= $field_num; $i++) { + $name = oci_field_name($this->_stmt, $i); + $this->_keys[] = $name; + } + } + + $this->_values = Array(); + if ($this->_keys) { + $this->_values = array_fill(0, count($this->_keys), null); + } + + return $retval; + } + + /** + * Fetches a row from the result set. + * + * @param int $style OPTIONAL Fetch mode for this fetch operation. + * @param int $cursor OPTIONAL Absolute, relative, or other. + * @param int $offset OPTIONAL Number for absolute or relative cursors. + * @return mixed Array, object, or scalar depending on fetch mode. + * @throws Zend_Db_Statement_Exception + */ + public function fetch($style = null, $cursor = null, $offset = null) + { + if (!$this->_stmt) { + return false; + } + + if ($style === null) { + $style = $this->_fetchMode; + } + + $lob_as_string = $this->getLobAsString() ? OCI_RETURN_LOBS : 0; + + switch ($style) { + case Zend_Db::FETCH_NUM: + $row = oci_fetch_array($this->_stmt, OCI_NUM | OCI_RETURN_NULLS | $lob_as_string); + break; + case Zend_Db::FETCH_ASSOC: + $row = oci_fetch_array($this->_stmt, OCI_ASSOC | OCI_RETURN_NULLS | $lob_as_string); + break; + case Zend_Db::FETCH_BOTH: + $row = oci_fetch_array($this->_stmt, OCI_BOTH | OCI_RETURN_NULLS | $lob_as_string); + break; + case Zend_Db::FETCH_OBJ: + $row = oci_fetch_object($this->_stmt); + break; + case Zend_Db::FETCH_BOUND: + $row = oci_fetch_array($this->_stmt, OCI_BOTH | OCI_RETURN_NULLS | $lob_as_string); + if ($row !== false) { + return $this->_fetchBound($row); + } + break; + default: + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception( + array( + 'code' => 'HYC00', + 'message' => "Invalid fetch mode '$style' specified" + ) + ); + break; + } + + if (! $row && $error = oci_error($this->_stmt)) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception($error); + } + + if (is_array($row) && array_key_exists('zend_db_rownum', $row)) { + unset($row['zend_db_rownum']); + } + + return $row; + } + + /** + * Returns an array containing all of the result set rows. + * + * @param int $style OPTIONAL Fetch mode. + * @param int $col OPTIONAL Column number, if fetch mode is by column. + * @return array Collection of rows, each in a format by the fetch mode. + * @throws Zend_Db_Statement_Exception + */ + public function fetchAll($style = null, $col = 0) + { + if (!$this->_stmt) { + return false; + } + + // make sure we have a fetch mode + if ($style === null) { + $style = $this->_fetchMode; + } + + $flags = OCI_FETCHSTATEMENT_BY_ROW; + + switch ($style) { + case Zend_Db::FETCH_BOTH: + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception( + array( + 'code' => 'HYC00', + 'message' => "OCI8 driver does not support fetchAll(FETCH_BOTH), use fetch() in a loop instead" + ) + ); + // notreached + $flags |= OCI_NUM; + $flags |= OCI_ASSOC; + break; + case Zend_Db::FETCH_NUM: + $flags |= OCI_NUM; + break; + case Zend_Db::FETCH_ASSOC: + $flags |= OCI_ASSOC; + break; + case Zend_Db::FETCH_OBJ: + break; + case Zend_Db::FETCH_COLUMN: + $flags = $flags &~ OCI_FETCHSTATEMENT_BY_ROW; + $flags |= OCI_FETCHSTATEMENT_BY_COLUMN; + $flags |= OCI_NUM; + break; + default: + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception( + array( + 'code' => 'HYC00', + 'message' => "Invalid fetch mode '$style' specified" + ) + ); + break; + } + + $result = Array(); + if ($flags != OCI_FETCHSTATEMENT_BY_ROW) { /* not Zend_Db::FETCH_OBJ */ + if (! ($rows = oci_fetch_all($this->_stmt, $result, 0, -1, $flags) )) { + if ($error = oci_error($this->_stmt)) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception($error); + } + if (!$rows) { + return array(); + } + } + if ($style == Zend_Db::FETCH_COLUMN) { + $result = $result[$col]; + } + foreach ($result as &$row) { + if (is_array($row) && array_key_exists('zend_db_rownum', $row)) { + unset($row['zend_db_rownum']); + } + } + } else { + while (($row = oci_fetch_object($this->_stmt)) !== false) { + $result [] = $row; + } + if ($error = oci_error($this->_stmt)) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception($error); + } + } + + return $result; + } + + + /** + * Returns a single column from the next row of a result set. + * + * @param int $col OPTIONAL Position of the column to fetch. + * @return string + * @throws Zend_Db_Statement_Exception + */ + public function fetchColumn($col = 0) + { + if (!$this->_stmt) { + return false; + } + + if (!oci_fetch($this->_stmt)) { + // if no error, there is simply no record + if (!$error = oci_error($this->_stmt)) { + return false; + } + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception($error); + } + + $data = oci_result($this->_stmt, $col+1); //1-based + if ($data === false) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception(oci_error($this->_stmt)); + } + + if ($this->getLobAsString()) { + // instanceof doesn't allow '-', we must use a temporary string + $type = 'OCI-Lob'; + if ($data instanceof $type) { + $data = $data->read($data->size()); + } + } + + return $data; + } + + /** + * Fetches the next row and returns it as an object. + * + * @param string $class OPTIONAL Name of the class to create. + * @param array $config OPTIONAL Constructor arguments for the class. + * @return mixed One object instance of the specified class. + * @throws Zend_Db_Statement_Exception + */ + public function fetchObject($class = 'stdClass', array $config = array()) + { + if (!$this->_stmt) { + return false; + } + + $obj = oci_fetch_object($this->_stmt); + + if ($error = oci_error($this->_stmt)) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception($error); + } + + /* @todo XXX handle parameters */ + + return $obj; + } + + /** + * Retrieves the next rowset (result set) for a SQL statement that has + * multiple result sets. An example is a stored procedure that returns + * the results of multiple queries. + * + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function nextRowset() + { + /** + * @see Zend_Db_Statement_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception( + array( + 'code' => 'HYC00', + 'message' => 'Optional feature not implemented' + ) + ); + } + + /** + * Returns the number of rows affected by the execution of the + * last INSERT, DELETE, or UPDATE statement executed by this + * statement object. + * + * @return int The number of rows affected. + * @throws Zend_Db_Statement_Exception + */ + public function rowCount() + { + if (!$this->_stmt) { + return false; + } + + $num_rows = oci_num_rows($this->_stmt); + + if ($num_rows === false) { + /** + * @see Zend_Db_Adapter_Oracle_Exception + */ + require_once 'Zend/Db/Statement/Oracle/Exception.php'; + throw new Zend_Db_Statement_Oracle_Exception(oci_error($this->_stmt)); + } + + return $num_rows; + } + +} diff --git a/library/Zend/Db/Statement/Oracle/Exception.php b/library/Zend/Db/Statement/Oracle/Exception.php new file mode 100644 index 00000000..26a610bf --- /dev/null +++ b/library/Zend/Db/Statement/Oracle/Exception.php @@ -0,0 +1,59 @@ +message = $error['code']." ".$error['message']; + } else { + $this->message = $error['code']." ".$error['message']." "; + $this->message .= substr($error['sqltext'], 0, $error['offset']); + $this->message .= "*"; + $this->message .= substr($error['sqltext'], $error['offset']); + } + $this->code = $error['code']; + } + if (!$this->code && $code) { + $this->code = $code; + } + } +} + diff --git a/library/Zend/Db/Statement/Pdo.php b/library/Zend/Db/Statement/Pdo.php new file mode 100644 index 00000000..7db7e014 --- /dev/null +++ b/library/Zend/Db/Statement/Pdo.php @@ -0,0 +1,439 @@ +_stmt = $this->_adapter->getConnection()->prepare($sql); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Bind a column of the statement result set to a PHP variable. + * + * @param string $column Name the column in the result set, either by + * position or by name. + * @param mixed $param Reference to the PHP variable containing the value. + * @param mixed $type OPTIONAL + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function bindColumn($column, &$param, $type = null) + { + try { + if ($type === null) { + return $this->_stmt->bindColumn($column, $param); + } else { + return $this->_stmt->bindColumn($column, $param, $type); + } + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Binds a parameter to the specified variable name. + * + * @param mixed $parameter Name the parameter, either integer or string. + * @param mixed $variable Reference to PHP variable containing the value. + * @param mixed $type OPTIONAL Datatype of SQL parameter. + * @param mixed $length OPTIONAL Length of SQL parameter. + * @param mixed $options OPTIONAL Other options. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + protected function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null) + { + try { + if ($type === null) { + if (is_bool($variable)) { + $type = PDO::PARAM_BOOL; + } elseif ($variable === null) { + $type = PDO::PARAM_NULL; + } elseif (is_integer($variable)) { + $type = PDO::PARAM_INT; + } else { + $type = PDO::PARAM_STR; + } + } + return $this->_stmt->bindParam($parameter, $variable, $type, $length, $options); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Binds a value to a parameter. + * + * @param mixed $parameter Name the parameter, either integer or string. + * @param mixed $value Scalar value to bind to the parameter. + * @param mixed $type OPTIONAL Datatype of the parameter. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function bindValue($parameter, $value, $type = null) + { + if (is_string($parameter) && $parameter[0] != ':') { + $parameter = ":$parameter"; + } + + $this->_bindParam[$parameter] = $value; + + try { + if ($type === null) { + return $this->_stmt->bindValue($parameter, $value); + } else { + return $this->_stmt->bindValue($parameter, $value, $type); + } + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Closes the cursor, allowing the statement to be executed again. + * + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function closeCursor() + { + try { + return $this->_stmt->closeCursor(); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Returns the number of columns in the result set. + * Returns null if the statement has no result set metadata. + * + * @return int The number of columns. + * @throws Zend_Db_Statement_Exception + */ + public function columnCount() + { + try { + return $this->_stmt->columnCount(); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Retrieves the error code, if any, associated with the last operation on + * the statement handle. + * + * @return string error code. + * @throws Zend_Db_Statement_Exception + */ + public function errorCode() + { + try { + return $this->_stmt->errorCode(); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Retrieves an array of error information, if any, associated with the + * last operation on the statement handle. + * + * @return array + * @throws Zend_Db_Statement_Exception + */ + public function errorInfo() + { + try { + return $this->_stmt->errorInfo(); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Executes a prepared statement. + * + * @param array $params OPTIONAL Values to bind to parameter placeholders. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function _execute(array $params = null) + { + try { + if ($params !== null) { + return $this->_stmt->execute($params); + } else { + return $this->_stmt->execute(); + } + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), (int) $e->getCode(), $e); + } + } + + /** + * Fetches a row from the result set. + * + * @param int $style OPTIONAL Fetch mode for this fetch operation. + * @param int $cursor OPTIONAL Absolute, relative, or other. + * @param int $offset OPTIONAL Number for absolute or relative cursors. + * @return mixed Array, object, or scalar depending on fetch mode. + * @throws Zend_Db_Statement_Exception + */ + public function fetch($style = null, $cursor = null, $offset = null) + { + if ($style === null) { + $style = $this->_fetchMode; + } + try { + return $this->_stmt->fetch($style, $cursor, $offset); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Required by IteratorAggregate interface + * + * @return IteratorIterator + */ + public function getIterator() + { + return new IteratorIterator($this->_stmt); + } + + /** + * Returns an array containing all of the result set rows. + * + * @param int $style OPTIONAL Fetch mode. + * @param int $col OPTIONAL Column number, if fetch mode is by column. + * @return array Collection of rows, each in a format by the fetch mode. + * @throws Zend_Db_Statement_Exception + */ + public function fetchAll($style = null, $col = null) + { + if ($style === null) { + $style = $this->_fetchMode; + } + try { + if ($style == PDO::FETCH_COLUMN) { + if ($col === null) { + $col = 0; + } + return $this->_stmt->fetchAll($style, $col); + } else { + return $this->_stmt->fetchAll($style); + } + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Returns a single column from the next row of a result set. + * + * @param int $col OPTIONAL Position of the column to fetch. + * @return string + * @throws Zend_Db_Statement_Exception + */ + public function fetchColumn($col = 0) + { + try { + return $this->_stmt->fetchColumn($col); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Fetches the next row and returns it as an object. + * + * @param string $class OPTIONAL Name of the class to create. + * @param array $config OPTIONAL Constructor arguments for the class. + * @return mixed One object instance of the specified class. + * @throws Zend_Db_Statement_Exception + */ + public function fetchObject($class = 'stdClass', array $config = array()) + { + try { + return $this->_stmt->fetchObject($class, $config); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Retrieve a statement attribute. + * + * @param integer $key Attribute name. + * @return mixed Attribute value. + * @throws Zend_Db_Statement_Exception + */ + public function getAttribute($key) + { + try { + return $this->_stmt->getAttribute($key); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Returns metadata for a column in a result set. + * + * @param int $column + * @return mixed + * @throws Zend_Db_Statement_Exception + */ + public function getColumnMeta($column) + { + try { + return $this->_stmt->getColumnMeta($column); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Retrieves the next rowset (result set) for a SQL statement that has + * multiple result sets. An example is a stored procedure that returns + * the results of multiple queries. + * + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function nextRowset() + { + try { + return $this->_stmt->nextRowset(); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Returns the number of rows affected by the execution of the + * last INSERT, DELETE, or UPDATE statement executed by this + * statement object. + * + * @return int The number of rows affected. + * @throws Zend_Db_Statement_Exception + */ + public function rowCount() + { + try { + return $this->_stmt->rowCount(); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Set a statement attribute. + * + * @param string $key Attribute name. + * @param mixed $val Attribute value. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function setAttribute($key, $val) + { + try { + return $this->_stmt->setAttribute($key, $val); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Set the default fetch mode for this statement. + * + * @param int $mode The fetch mode. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function setFetchMode($mode) + { + $this->_fetchMode = $mode; + try { + return $this->_stmt->setFetchMode($mode); + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + +} diff --git a/library/Zend/Db/Statement/Pdo/Ibm.php b/library/Zend/Db/Statement/Pdo/Ibm.php new file mode 100644 index 00000000..52f3ef27 --- /dev/null +++ b/library/Zend/Db/Statement/Pdo/Ibm.php @@ -0,0 +1,94 @@ +_adapter->foldCase('ZEND_DB_ROWNUM'); + + foreach ($data as $row) { + if (is_array($row) && array_key_exists($remove, $row)) { + unset($row[$remove]); + } + $results[] = $row; + } + return $results; + } + + /** + * Binds a parameter to the specified variable name. + * + * @param mixed $parameter Name the parameter, either integer or string. + * @param mixed $variable Reference to PHP variable containing the value. + * @param mixed $type OPTIONAL Datatype of SQL parameter. + * @param mixed $length OPTIONAL Length of SQL parameter. + * @param mixed $options OPTIONAL Other options. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null) + { + try { + if (($type === null) && ($length === null) && ($options === null)) { + return $this->_stmt->bindParam($parameter, $variable); + } else { + return $this->_stmt->bindParam($parameter, $variable, $type, $length, $options); + } + } catch (PDOException $e) { + require_once 'Zend/Db/Statement/Exception.php'; + throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e); + } + } + +} \ No newline at end of file diff --git a/library/Zend/Db/Statement/Pdo/Oci.php b/library/Zend/Db/Statement/Pdo/Oci.php new file mode 100644 index 00000000..90658b2f --- /dev/null +++ b/library/Zend/Db/Statement/Pdo/Oci.php @@ -0,0 +1,91 @@ +_adapter->foldCase('zend_db_rownum'); + + foreach ($data as $row) { + if (is_array($row) && array_key_exists($remove, $row)) { + unset($row[$remove]); + } + $results[] = $row; + } + return $results; + } + + + /** + * Fetches a row from the result set. + * + * @param int $style OPTIONAL Fetch mode for this fetch operation. + * @param int $cursor OPTIONAL Absolute, relative, or other. + * @param int $offset OPTIONAL Number for absolute or relative cursors. + * @return mixed Array, object, or scalar depending on fetch mode. + * @throws Zend_Db_Statement_Exception + */ + public function fetch($style = null, $cursor = null, $offset = null) + { + $row = parent::fetch($style, $cursor, $offset); + + $remove = $this->_adapter->foldCase('zend_db_rownum'); + if (is_array($row) && array_key_exists($remove, $row)) { + unset($row[$remove]); + } + + return $row; + } +} \ No newline at end of file diff --git a/library/Zend/Db/Statement/Sqlsrv.php b/library/Zend/Db/Statement/Sqlsrv.php new file mode 100644 index 00000000..8aaaa9dd --- /dev/null +++ b/library/Zend/Db/Statement/Sqlsrv.php @@ -0,0 +1,440 @@ +_adapter->getConnection(); + + $this->_stmt = sqlsrv_prepare($connection, $sql); + + if (!$this->_stmt) { + require_once 'Zend/Db/Statement/Sqlsrv/Exception.php'; + throw new Zend_Db_Statement_Sqlsrv_Exception(sqlsrv_errors()); + } + + $this->_originalSQL = $sql; + } + + /** + * Binds a parameter to the specified variable name. + * + * @param mixed $parameter Name the parameter, either integer or string. + * @param mixed $variable Reference to PHP variable containing the value. + * @param mixed $type OPTIONAL Datatype of SQL parameter. + * @param mixed $length OPTIONAL Length of SQL parameter. + * @param mixed $options OPTIONAL Other options. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + protected function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null) + { + //Sql server doesn't support bind by name + return true; + } + + /** + * Closes the cursor, allowing the statement to be executed again. + * + * @return bool + */ + public function closeCursor() + { + if (!$this->_stmt) { + return false; + } + + sqlsrv_free_stmt($this->_stmt); + $this->_stmt = false; + return true; + } + + /** + * Returns the number of columns in the result set. + * Returns null if the statement has no result set metadata. + * + * @return int The number of columns. + */ + public function columnCount() + { + if ($this->_stmt && $this->_executed) { + return sqlsrv_num_fields($this->_stmt); + } + + return 0; + } + + + /** + * Retrieves the error code, if any, associated with the last operation on + * the statement handle. + * + * @return string error code. + */ + public function errorCode() + { + if (!$this->_stmt) { + return false; + } + + $error = sqlsrv_errors(); + if (!$error) { + return false; + } + + return $error[0]['code']; + } + + + /** + * Retrieves an array of error information, if any, associated with the + * last operation on the statement handle. + * + * @return array + */ + public function errorInfo() + { + if (!$this->_stmt) { + return false; + } + + $error = sqlsrv_errors(); + if (!$error) { + return false; + } + + return array( + $error[0]['code'], + $error[0]['message'], + ); + } + + + /** + * Executes a prepared statement. + * + * @param array $params OPTIONAL Values to bind to parameter placeholders. + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function _execute(array $params = null) + { + $connection = $this->_adapter->getConnection(); + if (!$this->_stmt) { + return false; + } + + if ($params !== null) { + if (!is_array($params)) { + $params = array($params); + } + $error = false; + + // make all params passed by reference + $params_ = array(); + $temp = array(); + $i = 1; + foreach ($params as $param) { + $temp[$i] = $param; + $params_[] = &$temp[$i]; + $i++; + } + $params = $params_; + } + + $this->_stmt = sqlsrv_query($connection, $this->_originalSQL, $params); + + if (!$this->_stmt) { + require_once 'Zend/Db/Statement/Sqlsrv/Exception.php'; + throw new Zend_Db_Statement_Sqlsrv_Exception(sqlsrv_errors()); + } + + $this->_executed = true; + + return (!$this->_stmt); + } + + /** + * Fetches a row from the result set. + * + * @param int $style OPTIONAL Fetch mode for this fetch operation. + * @param int $cursor OPTIONAL Absolute, relative, or other. + * @param int $offset OPTIONAL Number for absolute or relative cursors. + * @return mixed Array, object, or scalar depending on fetch mode. + * @throws Zend_Db_Statement_Exception + */ + public function fetch($style = null, $cursor = null, $offset = null) + { + if (!$this->_stmt) { + return false; + } + + if (null === $style) { + $style = $this->_fetchMode; + } + + $values = sqlsrv_fetch_array($this->_stmt, SQLSRV_FETCH_ASSOC); + + if (!$values && (null !== $error = sqlsrv_errors())) { + require_once 'Zend/Db/Statement/Sqlsrv/Exception.php'; + throw new Zend_Db_Statement_Sqlsrv_Exception($error); + } + + if (null === $values) { + return null; + } + + if (!$this->_keys) { + foreach ($values as $key => $value) { + $this->_keys[] = $this->_adapter->foldCase($key); + } + } + + $values = array_values($values); + + $row = false; + switch ($style) { + case Zend_Db::FETCH_NUM: + $row = $values; + break; + case Zend_Db::FETCH_ASSOC: + $row = array_combine($this->_keys, $values); + break; + case Zend_Db::FETCH_BOTH: + $assoc = array_combine($this->_keys, $values); + $row = array_merge($values, $assoc); + break; + case Zend_Db::FETCH_OBJ: + $row = (object) array_combine($this->_keys, $values); + break; + case Zend_Db::FETCH_BOUND: + $assoc = array_combine($this->_keys, $values); + $row = array_merge($values, $assoc); + $row = $this->_fetchBound($row); + break; + default: + require_once 'Zend/Db/Statement/Sqlsrv/Exception.php'; + throw new Zend_Db_Statement_Sqlsrv_Exception("Invalid fetch mode '$style' specified"); + break; + } + + return $row; + } + + /** + * Returns a single column from the next row of a result set. + * + * @param int $col OPTIONAL Position of the column to fetch. + * @return string + * @throws Zend_Db_Statement_Exception + */ + public function fetchColumn($col = 0) + { + if (!$this->_stmt) { + return false; + } + + if (!sqlsrv_fetch($this->_stmt)) { + if (null !== $error = sqlsrv_errors()) { + require_once 'Zend/Db/Statement/Sqlsrv/Exception.php'; + throw new Zend_Db_Statement_Sqlsrv_Exception($error); + } + + // If no error, there is simply no record + return false; + } + + $data = sqlsrv_get_field($this->_stmt, $col); //0-based + if ($data === false) { + require_once 'Zend/Db/Statement/Sqlsrv/Exception.php'; + throw new Zend_Db_Statement_Sqlsrv_Exception(sqlsrv_errors()); + } + + return $data; + } + + /** + * Fetches the next row and returns it as an object. + * + * @param string $class OPTIONAL Name of the class to create. + * @param array $config OPTIONAL Constructor arguments for the class. + * @return mixed One object instance of the specified class. + * @throws Zend_Db_Statement_Exception + */ + public function fetchObject($class = 'stdClass', array $config = array()) + { + if (!$this->_stmt) { + return false; + } + + $obj = sqlsrv_fetch_object($this->_stmt); + + if ($error = sqlsrv_errors()) { + require_once 'Zend/Db/Statement/Sqlsrv/Exception.php'; + throw new Zend_Db_Statement_Sqlsrv_Exception($error); + } + + /* @todo XXX handle parameters */ + + if (null === $obj) { + return false; + } + + return $obj; + } + + /** + * Returns metadata for a column in a result set. + * + * @param int $column + * @return mixed + * @throws Zend_Db_Statement_Sqlsrv_Exception + */ + public function getColumnMeta($column) + { + $fields = sqlsrv_field_metadata($this->_stmt); + + if (!$fields) { + throw new Zend_Db_Statement_Sqlsrv_Exception('Column metadata can not be fetched'); + } + + if (!isset($fields[$column])) { + throw new Zend_Db_Statement_Sqlsrv_Exception('Column index does not exist in statement'); + } + + return $fields[$column]; + } + + /** + * Retrieves the next rowset (result set) for a SQL statement that has + * multiple result sets. An example is a stored procedure that returns + * the results of multiple queries. + * + * @return bool + * @throws Zend_Db_Statement_Exception + */ + public function nextRowset() + { + if (sqlsrv_next_result($this->_stmt) === false) { + require_once 'Zend/Db/Statement/Sqlsrv/Exception.php'; + throw new Zend_Db_Statement_Sqlsrv_Exception(sqlsrv_errors()); + } + + // reset column keys + $this->_keys = null; + + return true; + } + + /** + * Returns the number of rows affected by the execution of the + * last INSERT, DELETE, or UPDATE statement executed by this + * statement object. + * + * @return int The number of rows affected. + * @throws Zend_Db_Statement_Exception + */ + public function rowCount() + { + if (!$this->_stmt) { + return false; + } + + if (!$this->_executed) { + return 0; + } + + $num_rows = sqlsrv_rows_affected($this->_stmt); + + // Strict check is necessary; 0 is a valid return value + if ($num_rows === false) { + require_once 'Zend/Db/Statement/Sqlsrv/Exception.php'; + throw new Zend_Db_Statement_Sqlsrv_Exception(sqlsrv_errors()); + } + + return $num_rows; + } + + /** + * Returns an array containing all of the result set rows. + * + * @param int $style OPTIONAL Fetch mode. + * @param int $col OPTIONAL Column number, if fetch mode is by column. + * @return array Collection of rows, each in a format by the fetch mode. + * + * Behaves like parent, but if limit() + * is used, the final result removes the extra column + * 'zend_db_rownum' + */ + public function fetchAll($style = null, $col = null) + { + $data = parent::fetchAll($style, $col); + $results = array(); + $remove = $this->_adapter->foldCase('ZEND_DB_ROWNUM'); + + foreach ($data as $row) { + if (is_array($row) && array_key_exists($remove, $row)) { + unset($row[$remove]); + } + $results[] = $row; + } + return $results; + } +} diff --git a/library/Zend/Db/Statement/Sqlsrv/Exception.php b/library/Zend/Db/Statement/Sqlsrv/Exception.php new file mode 100644 index 00000000..3c44b14c --- /dev/null +++ b/library/Zend/Db/Statement/Sqlsrv/Exception.php @@ -0,0 +1,61 @@ + $config); + } else { + // process this as table with or without a definition + if ($definition instanceof Zend_Db_Table_Definition + && $definition->hasTableConfig($config)) { + // this will have DEFINITION_CONFIG_NAME & DEFINITION + $config = $definition->getTableConfig($config); + } else { + $config = array(self::NAME => $config); + } + } + } + + parent::__construct($config); + } +} diff --git a/library/Zend/Db/Table/Abstract.php b/library/Zend/Db/Table/Abstract.php new file mode 100644 index 00000000..c97d8740 --- /dev/null +++ b/library/Zend/Db/Table/Abstract.php @@ -0,0 +1,1534 @@ + $config); + } + + if ($config) { + $this->setOptions($config); + } + + $this->_setup(); + $this->init(); + } + + /** + * setOptions() + * + * @param array $options + * @return Zend_Db_Table_Abstract + */ + public function setOptions(Array $options) + { + foreach ($options as $key => $value) { + switch ($key) { + case self::ADAPTER: + $this->_setAdapter($value); + break; + case self::DEFINITION: + $this->setDefinition($value); + break; + case self::DEFINITION_CONFIG_NAME: + $this->setDefinitionConfigName($value); + break; + case self::SCHEMA: + $this->_schema = (string) $value; + break; + case self::NAME: + $this->_name = (string) $value; + break; + case self::PRIMARY: + $this->_primary = (array) $value; + break; + case self::ROW_CLASS: + $this->setRowClass($value); + break; + case self::ROWSET_CLASS: + $this->setRowsetClass($value); + break; + case self::REFERENCE_MAP: + $this->setReferences($value); + break; + case self::DEPENDENT_TABLES: + $this->setDependentTables($value); + break; + case self::METADATA_CACHE: + $this->_setMetadataCache($value); + break; + case self::METADATA_CACHE_IN_CLASS: + $this->setMetadataCacheInClass($value); + break; + case self::SEQUENCE: + $this->_setSequence($value); + break; + default: + // ignore unrecognized configuration directive + break; + } + } + + return $this; + } + + /** + * setDefinition() + * + * @param Zend_Db_Table_Definition $definition + * @return Zend_Db_Table_Abstract + */ + public function setDefinition(Zend_Db_Table_Definition $definition) + { + $this->_definition = $definition; + return $this; + } + + /** + * getDefinition() + * + * @return Zend_Db_Table_Definition|null + */ + public function getDefinition() + { + return $this->_definition; + } + + /** + * setDefinitionConfigName() + * + * @param string $definition + * @return Zend_Db_Table_Abstract + */ + public function setDefinitionConfigName($definitionConfigName) + { + $this->_definitionConfigName = $definitionConfigName; + return $this; + } + + /** + * getDefinitionConfigName() + * + * @return string + */ + public function getDefinitionConfigName() + { + return $this->_definitionConfigName; + } + + /** + * @param string $classname + * @return Zend_Db_Table_Abstract Provides a fluent interface + */ + public function setRowClass($classname) + { + $this->_rowClass = (string) $classname; + + return $this; + } + + /** + * @return string + */ + public function getRowClass() + { + return $this->_rowClass; + } + + /** + * @param string $classname + * @return Zend_Db_Table_Abstract Provides a fluent interface + */ + public function setRowsetClass($classname) + { + $this->_rowsetClass = (string) $classname; + + return $this; + } + + /** + * @return string + */ + public function getRowsetClass() + { + return $this->_rowsetClass; + } + + /** + * Add a reference to the reference map + * + * @param string $ruleKey + * @param string|array $columns + * @param string $refTableClass + * @param string|array $refColumns + * @param string $onDelete + * @param string $onUpdate + * @return Zend_Db_Table_Abstract + */ + public function addReference($ruleKey, $columns, $refTableClass, $refColumns, + $onDelete = null, $onUpdate = null) + { + $reference = array(self::COLUMNS => (array) $columns, + self::REF_TABLE_CLASS => $refTableClass, + self::REF_COLUMNS => (array) $refColumns); + + if (!empty($onDelete)) { + $reference[self::ON_DELETE] = $onDelete; + } + + if (!empty($onUpdate)) { + $reference[self::ON_UPDATE] = $onUpdate; + } + + $this->_referenceMap[$ruleKey] = $reference; + + return $this; + } + + /** + * @param array $referenceMap + * @return Zend_Db_Table_Abstract Provides a fluent interface + */ + public function setReferences(array $referenceMap) + { + $this->_referenceMap = $referenceMap; + + return $this; + } + + /** + * @param string $tableClassname + * @param string $ruleKey OPTIONAL + * @return array + * @throws Zend_Db_Table_Exception + */ + public function getReference($tableClassname, $ruleKey = null) + { + $thisClass = get_class($this); + if ($thisClass === 'Zend_Db_Table') { + $thisClass = $this->_definitionConfigName; + } + $refMap = $this->_getReferenceMapNormalized(); + if ($ruleKey !== null) { + if (!isset($refMap[$ruleKey])) { + require_once "Zend/Db/Table/Exception.php"; + throw new Zend_Db_Table_Exception("No reference rule \"$ruleKey\" from table $thisClass to table $tableClassname"); + } + if ($refMap[$ruleKey][self::REF_TABLE_CLASS] != $tableClassname) { + require_once "Zend/Db/Table/Exception.php"; + throw new Zend_Db_Table_Exception("Reference rule \"$ruleKey\" does not reference table $tableClassname"); + } + return $refMap[$ruleKey]; + } + foreach ($refMap as $reference) { + if ($reference[self::REF_TABLE_CLASS] == $tableClassname) { + return $reference; + } + } + require_once "Zend/Db/Table/Exception.php"; + throw new Zend_Db_Table_Exception("No reference from table $thisClass to table $tableClassname"); + } + + /** + * @param array $dependentTables + * @return Zend_Db_Table_Abstract Provides a fluent interface + */ + public function setDependentTables(array $dependentTables) + { + $this->_dependentTables = $dependentTables; + + return $this; + } + + /** + * @return array + */ + public function getDependentTables() + { + return $this->_dependentTables; + } + + /** + * set the defaultSource property - this tells the table class where to find default values + * + * @param string $defaultSource + * @return Zend_Db_Table_Abstract + */ + public function setDefaultSource($defaultSource = self::DEFAULT_NONE) + { + if (!in_array($defaultSource, array(self::DEFAULT_CLASS, self::DEFAULT_DB, self::DEFAULT_NONE))) { + $defaultSource = self::DEFAULT_NONE; + } + + $this->_defaultSource = $defaultSource; + return $this; + } + + /** + * returns the default source flag that determines where defaultSources come from + * + * @return unknown + */ + public function getDefaultSource() + { + return $this->_defaultSource; + } + + /** + * set the default values for the table class + * + * @param array $defaultValues + * @return Zend_Db_Table_Abstract + */ + public function setDefaultValues(Array $defaultValues) + { + foreach ($defaultValues as $defaultName => $defaultValue) { + if (array_key_exists($defaultName, $this->_metadata)) { + $this->_defaultValues[$defaultName] = $defaultValue; + } + } + return $this; + } + + public function getDefaultValues() + { + return $this->_defaultValues; + } + + + /** + * Sets the default Zend_Db_Adapter_Abstract for all Zend_Db_Table objects. + * + * @param mixed $db Either an Adapter object, or a string naming a Registry key + * @return void + */ + public static function setDefaultAdapter($db = null) + { + self::$_defaultDb = self::_setupAdapter($db); + } + + /** + * Gets the default Zend_Db_Adapter_Abstract for all Zend_Db_Table objects. + * + * @return Zend_Db_Adapter_Abstract or null + */ + public static function getDefaultAdapter() + { + return self::$_defaultDb; + } + + /** + * @param mixed $db Either an Adapter object, or a string naming a Registry key + * @return Zend_Db_Table_Abstract Provides a fluent interface + */ + protected function _setAdapter($db) + { + $this->_db = self::_setupAdapter($db); + return $this; + } + + /** + * Gets the Zend_Db_Adapter_Abstract for this particular Zend_Db_Table object. + * + * @return Zend_Db_Adapter_Abstract + */ + public function getAdapter() + { + return $this->_db; + } + + /** + * @param mixed $db Either an Adapter object, or a string naming a Registry key + * @return Zend_Db_Adapter_Abstract + * @throws Zend_Db_Table_Exception + */ + protected static function _setupAdapter($db) + { + if ($db === null) { + return null; + } + if (is_string($db)) { + require_once 'Zend/Registry.php'; + $db = Zend_Registry::get($db); + } + if (!$db instanceof Zend_Db_Adapter_Abstract) { + require_once 'Zend/Db/Table/Exception.php'; + throw new Zend_Db_Table_Exception('Argument must be of type Zend_Db_Adapter_Abstract, or a Registry key where a Zend_Db_Adapter_Abstract object is stored'); + } + return $db; + } + + /** + * Sets the default metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable(). + * + * If $defaultMetadataCache is null, then no metadata cache is used by default. + * + * @param mixed $metadataCache Either a Cache object, or a string naming a Registry key + * @return void + */ + public static function setDefaultMetadataCache($metadataCache = null) + { + self::$_defaultMetadataCache = self::_setupMetadataCache($metadataCache); + } + + /** + * Gets the default metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable(). + * + * @return Zend_Cache_Core or null + */ + public static function getDefaultMetadataCache() + { + return self::$_defaultMetadataCache; + } + + /** + * Sets the metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable(). + * + * If $metadataCache is null, then no metadata cache is used. Since there is no opportunity to reload metadata + * after instantiation, this method need not be public, particularly because that it would have no effect + * results in unnecessary API complexity. To configure the metadata cache, use the metadataCache configuration + * option for the class constructor upon instantiation. + * + * @param mixed $metadataCache Either a Cache object, or a string naming a Registry key + * @return Zend_Db_Table_Abstract Provides a fluent interface + */ + protected function _setMetadataCache($metadataCache) + { + $this->_metadataCache = self::_setupMetadataCache($metadataCache); + return $this; + } + + /** + * Gets the metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable(). + * + * @return Zend_Cache_Core or null + */ + public function getMetadataCache() + { + return $this->_metadataCache; + } + + /** + * Indicate whether metadata should be cached in the class for the duration + * of the instance + * + * @param bool $flag + * @return Zend_Db_Table_Abstract + */ + public function setMetadataCacheInClass($flag) + { + $this->_metadataCacheInClass = (bool) $flag; + return $this; + } + + /** + * Retrieve flag indicating if metadata should be cached for duration of + * instance + * + * @return bool + */ + public function metadataCacheInClass() + { + return $this->_metadataCacheInClass; + } + + /** + * @param mixed $metadataCache Either a Cache object, or a string naming a Registry key + * @return Zend_Cache_Core + * @throws Zend_Db_Table_Exception + */ + protected static function _setupMetadataCache($metadataCache) + { + if ($metadataCache === null) { + return null; + } + if (is_string($metadataCache)) { + require_once 'Zend/Registry.php'; + $metadataCache = Zend_Registry::get($metadataCache); + } + if (!$metadataCache instanceof Zend_Cache_Core) { + require_once 'Zend/Db/Table/Exception.php'; + throw new Zend_Db_Table_Exception('Argument must be of type Zend_Cache_Core, or a Registry key where a Zend_Cache_Core object is stored'); + } + return $metadataCache; + } + + /** + * Sets the sequence member, which defines the behavior for generating + * primary key values in new rows. + * - If this is a string, then the string names the sequence object. + * - If this is boolean true, then the key uses an auto-incrementing + * or identity mechanism. + * - If this is boolean false, then the key is user-defined. + * Use this for natural keys, for example. + * + * @param mixed $sequence + * @return Zend_Db_Table_Adapter_Abstract Provides a fluent interface + */ + protected function _setSequence($sequence) + { + $this->_sequence = $sequence; + + return $this; + } + + /** + * Turnkey for initialization of a table object. + * Calls other protected methods for individual tasks, to make it easier + * for a subclass to override part of the setup logic. + * + * @return void + */ + protected function _setup() + { + $this->_setupDatabaseAdapter(); + $this->_setupTableName(); + } + + /** + * Initialize database adapter. + * + * @return void + * @throws Zend_Db_Table_Exception + */ + protected function _setupDatabaseAdapter() + { + if (! $this->_db) { + $this->_db = self::getDefaultAdapter(); + if (!$this->_db instanceof Zend_Db_Adapter_Abstract) { + require_once 'Zend/Db/Table/Exception.php'; + throw new Zend_Db_Table_Exception('No adapter found for ' . get_class($this)); + } + } + } + + /** + * Initialize table and schema names. + * + * If the table name is not set in the class definition, + * use the class name itself as the table name. + * + * A schema name provided with the table name (e.g., "schema.table") overrides + * any existing value for $this->_schema. + * + * @return void + */ + protected function _setupTableName() + { + if (! $this->_name) { + $this->_name = get_class($this); + } else if (strpos($this->_name, '.')) { + list($this->_schema, $this->_name) = explode('.', $this->_name); + } + } + + /** + * Initializes metadata. + * + * If metadata cannot be loaded from cache, adapter's describeTable() method is called to discover metadata + * information. Returns true if and only if the metadata are loaded from cache. + * + * @return boolean + * @throws Zend_Db_Table_Exception + */ + protected function _setupMetadata() + { + if ($this->metadataCacheInClass() && (count($this->_metadata) > 0)) { + return true; + } + + // Assume that metadata will be loaded from cache + $isMetadataFromCache = true; + + // If $this has no metadata cache but the class has a default metadata cache + if (null === $this->_metadataCache && null !== self::$_defaultMetadataCache) { + // Make $this use the default metadata cache of the class + $this->_setMetadataCache(self::$_defaultMetadataCache); + } + + // If $this has a metadata cache + if (null !== $this->_metadataCache) { + // Define the cache identifier where the metadata are saved + + //get db configuration + $dbConfig = $this->_db->getConfig(); + + $port = isset($dbConfig['options']['port']) + ? ':'.$dbConfig['options']['port'] + : (isset($dbConfig['port']) + ? ':'.$dbConfig['port'] + : null); + + $host = isset($dbConfig['options']['host']) + ? ':'.$dbConfig['options']['host'] + : (isset($dbConfig['host']) + ? ':'.$dbConfig['host'] + : null); + + // Define the cache identifier where the metadata are saved + $cacheId = md5( // port:host/dbname:schema.table (based on availabilty) + $port . $host . '/'. $dbConfig['dbname'] . ':' + . $this->_schema. '.' . $this->_name + ); + } + + // If $this has no metadata cache or metadata cache misses + if (null === $this->_metadataCache || !($metadata = $this->_metadataCache->load($cacheId))) { + // Metadata are not loaded from cache + $isMetadataFromCache = false; + // Fetch metadata from the adapter's describeTable() method + $metadata = $this->_db->describeTable($this->_name, $this->_schema); + // If $this has a metadata cache, then cache the metadata + if (null !== $this->_metadataCache && !$this->_metadataCache->save($metadata, $cacheId)) { + trigger_error('Failed saving metadata to metadataCache', E_USER_NOTICE); + } + } + + // Assign the metadata to $this + $this->_metadata = $metadata; + + // Return whether the metadata were loaded from cache + return $isMetadataFromCache; + } + + /** + * Retrieve table columns + * + * @return array + */ + protected function _getCols() + { + if (null === $this->_cols) { + $this->_setupMetadata(); + $this->_cols = array_keys($this->_metadata); + } + return $this->_cols; + } + + /** + * Initialize primary key from metadata. + * If $_primary is not defined, discover primary keys + * from the information returned by describeTable(). + * + * @return void + * @throws Zend_Db_Table_Exception + */ + protected function _setupPrimaryKey() + { + if (!$this->_primary) { + $this->_setupMetadata(); + $this->_primary = array(); + foreach ($this->_metadata as $col) { + if ($col['PRIMARY']) { + $this->_primary[ $col['PRIMARY_POSITION'] ] = $col['COLUMN_NAME']; + if ($col['IDENTITY']) { + $this->_identity = $col['PRIMARY_POSITION']; + } + } + } + // if no primary key was specified and none was found in the metadata + // then throw an exception. + if (empty($this->_primary)) { + require_once 'Zend/Db/Table/Exception.php'; + throw new Zend_Db_Table_Exception('A table must have a primary key, but none was found'); + } + } else if (!is_array($this->_primary)) { + $this->_primary = array(1 => $this->_primary); + } else if (isset($this->_primary[0])) { + array_unshift($this->_primary, null); + unset($this->_primary[0]); + } + + $cols = $this->_getCols(); + if (! array_intersect((array) $this->_primary, $cols) == (array) $this->_primary) { + require_once 'Zend/Db/Table/Exception.php'; + throw new Zend_Db_Table_Exception("Primary key column(s) (" + . implode(',', (array) $this->_primary) + . ") are not columns in this table (" + . implode(',', $cols) + . ")"); + } + + $primary = (array) $this->_primary; + $pkIdentity = $primary[(int) $this->_identity]; + + /** + * Special case for PostgreSQL: a SERIAL key implicitly uses a sequence + * object whose name is "__seq". + */ + if ($this->_sequence === true && $this->_db instanceof Zend_Db_Adapter_Pdo_Pgsql) { + $this->_sequence = $this->_db->quoteIdentifier("{$this->_name}_{$pkIdentity}_seq"); + if ($this->_schema) { + $this->_sequence = $this->_db->quoteIdentifier($this->_schema) . '.' . $this->_sequence; + } + } + } + + /** + * Returns a normalized version of the reference map + * + * @return array + */ + protected function _getReferenceMapNormalized() + { + $referenceMapNormalized = array(); + + foreach ($this->_referenceMap as $rule => $map) { + + $referenceMapNormalized[$rule] = array(); + + foreach ($map as $key => $value) { + switch ($key) { + + // normalize COLUMNS and REF_COLUMNS to arrays + case self::COLUMNS: + case self::REF_COLUMNS: + if (!is_array($value)) { + $referenceMapNormalized[$rule][$key] = array($value); + } else { + $referenceMapNormalized[$rule][$key] = $value; + } + break; + + // other values are copied as-is + default: + $referenceMapNormalized[$rule][$key] = $value; + break; + } + } + } + + return $referenceMapNormalized; + } + + /** + * Initialize object + * + * Called from {@link __construct()} as final step of object instantiation. + * + * @return void + */ + public function init() + { + } + + /** + * Returns table information. + * + * You can elect to return only a part of this information by supplying its key name, + * otherwise all information is returned as an array. + * + * @param string $key The specific info part to return OPTIONAL + * @return mixed + * @throws Zend_Db_Table_Exception + */ + public function info($key = null) + { + $this->_setupPrimaryKey(); + + $info = array( + self::SCHEMA => $this->_schema, + self::NAME => $this->_name, + self::COLS => $this->_getCols(), + self::PRIMARY => (array) $this->_primary, + self::METADATA => $this->_metadata, + self::ROW_CLASS => $this->getRowClass(), + self::ROWSET_CLASS => $this->getRowsetClass(), + self::REFERENCE_MAP => $this->_referenceMap, + self::DEPENDENT_TABLES => $this->_dependentTables, + self::SEQUENCE => $this->_sequence + ); + + if ($key === null) { + return $info; + } + + if (!array_key_exists($key, $info)) { + require_once 'Zend/Db/Table/Exception.php'; + throw new Zend_Db_Table_Exception('There is no table information for the key "' . $key . '"'); + } + + return $info[$key]; + } + + /** + * Returns an instance of a Zend_Db_Table_Select object. + * + * @param bool $withFromPart Whether or not to include the from part of the select based on the table + * @return Zend_Db_Table_Select + */ + public function select($withFromPart = self::SELECT_WITHOUT_FROM_PART) + { + require_once 'Zend/Db/Table/Select.php'; + $select = new Zend_Db_Table_Select($this); + if ($withFromPart == self::SELECT_WITH_FROM_PART) { + $select->from($this->info(self::NAME), Zend_Db_Table_Select::SQL_WILDCARD, $this->info(self::SCHEMA)); + } + return $select; + } + + /** + * Inserts a new row. + * + * @param array $data Column-value pairs. + * @return mixed The primary key of the row inserted. + */ + public function insert(array $data) + { + $this->_setupPrimaryKey(); + + /** + * Zend_Db_Table assumes that if you have a compound primary key + * and one of the columns in the key uses a sequence, + * it's the _first_ column in the compound key. + */ + $primary = (array) $this->_primary; + $pkIdentity = $primary[(int)$this->_identity]; + + /** + * If this table uses a database sequence object and the data does not + * specify a value, then get the next ID from the sequence and add it + * to the row. We assume that only the first column in a compound + * primary key takes a value from a sequence. + */ + if (is_string($this->_sequence) && !isset($data[$pkIdentity])) { + $data[$pkIdentity] = $this->_db->nextSequenceId($this->_sequence); + $pkSuppliedBySequence = true; + } + + /** + * If the primary key can be generated automatically, and no value was + * specified in the user-supplied data, then omit it from the tuple. + * + * Note: this checks for sensible values in the supplied primary key + * position of the data. The following values are considered empty: + * null, false, true, '', array() + */ + if (!isset($pkSuppliedBySequence) && array_key_exists($pkIdentity, $data)) { + if ($data[$pkIdentity] === null // null + || $data[$pkIdentity] === '' // empty string + || is_bool($data[$pkIdentity]) // boolean + || (is_array($data[$pkIdentity]) && empty($data[$pkIdentity]))) { // empty array + unset($data[$pkIdentity]); + } + } + + /** + * INSERT the new row. + */ + $tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name; + $this->_db->insert($tableSpec, $data); + + /** + * Fetch the most recent ID generated by an auto-increment + * or IDENTITY column, unless the user has specified a value, + * overriding the auto-increment mechanism. + */ + if ($this->_sequence === true && !isset($data[$pkIdentity])) { + $data[$pkIdentity] = $this->_db->lastInsertId(); + } + + /** + * Return the primary key value if the PK is a single column, + * else return an associative array of the PK column/value pairs. + */ + $pkData = array_intersect_key($data, array_flip($primary)); + if (count($primary) == 1) { + reset($pkData); + return current($pkData); + } + + return $pkData; + } + + /** + * Check if the provided column is an identity of the table + * + * @param string $column + * @throws Zend_Db_Table_Exception + * @return boolean + */ + public function isIdentity($column) + { + $this->_setupPrimaryKey(); + + if (!isset($this->_metadata[$column])) { + /** + * @see Zend_Db_Table_Exception + */ + require_once 'Zend/Db/Table/Exception.php'; + + throw new Zend_Db_Table_Exception('Column "' . $column . '" not found in table.'); + } + + return (bool) $this->_metadata[$column]['IDENTITY']; + } + + /** + * Updates existing rows. + * + * @param array $data Column-value pairs. + * @param array|string $where An SQL WHERE clause, or an array of SQL WHERE clauses. + * @return int The number of rows updated. + */ + public function update(array $data, $where) + { + $tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name; + return $this->_db->update($tableSpec, $data, $where); + } + + /** + * Called by a row object for the parent table's class during save() method. + * + * @param string $parentTableClassname + * @param array $oldPrimaryKey + * @param array $newPrimaryKey + * @return int + */ + public function _cascadeUpdate($parentTableClassname, array $oldPrimaryKey, array $newPrimaryKey) + { + $this->_setupMetadata(); + $rowsAffected = 0; + foreach ($this->_getReferenceMapNormalized() as $map) { + if ($map[self::REF_TABLE_CLASS] == $parentTableClassname && isset($map[self::ON_UPDATE])) { + switch ($map[self::ON_UPDATE]) { + case self::CASCADE: + $newRefs = array(); + $where = array(); + for ($i = 0; $i < count($map[self::COLUMNS]); ++$i) { + $col = $this->_db->foldCase($map[self::COLUMNS][$i]); + $refCol = $this->_db->foldCase($map[self::REF_COLUMNS][$i]); + if (array_key_exists($refCol, $newPrimaryKey)) { + $newRefs[$col] = $newPrimaryKey[$refCol]; + } + $type = $this->_metadata[$col]['DATA_TYPE']; + $where[] = $this->_db->quoteInto( + $this->_db->quoteIdentifier($col, true) . ' = ?', + $oldPrimaryKey[$refCol], $type); + } + $rowsAffected += $this->update($newRefs, $where); + break; + default: + // no action + break; + } + } + } + return $rowsAffected; + } + + /** + * Deletes existing rows. + * + * @param array|string $where SQL WHERE clause(s). + * @return int The number of rows deleted. + */ + public function delete($where) + { + $tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name; + return $this->_db->delete($tableSpec, $where); + } + + /** + * Called by parent table's class during delete() method. + * + * @param string $parentTableClassname + * @param array $primaryKey + * @return int Number of affected rows + */ + public function _cascadeDelete($parentTableClassname, array $primaryKey) + { + $this->_setupMetadata(); + $rowsAffected = 0; + foreach ($this->_getReferenceMapNormalized() as $map) { + if ($map[self::REF_TABLE_CLASS] == $parentTableClassname && isset($map[self::ON_DELETE])) { + switch ($map[self::ON_DELETE]) { + case self::CASCADE: + $where = array(); + for ($i = 0; $i < count($map[self::COLUMNS]); ++$i) { + $col = $this->_db->foldCase($map[self::COLUMNS][$i]); + $refCol = $this->_db->foldCase($map[self::REF_COLUMNS][$i]); + $type = $this->_metadata[$col]['DATA_TYPE']; + $where[] = $this->_db->quoteInto( + $this->_db->quoteIdentifier($col, true) . ' = ?', + $primaryKey[$refCol], $type); + } + $rowsAffected += $this->delete($where); + break; + default: + // no action + break; + } + } + } + return $rowsAffected; + } + + /** + * Fetches rows by primary key. The argument specifies one or more primary + * key value(s). To find multiple rows by primary key, the argument must + * be an array. + * + * This method accepts a variable number of arguments. If the table has a + * multi-column primary key, the number of arguments must be the same as + * the number of columns in the primary key. To find multiple rows in a + * table with a multi-column primary key, each argument must be an array + * with the same number of elements. + * + * The find() method always returns a Rowset object, even if only one row + * was found. + * + * @param mixed $key The value(s) of the primary keys. + * @return Zend_Db_Table_Rowset_Abstract Row(s) matching the criteria. + * @throws Zend_Db_Table_Exception + */ + public function find() + { + $this->_setupPrimaryKey(); + $args = func_get_args(); + $keyNames = array_values((array) $this->_primary); + + if (count($args) < count($keyNames)) { + require_once 'Zend/Db/Table/Exception.php'; + throw new Zend_Db_Table_Exception("Too few columns for the primary key"); + } + + if (count($args) > count($keyNames)) { + require_once 'Zend/Db/Table/Exception.php'; + throw new Zend_Db_Table_Exception("Too many columns for the primary key"); + } + + $whereList = array(); + $numberTerms = 0; + foreach ($args as $keyPosition => $keyValues) { + $keyValuesCount = count($keyValues); + // Coerce the values to an array. + // Don't simply typecast to array, because the values + // might be Zend_Db_Expr objects. + if (!is_array($keyValues)) { + $keyValues = array($keyValues); + } + if ($numberTerms == 0) { + $numberTerms = $keyValuesCount; + } else if ($keyValuesCount != $numberTerms) { + require_once 'Zend/Db/Table/Exception.php'; + throw new Zend_Db_Table_Exception("Missing value(s) for the primary key"); + } + $keyValues = array_values($keyValues); + for ($i = 0; $i < $keyValuesCount; ++$i) { + if (!isset($whereList[$i])) { + $whereList[$i] = array(); + } + $whereList[$i][$keyPosition] = $keyValues[$i]; + } + } + + $whereClause = null; + if (count($whereList)) { + $whereOrTerms = array(); + $tableName = $this->_db->quoteTableAs($this->_name, null, true); + foreach ($whereList as $keyValueSets) { + $whereAndTerms = array(); + foreach ($keyValueSets as $keyPosition => $keyValue) { + $type = $this->_metadata[$keyNames[$keyPosition]]['DATA_TYPE']; + $columnName = $this->_db->quoteIdentifier($keyNames[$keyPosition], true); + $whereAndTerms[] = $this->_db->quoteInto( + $tableName . '.' . $columnName . ' = ?', + $keyValue, $type); + } + $whereOrTerms[] = '(' . implode(' AND ', $whereAndTerms) . ')'; + } + $whereClause = '(' . implode(' OR ', $whereOrTerms) . ')'; + } + + // issue ZF-5775 (empty where clause should return empty rowset) + if ($whereClause == null) { + $rowsetClass = $this->getRowsetClass(); + if (!class_exists($rowsetClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($rowsetClass); + } + return new $rowsetClass(array('table' => $this, 'rowClass' => $this->getRowClass(), 'stored' => true)); + } + + return $this->fetchAll($whereClause); + } + + /** + * Fetches all rows. + * + * Honors the Zend_Db_Adapter fetch mode. + * + * @param string|array|Zend_Db_Table_Select $where OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object. + * @param string|array $order OPTIONAL An SQL ORDER clause. + * @param int $count OPTIONAL An SQL LIMIT count. + * @param int $offset OPTIONAL An SQL LIMIT offset. + * @return Zend_Db_Table_Rowset_Abstract The row results per the Zend_Db_Adapter fetch mode. + */ + public function fetchAll($where = null, $order = null, $count = null, $offset = null) + { + if (!($where instanceof Zend_Db_Table_Select)) { + $select = $this->select(); + + if ($where !== null) { + $this->_where($select, $where); + } + + if ($order !== null) { + $this->_order($select, $order); + } + + if ($count !== null || $offset !== null) { + $select->limit($count, $offset); + } + + } else { + $select = $where; + } + + $rows = $this->_fetch($select); + + $data = array( + 'table' => $this, + 'data' => $rows, + 'readOnly' => $select->isReadOnly(), + 'rowClass' => $this->getRowClass(), + 'stored' => true + ); + + $rowsetClass = $this->getRowsetClass(); + if (!class_exists($rowsetClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($rowsetClass); + } + return new $rowsetClass($data); + } + + /** + * Fetches one row in an object of type Zend_Db_Table_Row_Abstract, + * or returns null if no row matches the specified criteria. + * + * @param string|array|Zend_Db_Table_Select $where OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object. + * @param string|array $order OPTIONAL An SQL ORDER clause. + * @param int $offset OPTIONAL An SQL OFFSET value. + * @return Zend_Db_Table_Row_Abstract|null The row results per the + * Zend_Db_Adapter fetch mode, or null if no row found. + */ + public function fetchRow($where = null, $order = null, $offset = null) + { + if (!($where instanceof Zend_Db_Table_Select)) { + $select = $this->select(); + + if ($where !== null) { + $this->_where($select, $where); + } + + if ($order !== null) { + $this->_order($select, $order); + } + + $select->limit(1, ((is_numeric($offset)) ? (int) $offset : null)); + + } else { + $select = $where->limit(1, $where->getPart(Zend_Db_Select::LIMIT_OFFSET)); + } + + $rows = $this->_fetch($select); + + if (count($rows) == 0) { + return null; + } + + $data = array( + 'table' => $this, + 'data' => $rows[0], + 'readOnly' => $select->isReadOnly(), + 'stored' => true + ); + + $rowClass = $this->getRowClass(); + if (!class_exists($rowClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($rowClass); + } + return new $rowClass($data); + } + + /** + * Fetches a new blank row (not from the database). + * + * @return Zend_Db_Table_Row_Abstract + * @deprecated since 0.9.3 - use createRow() instead. + */ + public function fetchNew() + { + return $this->createRow(); + } + + /** + * Fetches a new blank row (not from the database). + * + * @param array $data OPTIONAL data to populate in the new row. + * @param string $defaultSource OPTIONAL flag to force default values into new row + * @return Zend_Db_Table_Row_Abstract + */ + public function createRow(array $data = array(), $defaultSource = null) + { + $cols = $this->_getCols(); + $defaults = array_combine($cols, array_fill(0, count($cols), null)); + + // nothing provided at call-time, take the class value + if ($defaultSource == null) { + $defaultSource = $this->_defaultSource; + } + + if (!in_array($defaultSource, array(self::DEFAULT_CLASS, self::DEFAULT_DB, self::DEFAULT_NONE))) { + $defaultSource = self::DEFAULT_NONE; + } + + if ($defaultSource == self::DEFAULT_DB) { + foreach ($this->_metadata as $metadataName => $metadata) { + if (($metadata['DEFAULT'] != null) && + ($metadata['NULLABLE'] !== true || ($metadata['NULLABLE'] === true && isset($this->_defaultValues[$metadataName]) && $this->_defaultValues[$metadataName] === true)) && + (!(isset($this->_defaultValues[$metadataName]) && $this->_defaultValues[$metadataName] === false))) { + $defaults[$metadataName] = $metadata['DEFAULT']; + } + } + } elseif ($defaultSource == self::DEFAULT_CLASS && $this->_defaultValues) { + foreach ($this->_defaultValues as $defaultName => $defaultValue) { + if (array_key_exists($defaultName, $defaults)) { + $defaults[$defaultName] = $defaultValue; + } + } + } + + $config = array( + 'table' => $this, + 'data' => $defaults, + 'readOnly' => false, + 'stored' => false + ); + + $rowClass = $this->getRowClass(); + if (!class_exists($rowClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($rowClass); + } + $row = new $rowClass($config); + $row->setFromArray($data); + return $row; + } + + /** + * Generate WHERE clause from user-supplied string or array + * + * @param string|array $where OPTIONAL An SQL WHERE clause. + * @return Zend_Db_Table_Select + */ + protected function _where(Zend_Db_Table_Select $select, $where) + { + $where = (array) $where; + + foreach ($where as $key => $val) { + // is $key an int? + if (is_int($key)) { + // $val is the full condition + $select->where($val); + } else { + // $key is the condition with placeholder, + // and $val is quoted into the condition + $select->where($key, $val); + } + } + + return $select; + } + + /** + * Generate ORDER clause from user-supplied string or array + * + * @param string|array $order OPTIONAL An SQL ORDER clause. + * @return Zend_Db_Table_Select + */ + protected function _order(Zend_Db_Table_Select $select, $order) + { + if (!is_array($order)) { + $order = array($order); + } + + foreach ($order as $val) { + $select->order($val); + } + + return $select; + } + + /** + * Support method for fetching rows. + * + * @param Zend_Db_Table_Select $select query options. + * @return array An array containing the row results in FETCH_ASSOC mode. + */ + protected function _fetch(Zend_Db_Table_Select $select) + { + $stmt = $this->_db->query($select); + $data = $stmt->fetchAll(Zend_Db::FETCH_ASSOC); + return $data; + } + +} diff --git a/library/Zend/Db/Table/Definition.php b/library/Zend/Db/Table/Definition.php new file mode 100644 index 00000000..2d4cfcf0 --- /dev/null +++ b/library/Zend/Db/Table/Definition.php @@ -0,0 +1,131 @@ +setConfig($options); + } elseif (is_array($options)) { + $this->setOptions($options); + } + } + + /** + * setConfig() + * + * @param Zend_Config $config + * @return Zend_Db_Table_Definition + */ + public function setConfig(Zend_Config $config) + { + $this->setOptions($config->toArray()); + return $this; + } + + /** + * setOptions() + * + * @param array $options + * @return Zend_Db_Table_Definition + */ + public function setOptions(Array $options) + { + foreach ($options as $optionName => $optionValue) { + $this->setTableConfig($optionName, $optionValue); + } + return $this; + } + + /** + * @param string $tableName + * @param array $tableConfig + * @return Zend_Db_Table_Definition + */ + public function setTableConfig($tableName, array $tableConfig) + { + // @todo logic here + $tableConfig[Zend_Db_Table::DEFINITION_CONFIG_NAME] = $tableName; + $tableConfig[Zend_Db_Table::DEFINITION] = $this; + + if (!isset($tableConfig[Zend_Db_Table::NAME])) { + $tableConfig[Zend_Db_Table::NAME] = $tableName; + } + + $this->_tableConfigs[$tableName] = $tableConfig; + return $this; + } + + /** + * getTableConfig() + * + * @param string $tableName + * @return array + */ + public function getTableConfig($tableName) + { + return $this->_tableConfigs[$tableName]; + } + + /** + * removeTableConfig() + * + * @param string $tableName + */ + public function removeTableConfig($tableName) + { + unset($this->_tableConfigs[$tableName]); + } + + /** + * hasTableConfig() + * + * @param string $tableName + * @return bool + */ + public function hasTableConfig($tableName) + { + return (isset($this->_tableConfigs[$tableName])); + } + +} diff --git a/library/Zend/Db/Table/Exception.php b/library/Zend/Db/Table/Exception.php new file mode 100644 index 00000000..32f18df2 --- /dev/null +++ b/library/Zend/Db/Table/Exception.php @@ -0,0 +1,38 @@ + value). + * The keys must match the physical names of columns in the + * table for which this row is defined. + * + * @var array + */ + protected $_data = array(); + + /** + * This is set to a copy of $_data when the data is fetched from + * a database, specified as a new tuple in the constructor, or + * when dirty data is posted to the database with save(). + * + * @var array + */ + protected $_cleanData = array(); + + /** + * Tracks columns where data has been updated. Allows more specific insert and + * update operations. + * + * @var array + */ + protected $_modifiedFields = array(); + + /** + * Zend_Db_Table_Abstract parent class or instance. + * + * @var Zend_Db_Table_Abstract + */ + protected $_table = null; + + /** + * Connected is true if we have a reference to a live + * Zend_Db_Table_Abstract object. + * This is false after the Rowset has been deserialized. + * + * @var boolean + */ + protected $_connected = true; + + /** + * A row is marked read only if it contains columns that are not physically represented within + * the database schema (e.g. evaluated columns/Zend_Db_Expr columns). This can also be passed + * as a run-time config options as a means of protecting row data. + * + * @var boolean + */ + protected $_readOnly = false; + + /** + * Name of the class of the Zend_Db_Table_Abstract object. + * + * @var string + */ + protected $_tableClass = null; + + /** + * Primary row key(s). + * + * @var array + */ + protected $_primary; + + /** + * Constructor. + * + * Supported params for $config are:- + * - table = class name or object of type Zend_Db_Table_Abstract + * - data = values of columns in this row. + * + * @param array $config OPTIONAL Array of user-specified config options. + * @return void + * @throws Zend_Db_Table_Row_Exception + */ + public function __construct(array $config = array()) + { + if (isset($config['table']) && $config['table'] instanceof Zend_Db_Table_Abstract) { + $this->_table = $config['table']; + $this->_tableClass = get_class($this->_table); + } elseif ($this->_tableClass !== null) { + $this->_table = $this->_getTableFromString($this->_tableClass); + } + + if (isset($config['data'])) { + if (!is_array($config['data'])) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception('Data must be an array'); + } + $this->_data = $config['data']; + } + if (isset($config['stored']) && $config['stored'] === true) { + $this->_cleanData = $this->_data; + } + + if (isset($config['readOnly']) && $config['readOnly'] === true) { + $this->setReadOnly(true); + } + + // Retrieve primary keys from table schema + if (($table = $this->_getTable())) { + $info = $table->info(); + $this->_primary = (array) $info['primary']; + } + + $this->init(); + } + + /** + * Transform a column name from the user-specified form + * to the physical form used in the database. + * You can override this method in a custom Row class + * to implement column name mappings, for example inflection. + * + * @param string $columnName Column name given. + * @return string The column name after transformation applied (none by default). + * @throws Zend_Db_Table_Row_Exception if the $columnName is not a string. + */ + protected function _transformColumn($columnName) + { + if (!is_string($columnName)) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception('Specified column is not a string'); + } + // Perform no transformation by default + return $columnName; + } + + /** + * Retrieve row field value + * + * @param string $columnName The user-specified column name. + * @return string The corresponding column value. + * @throws Zend_Db_Table_Row_Exception if the $columnName is not a column in the row. + */ + public function __get($columnName) + { + $columnName = $this->_transformColumn($columnName); + if (!array_key_exists($columnName, $this->_data)) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row"); + } + return $this->_data[$columnName]; + } + + /** + * Set row field value + * + * @param string $columnName The column key. + * @param mixed $value The value for the property. + * @return void + * @throws Zend_Db_Table_Row_Exception + */ + public function __set($columnName, $value) + { + $columnName = $this->_transformColumn($columnName); + if (!array_key_exists($columnName, $this->_data)) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row"); + } + $this->_data[$columnName] = $value; + $this->_modifiedFields[$columnName] = true; + } + + /** + * Unset row field value + * + * @param string $columnName The column key. + * @return Zend_Db_Table_Row_Abstract + * @throws Zend_Db_Table_Row_Exception + */ + public function __unset($columnName) + { + $columnName = $this->_transformColumn($columnName); + if (!array_key_exists($columnName, $this->_data)) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row"); + } + if ($this->isConnected() && in_array($columnName, $this->_table->info('primary'))) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is a primary key and should not be unset"); + } + unset($this->_data[$columnName]); + return $this; + } + + /** + * Test existence of row field + * + * @param string $columnName The column key. + * @return boolean + */ + public function __isset($columnName) + { + $columnName = $this->_transformColumn($columnName); + return array_key_exists($columnName, $this->_data); + } + + /** + * Store table, primary key and data in serialized object + * + * @return array + */ + public function __sleep() + { + return array('_tableClass', '_primary', '_data', '_cleanData', '_readOnly' ,'_modifiedFields'); + } + + /** + * Setup to do on wakeup. + * A de-serialized Row should not be assumed to have access to a live + * database connection, so set _connected = false. + * + * @return void + */ + public function __wakeup() + { + $this->_connected = false; + } + + /** + * Proxy to __isset + * Required by the ArrayAccess implementation + * + * @param string $offset + * @return boolean + */ + public function offsetExists($offset) + { + return $this->__isset($offset); + } + + /** + * Proxy to __get + * Required by the ArrayAccess implementation + * + * @param string $offset + * @return string + */ + public function offsetGet($offset) + { + return $this->__get($offset); + } + + /** + * Proxy to __set + * Required by the ArrayAccess implementation + * + * @param string $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + $this->__set($offset, $value); + } + + /** + * Proxy to __unset + * Required by the ArrayAccess implementation + * + * @param string $offset + */ + public function offsetUnset($offset) + { + return $this->__unset($offset); + } + + /** + * Initialize object + * + * Called from {@link __construct()} as final step of object instantiation. + * + * @return void + */ + public function init() + { + } + + /** + * Returns the table object, or null if this is disconnected row + * + * @return Zend_Db_Table_Abstract|null + */ + public function getTable() + { + return $this->_table; + } + + /** + * Set the table object, to re-establish a live connection + * to the database for a Row that has been de-serialized. + * + * @param Zend_Db_Table_Abstract $table + * @return boolean + * @throws Zend_Db_Table_Row_Exception + */ + public function setTable(Zend_Db_Table_Abstract $table = null) + { + if ($table == null) { + $this->_table = null; + $this->_connected = false; + return false; + } + + $tableClass = get_class($table); + if (! $table instanceof $this->_tableClass) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("The specified Table is of class $tableClass, expecting class to be instance of $this->_tableClass"); + } + + $this->_table = $table; + $this->_tableClass = $tableClass; + + $info = $this->_table->info(); + + if ($info['cols'] != array_keys($this->_data)) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception('The specified Table does not have the same columns as the Row'); + } + + if (! array_intersect((array) $this->_primary, $info['primary']) == (array) $this->_primary) { + + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("The specified Table '$tableClass' does not have the same primary key as the Row"); + } + + $this->_connected = true; + return true; + } + + /** + * Query the class name of the Table object for which this + * Row was created. + * + * @return string + */ + public function getTableClass() + { + return $this->_tableClass; + } + + /** + * Test the connected status of the row. + * + * @return boolean + */ + public function isConnected() + { + return $this->_connected; + } + + /** + * Test the read-only status of the row. + * + * @return boolean + */ + public function isReadOnly() + { + return $this->_readOnly; + } + + /** + * Set the read-only status of the row. + * + * @param boolean $flag + * @return boolean + */ + public function setReadOnly($flag) + { + $this->_readOnly = (bool) $flag; + } + + /** + * Returns an instance of the parent table's Zend_Db_Table_Select object. + * + * @return Zend_Db_Table_Select + */ + public function select() + { + return $this->getTable()->select(); + } + + /** + * Saves the properties to the database. + * + * This performs an intelligent insert/update, and reloads the + * properties with fresh data from the table on success. + * + * @return mixed The primary key value(s), as an associative array if the + * key is compound, or a scalar if the key is single-column. + */ + public function save() + { + /** + * If the _cleanData array is empty, + * this is an INSERT of a new row. + * Otherwise it is an UPDATE. + */ + if (empty($this->_cleanData)) { + return $this->_doInsert(); + } else { + return $this->_doUpdate(); + } + } + + /** + * @return mixed The primary key value(s), as an associative array if the + * key is compound, or a scalar if the key is single-column. + */ + protected function _doInsert() + { + /** + * A read-only row cannot be saved. + */ + if ($this->_readOnly === true) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception('This row has been marked read-only'); + } + + /** + * Run pre-INSERT logic + */ + $this->_insert(); + + /** + * Execute the INSERT (this may throw an exception) + */ + $data = array_intersect_key($this->_data, $this->_modifiedFields); + $primaryKey = $this->_getTable()->insert($data); + + /** + * Normalize the result to an array indexed by primary key column(s). + * The table insert() method may return a scalar. + */ + if (is_array($primaryKey)) { + $newPrimaryKey = $primaryKey; + } else { + //ZF-6167 Use tempPrimaryKey temporary to avoid that zend encoding fails. + $tempPrimaryKey = (array) $this->_primary; + $newPrimaryKey = array(current($tempPrimaryKey) => $primaryKey); + } + + /** + * Save the new primary key value in _data. The primary key may have + * been generated by a sequence or auto-increment mechanism, and this + * merge should be done before the _postInsert() method is run, so the + * new values are available for logging, etc. + */ + $this->_data = array_merge($this->_data, $newPrimaryKey); + + /** + * Run post-INSERT logic + */ + $this->_postInsert(); + + /** + * Update the _cleanData to reflect that the data has been inserted. + */ + $this->_refresh(); + + return $primaryKey; + } + + /** + * @return mixed The primary key value(s), as an associative array if the + * key is compound, or a scalar if the key is single-column. + */ + protected function _doUpdate() + { + /** + * A read-only row cannot be saved. + */ + if ($this->_readOnly === true) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception('This row has been marked read-only'); + } + + /** + * Get expressions for a WHERE clause + * based on the primary key value(s). + */ + $where = $this->_getWhereQuery(false); + + /** + * Run pre-UPDATE logic + */ + $this->_update(); + + /** + * Compare the data to the modified fields array to discover + * which columns have been changed. + */ + $diffData = array_intersect_key($this->_data, $this->_modifiedFields); + + /** + * Were any of the changed columns part of the primary key? + */ + $pkDiffData = array_intersect_key($diffData, array_flip((array)$this->_primary)); + + /** + * Execute cascading updates against dependent tables. + * Do this only if primary key value(s) were changed. + */ + if (count($pkDiffData) > 0) { + $depTables = $this->_getTable()->getDependentTables(); + if (!empty($depTables)) { + $pkNew = $this->_getPrimaryKey(true); + $pkOld = $this->_getPrimaryKey(false); + foreach ($depTables as $tableClass) { + $t = $this->_getTableFromString($tableClass); + $t->_cascadeUpdate($this->getTableClass(), $pkOld, $pkNew); + } + } + } + + /** + * Execute the UPDATE (this may throw an exception) + * Do this only if data values were changed. + * Use the $diffData variable, so the UPDATE statement + * includes SET terms only for data values that changed. + */ + if (count($diffData) > 0) { + $this->_getTable()->update($diffData, $where); + } + + /** + * Run post-UPDATE logic. Do this before the _refresh() + * so the _postUpdate() function can tell the difference + * between changed data and clean (pre-changed) data. + */ + $this->_postUpdate(); + + /** + * Refresh the data just in case triggers in the RDBMS changed + * any columns. Also this resets the _cleanData. + */ + $this->_refresh(); + + /** + * Return the primary key value(s) as an array + * if the key is compound or a scalar if the key + * is a scalar. + */ + $primaryKey = $this->_getPrimaryKey(true); + if (count($primaryKey) == 1) { + return current($primaryKey); + } + + return $primaryKey; + } + + /** + * Deletes existing rows. + * + * @return int The number of rows deleted. + */ + public function delete() + { + /** + * A read-only row cannot be deleted. + */ + if ($this->_readOnly === true) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception('This row has been marked read-only'); + } + + $where = $this->_getWhereQuery(); + + /** + * Execute pre-DELETE logic + */ + $this->_delete(); + + /** + * Execute cascading deletes against dependent tables + */ + $depTables = $this->_getTable()->getDependentTables(); + if (!empty($depTables)) { + $pk = $this->_getPrimaryKey(); + foreach ($depTables as $tableClass) { + $t = $this->_getTableFromString($tableClass); + $t->_cascadeDelete($this->getTableClass(), $pk); + } + } + + /** + * Execute the DELETE (this may throw an exception) + */ + $result = $this->_getTable()->delete($where); + + /** + * Execute post-DELETE logic + */ + $this->_postDelete(); + + /** + * Reset all fields to null to indicate that the row is not there + */ + $this->_data = array_combine( + array_keys($this->_data), + array_fill(0, count($this->_data), null) + ); + + return $result; + } + + public function getIterator() + { + return new ArrayIterator((array) $this->_data); + } + + /** + * Returns the column/value data as an array. + * + * @return array + */ + public function toArray() + { + return (array)$this->_data; + } + + /** + * Sets all data in the row from an array. + * + * @param array $data + * @return Zend_Db_Table_Row_Abstract Provides a fluent interface + */ + public function setFromArray(array $data) + { + $data = array_intersect_key($data, $this->_data); + + foreach ($data as $columnName => $value) { + $this->__set($columnName, $value); + } + + return $this; + } + + /** + * Refreshes properties from the database. + * + * @return void + */ + public function refresh() + { + return $this->_refresh(); + } + + /** + * Retrieves an instance of the parent table. + * + * @return Zend_Db_Table_Abstract + */ + protected function _getTable() + { + if (!$this->_connected) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception('Cannot save a Row unless it is connected'); + } + return $this->_table; + } + + /** + * Retrieves an associative array of primary keys. + * + * @param bool $useDirty + * @return array + */ + protected function _getPrimaryKey($useDirty = true) + { + if (!is_array($this->_primary)) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("The primary key must be set as an array"); + } + + $primary = array_flip($this->_primary); + if ($useDirty) { + $array = array_intersect_key($this->_data, $primary); + } else { + $array = array_intersect_key($this->_cleanData, $primary); + } + if (count($primary) != count($array)) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("The specified Table '$this->_tableClass' does not have the same primary key as the Row"); + } + return $array; + } + + /** + * Constructs where statement for retrieving row(s). + * + * @param bool $useDirty + * @return array + */ + protected function _getWhereQuery($useDirty = true) + { + $where = array(); + $db = $this->_getTable()->getAdapter(); + $primaryKey = $this->_getPrimaryKey($useDirty); + $info = $this->_getTable()->info(); + $metadata = $info[Zend_Db_Table_Abstract::METADATA]; + + // retrieve recently updated row using primary keys + $where = array(); + foreach ($primaryKey as $column => $value) { + $tableName = $db->quoteIdentifier($info[Zend_Db_Table_Abstract::NAME], true); + $type = $metadata[$column]['DATA_TYPE']; + $columnName = $db->quoteIdentifier($column, true); + $where[] = $db->quoteInto("{$tableName}.{$columnName} = ?", $value, $type); + } + return $where; + } + + /** + * Refreshes properties from the database. + * + * @return void + */ + protected function _refresh() + { + $where = $this->_getWhereQuery(); + $row = $this->_getTable()->fetchRow($where); + + if (null === $row) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception('Cannot refresh row as parent is missing'); + } + + $this->_data = $row->toArray(); + $this->_cleanData = $this->_data; + $this->_modifiedFields = array(); + } + + /** + * Allows pre-insert logic to be applied to row. + * Subclasses may override this method. + * + * @return void + */ + protected function _insert() + { + } + + /** + * Allows post-insert logic to be applied to row. + * Subclasses may override this method. + * + * @return void + */ + protected function _postInsert() + { + } + + /** + * Allows pre-update logic to be applied to row. + * Subclasses may override this method. + * + * @return void + */ + protected function _update() + { + } + + /** + * Allows post-update logic to be applied to row. + * Subclasses may override this method. + * + * @return void + */ + protected function _postUpdate() + { + } + + /** + * Allows pre-delete logic to be applied to row. + * Subclasses may override this method. + * + * @return void + */ + protected function _delete() + { + } + + /** + * Allows post-delete logic to be applied to row. + * Subclasses may override this method. + * + * @return void + */ + protected function _postDelete() + { + } + + /** + * Prepares a table reference for lookup. + * + * Ensures all reference keys are set and properly formatted. + * + * @param Zend_Db_Table_Abstract $dependentTable + * @param Zend_Db_Table_Abstract $parentTable + * @param string $ruleKey + * @return array + */ + protected function _prepareReference(Zend_Db_Table_Abstract $dependentTable, Zend_Db_Table_Abstract $parentTable, $ruleKey) + { + $parentTableName = (get_class($parentTable) === 'Zend_Db_Table') ? $parentTable->getDefinitionConfigName() : get_class($parentTable); + $map = $dependentTable->getReference($parentTableName, $ruleKey); + + if (!isset($map[Zend_Db_Table_Abstract::REF_COLUMNS])) { + $parentInfo = $parentTable->info(); + $map[Zend_Db_Table_Abstract::REF_COLUMNS] = array_values((array) $parentInfo['primary']); + } + + $map[Zend_Db_Table_Abstract::COLUMNS] = (array) $map[Zend_Db_Table_Abstract::COLUMNS]; + $map[Zend_Db_Table_Abstract::REF_COLUMNS] = (array) $map[Zend_Db_Table_Abstract::REF_COLUMNS]; + + return $map; + } + + /** + * Query a dependent table to retrieve rows matching the current row. + * + * @param string|Zend_Db_Table_Abstract $dependentTable + * @param string OPTIONAL $ruleKey + * @param Zend_Db_Table_Select OPTIONAL $select + * @return Zend_Db_Table_Rowset_Abstract Query result from $dependentTable + * @throws Zend_Db_Table_Row_Exception If $dependentTable is not a table or is not loadable. + */ + public function findDependentRowset($dependentTable, $ruleKey = null, Zend_Db_Table_Select $select = null) + { + $db = $this->_getTable()->getAdapter(); + + if (is_string($dependentTable)) { + $dependentTable = $this->_getTableFromString($dependentTable); + } + + if (!$dependentTable instanceof Zend_Db_Table_Abstract) { + $type = gettype($dependentTable); + if ($type == 'object') { + $type = get_class($dependentTable); + } + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("Dependent table must be a Zend_Db_Table_Abstract, but it is $type"); + } + + // even if we are interacting between a table defined in a class and a + // table via extension, ensure to persist the definition + if (($tableDefinition = $this->_table->getDefinition()) !== null + && ($dependentTable->getDefinition() == null)) { + $dependentTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition)); + } + + if ($select === null) { + $select = $dependentTable->select(); + } else { + $select->setTable($dependentTable); + } + + $map = $this->_prepareReference($dependentTable, $this->_getTable(), $ruleKey); + + for ($i = 0; $i < count($map[Zend_Db_Table_Abstract::COLUMNS]); ++$i) { + $parentColumnName = $db->foldCase($map[Zend_Db_Table_Abstract::REF_COLUMNS][$i]); + $value = $this->_data[$parentColumnName]; + // Use adapter from dependent table to ensure correct query construction + $dependentDb = $dependentTable->getAdapter(); + $dependentColumnName = $dependentDb->foldCase($map[Zend_Db_Table_Abstract::COLUMNS][$i]); + $dependentColumn = $dependentDb->quoteIdentifier($dependentColumnName, true); + $dependentInfo = $dependentTable->info(); + $type = $dependentInfo[Zend_Db_Table_Abstract::METADATA][$dependentColumnName]['DATA_TYPE']; + $select->where("$dependentColumn = ?", $value, $type); + } + + return $dependentTable->fetchAll($select); + } + + /** + * Query a parent table to retrieve the single row matching the current row. + * + * @param string|Zend_Db_Table_Abstract $parentTable + * @param string OPTIONAL $ruleKey + * @param Zend_Db_Table_Select OPTIONAL $select + * @return Zend_Db_Table_Row_Abstract Query result from $parentTable + * @throws Zend_Db_Table_Row_Exception If $parentTable is not a table or is not loadable. + */ + public function findParentRow($parentTable, $ruleKey = null, Zend_Db_Table_Select $select = null) + { + $db = $this->_getTable()->getAdapter(); + + if (is_string($parentTable)) { + $parentTable = $this->_getTableFromString($parentTable); + } + + if (!$parentTable instanceof Zend_Db_Table_Abstract) { + $type = gettype($parentTable); + if ($type == 'object') { + $type = get_class($parentTable); + } + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("Parent table must be a Zend_Db_Table_Abstract, but it is $type"); + } + + // even if we are interacting between a table defined in a class and a + // table via extension, ensure to persist the definition + if (($tableDefinition = $this->_table->getDefinition()) !== null + && ($parentTable->getDefinition() == null)) { + $parentTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition)); + } + + if ($select === null) { + $select = $parentTable->select(); + } else { + $select->setTable($parentTable); + } + + $map = $this->_prepareReference($this->_getTable(), $parentTable, $ruleKey); + + // iterate the map, creating the proper wheres + for ($i = 0; $i < count($map[Zend_Db_Table_Abstract::COLUMNS]); ++$i) { + $dependentColumnName = $db->foldCase($map[Zend_Db_Table_Abstract::COLUMNS][$i]); + $value = $this->_data[$dependentColumnName]; + // Use adapter from parent table to ensure correct query construction + $parentDb = $parentTable->getAdapter(); + $parentColumnName = $parentDb->foldCase($map[Zend_Db_Table_Abstract::REF_COLUMNS][$i]); + $parentColumn = $parentDb->quoteIdentifier($parentColumnName, true); + $parentInfo = $parentTable->info(); + + // determine where part + $type = $parentInfo[Zend_Db_Table_Abstract::METADATA][$parentColumnName]['DATA_TYPE']; + $nullable = $parentInfo[Zend_Db_Table_Abstract::METADATA][$parentColumnName]['NULLABLE']; + if ($value === null && $nullable == true) { + $select->where("$parentColumn IS NULL"); + } elseif ($value === null && $nullable == false) { + return null; + } else { + $select->where("$parentColumn = ?", $value, $type); + } + + } + + return $parentTable->fetchRow($select); + } + + /** + * @param string|Zend_Db_Table_Abstract $matchTable + * @param string|Zend_Db_Table_Abstract $intersectionTable + * @param string OPTIONAL $callerRefRule + * @param string OPTIONAL $matchRefRule + * @param Zend_Db_Table_Select OPTIONAL $select + * @return Zend_Db_Table_Rowset_Abstract Query result from $matchTable + * @throws Zend_Db_Table_Row_Exception If $matchTable or $intersectionTable is not a table class or is not loadable. + */ + public function findManyToManyRowset($matchTable, $intersectionTable, $callerRefRule = null, + $matchRefRule = null, Zend_Db_Table_Select $select = null) + { + $db = $this->_getTable()->getAdapter(); + + if (is_string($intersectionTable)) { + $intersectionTable = $this->_getTableFromString($intersectionTable); + } + + if (!$intersectionTable instanceof Zend_Db_Table_Abstract) { + $type = gettype($intersectionTable); + if ($type == 'object') { + $type = get_class($intersectionTable); + } + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("Intersection table must be a Zend_Db_Table_Abstract, but it is $type"); + } + + // even if we are interacting between a table defined in a class and a + // table via extension, ensure to persist the definition + if (($tableDefinition = $this->_table->getDefinition()) !== null + && ($intersectionTable->getDefinition() == null)) { + $intersectionTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition)); + } + + if (is_string($matchTable)) { + $matchTable = $this->_getTableFromString($matchTable); + } + + if (! $matchTable instanceof Zend_Db_Table_Abstract) { + $type = gettype($matchTable); + if ($type == 'object') { + $type = get_class($matchTable); + } + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("Match table must be a Zend_Db_Table_Abstract, but it is $type"); + } + + // even if we are interacting between a table defined in a class and a + // table via extension, ensure to persist the definition + if (($tableDefinition = $this->_table->getDefinition()) !== null + && ($matchTable->getDefinition() == null)) { + $matchTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition)); + } + + if ($select === null) { + $select = $matchTable->select(); + } else { + $select->setTable($matchTable); + } + + // Use adapter from intersection table to ensure correct query construction + $interInfo = $intersectionTable->info(); + $interDb = $intersectionTable->getAdapter(); + $interName = $interInfo['name']; + $interSchema = isset($interInfo['schema']) ? $interInfo['schema'] : null; + $matchInfo = $matchTable->info(); + $matchName = $matchInfo['name']; + $matchSchema = isset($matchInfo['schema']) ? $matchInfo['schema'] : null; + + $matchMap = $this->_prepareReference($intersectionTable, $matchTable, $matchRefRule); + + for ($i = 0; $i < count($matchMap[Zend_Db_Table_Abstract::COLUMNS]); ++$i) { + $interCol = $interDb->quoteIdentifier('i' . '.' . $matchMap[Zend_Db_Table_Abstract::COLUMNS][$i], true); + $matchCol = $interDb->quoteIdentifier('m' . '.' . $matchMap[Zend_Db_Table_Abstract::REF_COLUMNS][$i], true); + $joinCond[] = "$interCol = $matchCol"; + } + $joinCond = implode(' AND ', $joinCond); + + $select->from(array('i' => $interName), array(), $interSchema) + ->joinInner(array('m' => $matchName), $joinCond, Zend_Db_Select::SQL_WILDCARD, $matchSchema) + ->setIntegrityCheck(false); + + $callerMap = $this->_prepareReference($intersectionTable, $this->_getTable(), $callerRefRule); + + for ($i = 0; $i < count($callerMap[Zend_Db_Table_Abstract::COLUMNS]); ++$i) { + $callerColumnName = $db->foldCase($callerMap[Zend_Db_Table_Abstract::REF_COLUMNS][$i]); + $value = $this->_data[$callerColumnName]; + $interColumnName = $interDb->foldCase($callerMap[Zend_Db_Table_Abstract::COLUMNS][$i]); + $interCol = $interDb->quoteIdentifier("i.$interColumnName", true); + $interInfo = $intersectionTable->info(); + $type = $interInfo[Zend_Db_Table_Abstract::METADATA][$interColumnName]['DATA_TYPE']; + $select->where($interDb->quoteInto("$interCol = ?", $value, $type)); + } + + $stmt = $select->query(); + + $config = array( + 'table' => $matchTable, + 'data' => $stmt->fetchAll(Zend_Db::FETCH_ASSOC), + 'rowClass' => $matchTable->getRowClass(), + 'readOnly' => false, + 'stored' => true + ); + + $rowsetClass = $matchTable->getRowsetClass(); + if (!class_exists($rowsetClass)) { + try { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($rowsetClass); + } catch (Zend_Exception $e) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception($e->getMessage(), $e->getCode(), $e); + } + } + $rowset = new $rowsetClass($config); + return $rowset; + } + + /** + * Turn magic function calls into non-magic function calls + * to the above methods. + * + * @param string $method + * @param array $args OPTIONAL Zend_Db_Table_Select query modifier + * @return Zend_Db_Table_Row_Abstract|Zend_Db_Table_Rowset_Abstract + * @throws Zend_Db_Table_Row_Exception If an invalid method is called. + */ + public function __call($method, array $args) + { + $matches = array(); + + if (count($args) && $args[0] instanceof Zend_Db_Table_Select) { + $select = $args[0]; + } else { + $select = null; + } + + /** + * Recognize methods for Has-Many cases: + * findParent() + * findParentBy() + * Use the non-greedy pattern repeat modifier e.g. \w+? + */ + if (preg_match('/^findParent(\w+?)(?:By(\w+))?$/', $method, $matches)) { + $class = $matches[1]; + $ruleKey1 = isset($matches[2]) ? $matches[2] : null; + return $this->findParentRow($class, $ruleKey1, $select); + } + + /** + * Recognize methods for Many-to-Many cases: + * findVia() + * findViaBy() + * findViaByAnd() + * Use the non-greedy pattern repeat modifier e.g. \w+? + */ + if (preg_match('/^find(\w+?)Via(\w+?)(?:By(\w+?)(?:And(\w+))?)?$/', $method, $matches)) { + $class = $matches[1]; + $viaClass = $matches[2]; + $ruleKey1 = isset($matches[3]) ? $matches[3] : null; + $ruleKey2 = isset($matches[4]) ? $matches[4] : null; + return $this->findManyToManyRowset($class, $viaClass, $ruleKey1, $ruleKey2, $select); + } + + /** + * Recognize methods for Belongs-To cases: + * find() + * findBy() + * Use the non-greedy pattern repeat modifier e.g. \w+? + */ + if (preg_match('/^find(\w+?)(?:By(\w+))?$/', $method, $matches)) { + $class = $matches[1]; + $ruleKey1 = isset($matches[2]) ? $matches[2] : null; + return $this->findDependentRowset($class, $ruleKey1, $select); + } + + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception("Unrecognized method '$method()'"); + } + + + /** + * _getTableFromString + * + * @param string $tableName + * @return Zend_Db_Table_Abstract + */ + protected function _getTableFromString($tableName) + { + + if ($this->_table instanceof Zend_Db_Table_Abstract) { + $tableDefinition = $this->_table->getDefinition(); + + if ($tableDefinition !== null && $tableDefinition->hasTableConfig($tableName)) { + return new Zend_Db_Table($tableName, $tableDefinition); + } + } + + // assume the tableName is the class name + if (!class_exists($tableName)) { + try { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($tableName); + } catch (Zend_Exception $e) { + require_once 'Zend/Db/Table/Row/Exception.php'; + throw new Zend_Db_Table_Row_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + $options = array(); + + if (($table = $this->_getTable())) { + $options['db'] = $table->getAdapter(); + } + + if (isset($tableDefinition) && $tableDefinition !== null) { + $options[Zend_Db_Table_Abstract::DEFINITION] = $tableDefinition; + } + + return new $tableName($options); + } + +} diff --git a/library/Zend/Db/Table/Row/Exception.php b/library/Zend/Db/Table/Row/Exception.php new file mode 100644 index 00000000..df82c081 --- /dev/null +++ b/library/Zend/Db/Table/Row/Exception.php @@ -0,0 +1,38 @@ +_table = $config['table']; + $this->_tableClass = get_class($this->_table); + } + if (isset($config['rowClass'])) { + $this->_rowClass = $config['rowClass']; + } + if (!class_exists($this->_rowClass)) { + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($this->_rowClass); + } + if (isset($config['data'])) { + $this->_data = $config['data']; + } + if (isset($config['readOnly'])) { + $this->_readOnly = $config['readOnly']; + } + if (isset($config['stored'])) { + $this->_stored = $config['stored']; + } + + // set the count of rows + $this->_count = count($this->_data); + + $this->init(); + } + + /** + * Store data, class names, and state in serialized object + * + * @return array + */ + public function __sleep() + { + return array('_data', '_tableClass', '_rowClass', '_pointer', '_count', '_rows', '_stored', + '_readOnly'); + } + + /** + * Setup to do on wakeup. + * A de-serialized Rowset should not be assumed to have access to a live + * database connection, so set _connected = false. + * + * @return void + */ + public function __wakeup() + { + $this->_connected = false; + } + + /** + * Initialize object + * + * Called from {@link __construct()} as final step of object instantiation. + * + * @return void + */ + public function init() + { + } + + /** + * Return the connected state of the rowset. + * + * @return boolean + */ + public function isConnected() + { + return $this->_connected; + } + + /** + * Returns the table object, or null if this is disconnected rowset + * + * @return Zend_Db_Table_Abstract + */ + public function getTable() + { + return $this->_table; + } + + /** + * Set the table object, to re-establish a live connection + * to the database for a Rowset that has been de-serialized. + * + * @param Zend_Db_Table_Abstract $table + * @return boolean + * @throws Zend_Db_Table_Row_Exception + */ + public function setTable(Zend_Db_Table_Abstract $table) + { + $this->_table = $table; + $this->_connected = false; + // @todo This works only if we have iterated through + // the result set once to instantiate the rows. + foreach ($this as $row) { + $connected = $row->setTable($table); + if ($connected == true) { + $this->_connected = true; + } + } + return $this->_connected; + } + + /** + * Query the class name of the Table object for which this + * Rowset was created. + * + * @return string + */ + public function getTableClass() + { + return $this->_tableClass; + } + + /** + * Rewind the Iterator to the first element. + * Similar to the reset() function for arrays in PHP. + * Required by interface Iterator. + * + * @return Zend_Db_Table_Rowset_Abstract Fluent interface. + */ + public function rewind() + { + $this->_pointer = 0; + return $this; + } + + /** + * Return the current element. + * Similar to the current() function for arrays in PHP + * Required by interface Iterator. + * + * @return Zend_Db_Table_Row_Abstract current element from the collection + */ + public function current() + { + if ($this->valid() === false) { + return null; + } + + // return the row object + return $this->_loadAndReturnRow($this->_pointer); + } + + /** + * Return the identifying key of the current element. + * Similar to the key() function for arrays in PHP. + * Required by interface Iterator. + * + * @return int + */ + public function key() + { + return $this->_pointer; + } + + /** + * Move forward to next element. + * Similar to the next() function for arrays in PHP. + * Required by interface Iterator. + * + * @return void + */ + public function next() + { + ++$this->_pointer; + } + + /** + * Check if there is a current element after calls to rewind() or next(). + * Used to check if we've iterated to the end of the collection. + * Required by interface Iterator. + * + * @return bool False if there's nothing more to iterate over + */ + public function valid() + { + return $this->_pointer >= 0 && $this->_pointer < $this->_count; + } + + /** + * Returns the number of elements in the collection. + * + * Implements Countable::count() + * + * @return int + */ + public function count() + { + return $this->_count; + } + + /** + * Take the Iterator to position $position + * Required by interface SeekableIterator. + * + * @param int $position the position to seek to + * @return Zend_Db_Table_Rowset_Abstract + * @throws Zend_Db_Table_Rowset_Exception + */ + public function seek($position) + { + $position = (int) $position; + if ($position < 0 || $position >= $this->_count) { + require_once 'Zend/Db/Table/Rowset/Exception.php'; + throw new Zend_Db_Table_Rowset_Exception("Illegal index $position"); + } + $this->_pointer = $position; + return $this; + } + + /** + * Check if an offset exists + * Required by the ArrayAccess implementation + * + * @param string $offset + * @return boolean + */ + public function offsetExists($offset) + { + return isset($this->_data[(int) $offset]); + } + + /** + * Get the row for the given offset + * Required by the ArrayAccess implementation + * + * @param string $offset + * @return Zend_Db_Table_Row_Abstract + */ + public function offsetGet($offset) + { + $offset = (int) $offset; + if ($offset < 0 || $offset >= $this->_count) { + require_once 'Zend/Db/Table/Rowset/Exception.php'; + throw new Zend_Db_Table_Rowset_Exception("Illegal index $offset"); + } + $this->_pointer = $offset; + + return $this->current(); + } + + /** + * Does nothing + * Required by the ArrayAccess implementation + * + * @param string $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + } + + /** + * Does nothing + * Required by the ArrayAccess implementation + * + * @param string $offset + */ + public function offsetUnset($offset) + { + } + + /** + * Returns a Zend_Db_Table_Row from a known position into the Iterator + * + * @param int $position the position of the row expected + * @param bool $seek wether or not seek the iterator to that position after + * @return Zend_Db_Table_Row + * @throws Zend_Db_Table_Rowset_Exception + */ + public function getRow($position, $seek = false) + { + try { + $row = $this->_loadAndReturnRow($position); + } catch (Zend_Db_Table_Rowset_Exception $e) { + require_once 'Zend/Db/Table/Rowset/Exception.php'; + throw new Zend_Db_Table_Rowset_Exception('No row could be found at position ' . (int) $position, 0, $e); + } + + if ($seek == true) { + $this->seek($position); + } + + return $row; + } + + /** + * Returns all data as an array. + * + * Updates the $_data property with current row object values. + * + * @return array + */ + public function toArray() + { + // @todo This works only if we have iterated through + // the result set once to instantiate the rows. + foreach ($this->_rows as $i => $row) { + $this->_data[$i] = $row->toArray(); + } + return $this->_data; + } + + protected function _loadAndReturnRow($position) + { + if (!isset($this->_data[$position])) { + require_once 'Zend/Db/Table/Rowset/Exception.php'; + throw new Zend_Db_Table_Rowset_Exception("Data for provided position does not exist"); + } + + // do we already have a row object for this position? + if (empty($this->_rows[$position])) { + $this->_rows[$position] = new $this->_rowClass( + array( + 'table' => $this->_table, + 'data' => $this->_data[$position], + 'stored' => $this->_stored, + 'readOnly' => $this->_readOnly + ) + ); + } + + // return the row object + return $this->_rows[$position]; + } + +} diff --git a/library/Zend/Db/Table/Rowset/Exception.php b/library/Zend/Db/Table/Rowset/Exception.php new file mode 100644 index 00000000..d775a402 --- /dev/null +++ b/library/Zend/Db/Table/Rowset/Exception.php @@ -0,0 +1,37 @@ +getAdapter()); + + $this->setTable($table); + } + + /** + * Return the table that created this select object + * + * @return Zend_Db_Table_Abstract + */ + public function getTable() + { + return $this->_table; + } + + /** + * Sets the primary table name and retrieves the table schema. + * + * @param Zend_Db_Table_Abstract $adapter + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function setTable(Zend_Db_Table_Abstract $table) + { + $this->_adapter = $table->getAdapter(); + $this->_info = $table->info(); + $this->_table = $table; + + return $this; + } + + /** + * Sets the integrity check flag. + * + * Setting this flag to false skips the checks for table joins, allowing + * 'hybrid' table rows to be created. + * + * @param Zend_Db_Table_Abstract $adapter + * @return Zend_Db_Select This Zend_Db_Select object. + */ + public function setIntegrityCheck($flag = true) + { + $this->_integrityCheck = $flag; + return $this; + } + + /** + * Tests query to determine if expressions or aliases columns exist. + * + * @return boolean + */ + public function isReadOnly() + { + $readOnly = false; + $fields = $this->getPart(Zend_Db_Table_Select::COLUMNS); + $cols = $this->_info[Zend_Db_Table_Abstract::COLS]; + + if (!count($fields)) { + return $readOnly; + } + + foreach ($fields as $columnEntry) { + $column = $columnEntry[1]; + $alias = $columnEntry[2]; + + if ($alias !== null) { + $column = $alias; + } + + switch (true) { + case ($column == self::SQL_WILDCARD): + break; + + case ($column instanceof Zend_Db_Expr): + case (!in_array($column, $cols)): + $readOnly = true; + break 2; + } + } + + return $readOnly; + } + + /** + * Adds a FROM table and optional columns to the query. + * + * The table name can be expressed + * + * @param array|string|Zend_Db_Expr|Zend_Db_Table_Abstract $name The table name or an + associative array relating + table name to correlation + 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_Table_Select This Zend_Db_Table_Select object. + */ + public function from($name, $cols = self::SQL_WILDCARD, $schema = null) + { + if ($name instanceof Zend_Db_Table_Abstract) { + $info = $name->info(); + $name = $info[Zend_Db_Table_Abstract::NAME]; + if (isset($info[Zend_Db_Table_Abstract::SCHEMA])) { + $schema = $info[Zend_Db_Table_Abstract::SCHEMA]; + } + } + + return $this->joinInner($name, null, $cols, $schema); + } + + /** + * Performs a validation on the select query before passing back to the parent class. + * Ensures that only columns from the primary Zend_Db_Table are returned in the result. + * + * @return string|null This object as a SELECT string (or null if a string cannot be produced) + */ + public function assemble() + { + $fields = $this->getPart(Zend_Db_Table_Select::COLUMNS); + $primary = $this->_info[Zend_Db_Table_Abstract::NAME]; + $schema = $this->_info[Zend_Db_Table_Abstract::SCHEMA]; + + + if (count($this->_parts[self::UNION]) == 0) { + + // If no fields are specified we assume all fields from primary table + if (!count($fields)) { + $this->from($primary, self::SQL_WILDCARD, $schema); + $fields = $this->getPart(Zend_Db_Table_Select::COLUMNS); + } + + $from = $this->getPart(Zend_Db_Table_Select::FROM); + + if ($this->_integrityCheck !== false) { + foreach ($fields as $columnEntry) { + list($table, $column) = $columnEntry; + + // Check each column to ensure it only references the primary table + if ($column) { + if (!isset($from[$table]) || $from[$table]['tableName'] != $primary) { + require_once 'Zend/Db/Table/Select/Exception.php'; + throw new Zend_Db_Table_Select_Exception('Select query cannot join with another table'); + } + } + } + } + } + + return parent::assemble(); + } +} \ No newline at end of file diff --git a/library/Zend/Db/Table/Select/Exception.php b/library/Zend/Db/Table/Select/Exception.php new file mode 100644 index 00000000..4a0bab92 --- /dev/null +++ b/library/Zend/Db/Table/Select/Exception.php @@ -0,0 +1,39 @@ + tags, cleans up newlines and indents, and runs + * htmlentities() before output. + * + * @param mixed $var The variable to dump. + * @param string $label OPTIONAL Label to prepend to output. + * @param bool $echo OPTIONAL Echo output if true. + * @return string + */ + public static function dump($var, $label=null, $echo=true) + { + // format the label + $label = ($label===null) ? '' : rtrim($label) . ' '; + + // var_dump the variable into a buffer and keep the output + ob_start(); + var_dump($var); + $output = ob_get_clean(); + + // neaten the newlines and indents + $output = preg_replace("/\]\=\>\n(\s+)/m", "] => ", $output); + if (self::getSapi() == 'cli') { + $output = PHP_EOL . $label + . PHP_EOL . $output + . PHP_EOL; + } else { + if(!extension_loaded('xdebug')) { + $output = htmlspecialchars($output, ENT_QUOTES); + } + + $output = '
'
+                    . $label
+                    . $output
+                    . '
'; + } + + if ($echo) { + echo($output); + } + return $output; + } + +} diff --git a/library/Zend/Dojo.php b/library/Zend/Dojo.php new file mode 100644 index 00000000..3ec3b593 --- /dev/null +++ b/library/Zend/Dojo.php @@ -0,0 +1,87 @@ +addPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator', 'decorator') + ->addPrefixPath('Zend_Dojo_Form_Element', 'Zend/Dojo/Form/Element', 'element') + ->addElementPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator', 'decorator') + ->addDisplayGroupPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator') + ->setDefaultDisplayGroupClass('Zend_Dojo_Form_DisplayGroup'); + + foreach ($form->getSubForms() as $subForm) { + self::enableForm($subForm); + } + + if (null !== ($view = $form->getView())) { + self::enableView($view); + } + } + + /** + * Dojo-enable a view instance + * + * @param Zend_View_Interface $view + * @return void + */ + public static function enableView(Zend_View_Interface $view) + { + if (false === $view->getPluginLoader('helper')->getPaths('Zend_Dojo_View_Helper')) { + $view->addHelperPath('Zend/Dojo/View/Helper', 'Zend_Dojo_View_Helper'); + } + } +} + diff --git a/library/Zend/Dojo/BuildLayer.php b/library/Zend/Dojo/BuildLayer.php new file mode 100644 index 00000000..2943e071 --- /dev/null +++ b/library/Zend/Dojo/BuildLayer.php @@ -0,0 +1,536 @@ + 'release', + 'optimize' => 'shrinksafe', + 'layerOptimize' => 'shrinksafe', + 'copyTests' => false, + 'loader' => 'default', + 'cssOptimize' => 'comments', + ); + + /** + * Associative array of module/path pairs for the build profile + * @var array + */ + protected $_profilePrefixes = array(); + + /** + * Zend_View reference + * @var Zend_View_Interface + */ + protected $_view; + + /** + * Constructor + * + * @param array|Zend_Config $options + * @return void + * @throws Zend_Dojo_Exception for invalid option argument + */ + public function __construct($options = null) + { + if (null !== $options) { + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } elseif (!is_array($options)) { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('Invalid options provided to constructor'); + } + $this->setOptions($options); + } + } + + /** + * Set options + * + * Proxies to any setter that matches an option key. + * + * @param array $options + * @return Zend_Dojo_BuildLayer + */ + public function setOptions(array $options) + { + $methods = get_class_methods($this); + foreach ($options as $key => $value) { + $method = 'set' . ucfirst($key); + if (in_array($method, $methods)) { + $this->$method($value); + } + } + return $this; + } + + /** + * Set View object + * + * @param Zend_View_Interface $view + * @return Zend_Dojo_BuildLayer + */ + public function setView(Zend_View_Interface $view) + { + $this->_view = $view; + return $this; + } + + /** + * Retrieve view object + * + * @return Zend_View_Interface|null + */ + public function getView() + { + return $this->_view; + } + + /** + * Set dojo() view helper instance + * + * @param Zend_Dojo_View_Helper_Dojo_Container $helper + * @return Zend_Dojo_BuildLayer + */ + public function setDojoHelper(Zend_Dojo_View_Helper_Dojo_Container $helper) + { + $this->_dojo = $helper; + return $this; + } + + /** + * Retrieve dojo() view helper instance + * + * Will retrieve it from the view object if not registered. + * + * @return Zend_Dojo_View_Helper_Dojo_Container + * @throws Zend_Dojo_Exception if not registered and no view object found + */ + public function getDojoHelper() + { + if (null === $this->_dojo) { + if (null === ($view = $this->getView())) { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('View object not registered; cannot retrieve dojo helper'); + } + $helper = $view->getHelper('dojo'); + $this->setDojoHelper($view->dojo()); + } + return $this->_dojo; + } + + /** + * Set custom layer name; e.g. "custom.main" + * + * @param string $name + * @return Zend_Dojo_BuildLayer + */ + public function setLayerName($name) + { + if (!preg_match('/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/i', $name)) { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('Invalid layer name provided; must be of form[a-z][a-z0-9_](\.[a-z][a-z0-9_])+'); + } + $this->_layerName = $name; + return $this; + } + + /** + * Retrieve custom layer name + * + * @return string|null + */ + public function getLayerName() + { + return $this->_layerName; + } + + /** + * Set the path to the custom layer script + * + * Should be a path relative to dojo.js + * + * @param string $path + * @return Zend_Dojo_BuildLayer + */ + public function setLayerScriptPath($path) + { + $this->_layerScriptPath = (string) $path; + return $this; + } + + /** + * Get custom layer script path + * + * @return string|null + */ + public function getLayerScriptPath() + { + return $this->_layerScriptPath; + } + + /** + * Set flag indicating whether or not to consume JS aggregated in dojo() + * view helper + * + * @param bool $flag + * @return Zend_Dojo_BuildLayer + */ + public function setConsumeJavascript($flag) + { + $this->_consumeJavascript = (bool) $flag; + return $this; + } + + /** + * Get flag indicating whether or not to consume JS aggregated in dojo() + * view helper + * + * @return bool + */ + public function consumeJavascript() + { + return $this->_consumeJavascript; + } + + /** + * Set flag indicating whether or not to consume dojo.addOnLoad events + * aggregated in dojo() view helper + * + * @param bool $flag + * @return Zend_Dojo_BuildLayer + */ + public function setConsumeOnLoad($flag) + { + $this->_consumeOnLoad = (bool) $flag; + return $this; + } + + /** + * Get flag indicating whether or not to consume dojo.addOnLoad events aggregated in dojo() view helper + * + * @return bool + */ + public function consumeOnLoad() + { + return $this->_consumeOnLoad; + } + + /** + * Set many build profile options at once + * + * @param array $options + * @return Zend_Dojo_BuildLayer + */ + public function setProfileOptions(array $options) + { + $this->_profileOptions += $options; + return $this; + } + + /** + * Add many build profile options at once + * + * @param array $options + * @return Zend_Dojo_BuildLayer + */ + public function addProfileOptions(array $options) + { + $this->_profileOptions = $this->_profileOptions + $options; + return $this; + } + + /** + * Add a single build profile option + * + * @param string $key + * @param value $value + * @return Zend_Dojo_BuildLayer + */ + public function addProfileOption($key, $value) + { + $this->_profileOptions[(string) $key] = $value; + return $this; + } + + /** + * Is a given build profile option set? + * + * @param string $key + * @return bool + */ + public function hasProfileOption($key) + { + return array_key_exists((string) $key, $this->_profileOptions); + } + + /** + * Retrieve a single build profile option + * + * Returns null if profile option does not exist. + * + * @param string $key + * @return mixed + */ + public function getProfileOption($key) + { + if ($this->hasProfileOption($key)) { + return $this->_profileOptions[(string) $key]; + } + return null; + } + + /** + * Get all build profile options + * + * @return array + */ + public function getProfileOptions() + { + return $this->_profileOptions; + } + + /** + * Remove a build profile option + * + * @param string $name + * @return Zend_Dojo_BuildLayer + */ + public function removeProfileOption($name) + { + if ($this->hasProfileOption($name)) { + unset($this->_profileOptions[(string) $name]); + } + return $this; + } + + /** + * Remove all build profile options + * + * @return Zend_Dojo_BuildLayer + */ + public function clearProfileOptions() + { + $this->_profileOptions = array(); + return $this; + } + + /** + * Add a build profile dependency prefix + * + * If just the prefix is passed, sets path to "../$prefix". + * + * @param string $prefix + * @param null|string $path + * @return Zend_Dojo_BuildLayer + */ + public function addProfilePrefix($prefix, $path = null) + { + if (null === $path) { + $path = '../' . $prefix; + } + $this->_profilePrefixes[$prefix] = array($prefix, $path); + return $this; + } + + /** + * Set multiple dependency prefixes for bulid profile + * + * @param array $prefixes + * @return Zend_Dojo_BuildLayer + */ + public function setProfilePrefixes(array $prefixes) + { + foreach ($prefixes as $prefix => $path) { + $this->addProfilePrefix($prefix, $path); + } + return $this; + } + + /** + * Get build profile dependency prefixes + * + * @return array + */ + public function getProfilePrefixes() + { + $layerName = $this->getLayerName(); + if (null !== $layerName) { + $prefix = $this->_getPrefix($layerName); + if (!array_key_exists($prefix, $this->_profilePrefixes)) { + $this->addProfilePrefix($prefix); + } + } + $view = $this->getView(); + if (!empty($view)) { + $helper = $this->getDojoHelper(); + if ($helper) { + $modules = $helper->getModules(); + foreach ($modules as $module) { + $prefix = $this->_getPrefix($module); + if (!array_key_exists($prefix, $this->_profilePrefixes)) { + $this->addProfilePrefix($prefix); + } + } + } + } + return $this->_profilePrefixes; + } + + /** + * Generate module layer script + * + * @return string + */ + public function generateLayerScript() + { + $helper = $this->getDojoHelper(); + $layerName = $this->getLayerName(); + $modulePaths = $helper->getModulePaths(); + $modules = $helper->getModules(); + $onLoadActions = $helper->getOnLoadActions(); + $javascript = $helper->getJavascript(); + + $content = 'dojo.provide("' . $layerName . '");' . "\n\n(function(){\n"; + + foreach ($modulePaths as $module => $path) { + $content .= sprintf("dojo.registerModulePath(\"%s\", \"%s\");\n", $module, $path); + } + foreach ($modules as $module) { + $content .= sprintf("dojo.require(\"%s\");\n", $module); + } + + if ($this->consumeOnLoad()) { + foreach ($helper->getOnLoadActions() as $callback) { + $content .= sprintf("dojo.addOnLoad(%s);\n", $callback); + } + } + if ($this->consumeJavascript()) { + $javascript = implode("\n", $helper->getJavascript()); + if (!empty($javascript)) { + $content .= "\n" . $javascript . "\n"; + } + } + + $content .= "})();"; + + return $content; + } + + /** + * Generate build profile + * + * @return string + */ + public function generateBuildProfile() + { + $profileOptions = $this->getProfileOptions(); + $layerName = $this->getLayerName(); + $layerScriptPath = $this->getLayerScriptPath(); + $profilePrefixes = $this->getProfilePrefixes(); + + if (!array_key_exists('releaseName', $profileOptions)) { + $profileOptions['releaseName'] = substr($layerName, 0, strpos($layerName, '.')); + } + + $profile = $profileOptions; + $profile['layers'] = array(array( + 'name' => $layerScriptPath, + 'layerDependencies' => array(), + 'dependencies' => array($layerName), + )); + $profile['prefixes'] = array_values($profilePrefixes); + + return 'dependencies = ' . $this->_filterJsonProfileToJavascript($profile) . ';'; + } + + /** + * Retrieve module prefix + * + * @param string $module + * @return void + */ + protected function _getPrefix($module) + { + $segments = explode('.', $module, 2); + return $segments[0]; + } + + /** + * Filter a JSON build profile to JavaScript + * + * @param string $profile + * @return string + */ + protected function _filterJsonProfileToJavascript($profile) + { + require_once 'Zend/Json.php'; + $profile = Zend_Json::encode($profile); + $profile = trim($profile, '"'); + $profile = preg_replace('/' . preg_quote('\\') . '/', '', $profile); + return $profile; + } +} diff --git a/library/Zend/Dojo/Data.php b/library/Zend/Dojo/Data.php new file mode 100644 index 00000000..6c908992 --- /dev/null +++ b/library/Zend/Dojo/Data.php @@ -0,0 +1,563 @@ +setIdentifier($identifier); + } + if (null !== $items) { + $this->setItems($items); + } + if (null !== $label) { + $this->setLabel($label); + } + } + + /** + * Set the items to collect + * + * @param array|Traversable $items + * @return Zend_Dojo_Data + */ + public function setItems($items) + { + $this->clearItems(); + return $this->addItems($items); + } + + /** + * Set an individual item, optionally by identifier (overwrites) + * + * @param array|object $item + * @param string|null $identifier + * @return Zend_Dojo_Data + */ + public function setItem($item, $id = null) + { + $item = $this->_normalizeItem($item, $id); + $this->_items[$item['id']] = $item['data']; + return $this; + } + + /** + * Add an individual item, optionally by identifier + * + * @param array|object $item + * @param string|null $id + * @return Zend_Dojo_Data + */ + public function addItem($item, $id = null) + { + $item = $this->_normalizeItem($item, $id); + + if ($this->hasItem($item['id'])) { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('Overwriting items using addItem() is not allowed'); + } + + $this->_items[$item['id']] = $item['data']; + + return $this; + } + + /** + * Add multiple items at once + * + * @param array|Traversable $items + * @return Zend_Dojo_Data + */ + public function addItems($items) + { + if (!is_array($items) && (!is_object($items) || !($items instanceof Traversable))) { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('Only arrays and Traversable objects may be added to ' . __CLASS__); + } + + foreach ($items as $item) { + $this->addItem($item); + } + + return $this; + } + + /** + * Get all items as an array + * + * Serializes items to arrays. + * + * @return array + */ + public function getItems() + { + return $this->_items; + } + + /** + * Does an item with the given identifier exist? + * + * @param string|int $id + * @return bool + */ + public function hasItem($id) + { + return array_key_exists($id, $this->_items); + } + + /** + * Retrieve an item by identifier + * + * Item retrieved will be flattened to an array. + * + * @param string $id + * @return array + */ + public function getItem($id) + { + if (!$this->hasItem($id)) { + return null; + } + + return $this->_items[$id]; + } + + /** + * Remove item by identifier + * + * @param string $id + * @return Zend_Dojo_Data + */ + public function removeItem($id) + { + if ($this->hasItem($id)) { + unset($this->_items[$id]); + } + + return $this; + } + + /** + * Remove all items at once + * + * @return Zend_Dojo_Data + */ + public function clearItems() + { + $this->_items = array(); + return $this; + } + + + /** + * Set identifier for item lookups + * + * @param string|int|null $identifier + * @return Zend_Dojo_Data + */ + public function setIdentifier($identifier) + { + if (null === $identifier) { + $this->_identifier = null; + } elseif (is_string($identifier)) { + $this->_identifier = $identifier; + } elseif (is_numeric($identifier)) { + $this->_identifier = (int) $identifier; + } else { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('Invalid identifier; please use a string or integer'); + } + + return $this; + } + + /** + * Retrieve current item identifier + * + * @return string|int|null + */ + public function getIdentifier() + { + return $this->_identifier; + } + + + /** + * Set label to use for displaying item associations + * + * @param string|null $label + * @return Zend_Dojo_Data + */ + public function setLabel($label) + { + if (null === $label) { + $this->_label = null; + } else { + $this->_label = (string) $label; + } + return $this; + } + + /** + * Retrieve item association label + * + * @return string|null + */ + public function getLabel() + { + return $this->_label; + } + + /** + * Set metadata by key or en masse + * + * @param string|array $spec + * @param mixed $value + * @return Zend_Dojo_Data + */ + public function setMetadata($spec, $value = null) + { + if (is_string($spec) && (null !== $value)) { + $this->_metadata[$spec] = $value; + } elseif (is_array($spec)) { + foreach ($spec as $key => $value) { + $this->setMetadata($key, $value); + } + } + return $this; + } + + /** + * Get metadata item or all metadata + * + * @param null|string $key Metadata key when pulling single metadata item + * @return mixed + */ + public function getMetadata($key = null) + { + if (null === $key) { + return $this->_metadata; + } + + if (array_key_exists($key, $this->_metadata)) { + return $this->_metadata[$key]; + } + + return null; + } + + /** + * Clear individual or all metadata item(s) + * + * @param null|string $key + * @return Zend_Dojo_Data + */ + public function clearMetadata($key = null) + { + if (null === $key) { + $this->_metadata = array(); + } elseif (array_key_exists($key, $this->_metadata)) { + unset($this->_metadata[$key]); + } + return $this; + } + + /** + * Load object from array + * + * @param array $data + * @return Zend_Dojo_Data + */ + public function fromArray(array $data) + { + if (array_key_exists('identifier', $data)) { + $this->setIdentifier($data['identifier']); + } + if (array_key_exists('label', $data)) { + $this->setLabel($data['label']); + } + if (array_key_exists('items', $data) && is_array($data['items'])) { + $this->setItems($data['items']); + } else { + $this->clearItems(); + } + return $this; + } + + /** + * Load object from JSON + * + * @param string $json + * @return Zend_Dojo_Data + */ + public function fromJson($json) + { + if (!is_string($json)) { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('fromJson() expects JSON input'); + } + require_once 'Zend/Json.php'; + $data = Zend_Json::decode($json); + return $this->fromArray($data); + } + + /** + * Seralize entire data structure, including identifier and label, to array + * + * @return array + */ + public function toArray() + { + if (null === ($identifier = $this->getIdentifier())) { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('Serialization requires that an identifier be present in the object; first call setIdentifier()'); + } + + $array = array( + 'identifier' => $identifier, + 'items' => array_values($this->getItems()), + ); + + $metadata = $this->getMetadata(); + if (!empty($metadata)) { + foreach ($metadata as $key => $value) { + $array[$key] = $value; + } + } + + if (null !== ($label = $this->getLabel())) { + $array['label'] = $label; + } + + return $array; + } + + /** + * Serialize to JSON (dojo.data format) + * + * @return string + */ + public function toJson() + { + require_once 'Zend/Json.php'; + return Zend_Json::encode($this->toArray()); + } + + /** + * Serialize to string (proxy to {@link toJson()}) + * + * @return string + */ + public function __toString() + { + return $this->toJson(); + } + + /** + * ArrayAccess: does offset exist? + * + * @param string|int $offset + * @return bool + */ + public function offsetExists($offset) + { + return (null !== $this->getItem($offset)); + } + + /** + * ArrayAccess: retrieve by offset + * + * @param string|int $offset + * @return array + */ + public function offsetGet($offset) + { + return $this->getItem($offset); + } + + /** + * ArrayAccess: set value by offset + * + * @param string $offset + * @param array|object|null $value + * @return void + */ + public function offsetSet($offset, $value) + { + $this->setItem($value, $offset); + } + + /** + * ArrayAccess: unset value by offset + * + * @param string $offset + * @return void + */ + public function offsetUnset($offset) + { + $this->removeItem($offset); + } + + /** + * Iterator: get current value + * + * @return array + */ + public function current() + { + return current($this->_items); + } + + /** + * Iterator: get current key + * + * @return string|int + */ + public function key() + { + return key($this->_items); + } + + /** + * Iterator: get next item + * + * @return void + */ + public function next() + { + return next($this->_items); + } + + /** + * Iterator: rewind to first value in collection + * + * @return void + */ + public function rewind() + { + return reset($this->_items); + } + + /** + * Iterator: is item valid? + * + * @return bool + */ + public function valid() + { + return (bool) $this->current(); + } + + /** + * Countable: how many items are present + * + * @return int + */ + public function count() + { + return count($this->_items); + } + + /** + * Normalize an item to attach to the collection + * + * @param array|object $item + * @param string|int|null $id + * @return array + */ + protected function _normalizeItem($item, $id) + { + if (null === ($identifier = $this->getIdentifier())) { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('You must set an identifier prior to adding items'); + } + + if (!is_object($item) && !is_array($item)) { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('Only arrays and objects may be attached'); + } + + if (is_object($item)) { + if (method_exists($item, 'toArray')) { + $item = $item->toArray(); + } else { + $item = get_object_vars($item); + } + } + + if ((null === $id) && !array_key_exists($identifier, $item)) { + require_once 'Zend/Dojo/Exception.php'; + throw new Zend_Dojo_Exception('Item must contain a column matching the currently set identifier'); + } elseif (null === $id) { + $id = $item[$identifier]; + } else { + $item[$identifier] = $id; + } + + return array( + 'id' => $id, + 'data' => $item, + ); + } +} diff --git a/library/Zend/Dojo/Exception.php b/library/Zend/Dojo/Exception.php new file mode 100644 index 00000000..2aab0152 --- /dev/null +++ b/library/Zend/Dojo/Exception.php @@ -0,0 +1,35 @@ +addPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator', 'decorator') + ->addPrefixPath('Zend_Dojo_Form_Element', 'Zend/Dojo/Form/Element', 'element') + ->addElementPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator', 'decorator') + ->addDisplayGroupPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator') + ->setDefaultDisplayGroupClass('Zend_Dojo_Form_DisplayGroup'); + parent::__construct($options); + } + + /** + * Load the default decorators + * + * @return void + */ + public function loadDefaultDecorators() + { + if ($this->loadDefaultDecoratorsIsDisabled()) { + return; + } + + $decorators = $this->getDecorators(); + if (empty($decorators)) { + $this->addDecorator('FormElements') + ->addDecorator('HtmlTag', array('tag' => 'dl', 'class' => 'zend_form_dojo')) + ->addDecorator('DijitForm'); + } + } + + /** + * Set the view object + * + * Ensures that the view object has the dojo view helper path set. + * + * @param Zend_View_Interface $view + * @return Zend_Dojo_Form_Element_Dijit + */ + public function setView(Zend_View_Interface $view = null) + { + if (null !== $view) { + if (false === $view->getPluginLoader('helper')->getPaths('Zend_Dojo_View_Helper')) { + $view->addHelperPath('Zend/Dojo/View/Helper', 'Zend_Dojo_View_Helper'); + } + } + return parent::setView($view); + } +} diff --git a/library/Zend/Dojo/Form/Decorator/AccordionContainer.php b/library/Zend/Dojo/Form/Decorator/AccordionContainer.php new file mode 100644 index 00000000..8feb0dd9 --- /dev/null +++ b/library/Zend/Dojo/Form/Decorator/AccordionContainer.php @@ -0,0 +1,43 @@ +_helper) { + require_once 'Zend/Form/Decorator/Exception.php'; + throw new Zend_Form_Decorator_Exception('No view helper specified fo DijitContainer decorator'); + } + return $this->_helper; + } + + /** + * Get element attributes + * + * @return array + */ + public function getAttribs() + { + if (null === $this->_attribs) { + $attribs = $this->getElement()->getAttribs(); + if (array_key_exists('dijitParams', $attribs)) { + unset($attribs['dijitParams']); + } + $this->_attribs = $attribs; + } + return $this->_attribs; + } + + /** + * Get dijit option parameters + * + * @return array + */ + public function getDijitParams() + { + if (null === $this->_dijitParams) { + $attribs = $this->getElement()->getAttribs(); + if (array_key_exists('dijitParams', $attribs)) { + $this->_dijitParams = $attribs['dijitParams']; + } else { + $this->_dijitParams = array(); + } + + $options = $this->getOptions(); + if (array_key_exists('dijitParams', $options)) { + $this->_dijitParams = array_merge($this->_dijitParams, $options['dijitParams']); + $this->removeOption('dijitParams'); + } + } + + // Ensure we have a title param + if (!array_key_exists('title', $this->_dijitParams)) { + $this->_dijitParams['title'] = $this->getTitle(); + } + + return $this->_dijitParams; + } + + /** + * Get title + * + * @return string + */ + public function getTitle() + { + if (null === $this->_title) { + $title = null; + if (null !== ($element = $this->getElement())) { + if (method_exists($element, 'getLegend')) { + $title = $element->getLegend(); + } + } + if (empty($title) && (null !== ($title = $this->getOption('legend')))) { + $this->removeOption('legend'); + } + if (empty($title) && (null !== ($title = $this->getOption('title')))) { + $this->removeOption('title'); + } + + if (!empty($title)) { + if (null !== ($translator = $element->getTranslator())) { + $title = $translator->translate($title); + } + $this->_title = $title; + } + } + + return (empty($this->_title) ? '' : $this->_title); + } + + /** + * Render a dijit layout container + * + * Replaces $content entirely from currently set element. + * + * @param string $content + * @return string + */ + public function render($content) + { + $element = $this->getElement(); + $view = $element->getView(); + if (null === $view) { + return $content; + } + + $dijitParams = $this->getDijitParams(); + $attribs = array_merge($this->getAttribs(), $this->getOptions()); + + if (array_key_exists('legend', $attribs)) { + if (!array_key_exists('title', $dijitParams) || empty($dijitParams['title'])) { + $dijitParams['title'] = $attribs['legend']; + } + unset($attribs['legend']); + } + + $helper = $this->getHelper(); + $id = $element->getId() . '-' . $helper; + + if ($view->dojo()->hasDijit($id)) { + trigger_error(sprintf('Duplicate dijit ID detected for id "%s; temporarily generating uniqid"', $id), E_USER_WARNING); + $base = $id; + do { + $id = $base . '-' . uniqid(); + } while ($view->dojo()->hasDijit($id)); + } + + return $view->$helper($id, $content, $dijitParams, $attribs); + } +} diff --git a/library/Zend/Dojo/Form/Decorator/DijitElement.php b/library/Zend/Dojo/Form/Decorator/DijitElement.php new file mode 100644 index 00000000..d601bd31 --- /dev/null +++ b/library/Zend/Dojo/Form/Decorator/DijitElement.php @@ -0,0 +1,193 @@ +_attribs) { + $this->_attribs = parent::getElementAttribs(); + if (array_key_exists('dijitParams', $this->_attribs)) { + $this->setDijitParams($this->_attribs['dijitParams']); + unset($this->_attribs['dijitParams']); + } + } + + return $this->_attribs; + } + + /** + * Set a single dijit option parameter + * + * @param string $key + * @param mixed $value + * @return Zend_Dojo_Form_Decorator_DijitContainer + */ + public function setDijitParam($key, $value) + { + $this->_dijitParams[(string) $key] = $value; + return $this; + } + + /** + * Set dijit option parameters + * + * @param array $params + * @return Zend_Dojo_Form_Decorator_DijitContainer + */ + public function setDijitParams(array $params) + { + $this->_dijitParams = array_merge($this->_dijitParams, $params); + return $this; + } + + /** + * Retrieve a single dijit option parameter + * + * @param string $key + * @return mixed|null + */ + public function getDijitParam($key) + { + $this->getElementAttribs(); + $key = (string) $key; + if (array_key_exists($key, $this->_dijitParams)) { + return $this->_dijitParams[$key]; + } + + return null; + } + + /** + * Get dijit option parameters + * + * @return array + */ + public function getDijitParams() + { + $this->getElementAttribs(); + return $this->_dijitParams; + } + + /** + * Render an element using a view helper + * + * Determine view helper from 'helper' option, or, if none set, from + * the element type. Then call as + * helper($element->getName(), $element->getValue(), $element->getAttribs()) + * + * @param string $content + * @return string + * @throws Zend_Form_Decorator_Exception if element or view are not registered + */ + public function render($content) + { + $element = $this->getElement(); + $view = $element->getView(); + if (null === $view) { + require_once 'Zend/Form/Decorator/Exception.php'; + throw new Zend_Form_Decorator_Exception('DijitElement decorator cannot render without a registered view object'); + } + + $options = null; + $helper = $this->getHelper(); + $separator = $this->getSeparator(); + $value = $this->getValue($element); + $attribs = $this->getElementAttribs(); + $name = $element->getFullyQualifiedName(); + + $dijitParams = $this->getDijitParams(); + $dijitParams['required'] = $element->isRequired(); + + $id = $element->getId(); + if ($view->dojo()->hasDijit($id)) { + trigger_error(sprintf('Duplicate dijit ID detected for id "%s; temporarily generating uniqid"', $id), E_USER_NOTICE); + $base = $id; + do { + $id = $base . '-' . uniqid(); + } while ($view->dojo()->hasDijit($id)); + } + $attribs['id'] = $id; + + if (array_key_exists('options', $attribs)) { + $options = $attribs['options']; + } + + $elementContent = $view->$helper($name, $value, $dijitParams, $attribs, $options); + switch ($this->getPlacement()) { + case self::APPEND: + return $content . $separator . $elementContent; + case self::PREPEND: + return $elementContent . $separator . $content; + default: + return $elementContent; + } + } +} diff --git a/library/Zend/Dojo/Form/Decorator/DijitForm.php b/library/Zend/Dojo/Form/Decorator/DijitForm.php new file mode 100644 index 00000000..1ac9d628 --- /dev/null +++ b/library/Zend/Dojo/Form/Decorator/DijitForm.php @@ -0,0 +1,61 @@ +getElement(); + $view = $element->getView(); + if (null === $view) { + return $content; + } + + $dijitParams = $this->getDijitParams(); + $attribs = array_merge($this->getAttribs(), $this->getOptions()); + + return $view->form($element->getName(), $attribs, $content); + } +} diff --git a/library/Zend/Dojo/Form/Decorator/SplitContainer.php b/library/Zend/Dojo/Form/Decorator/SplitContainer.php new file mode 100644 index 00000000..34a14145 --- /dev/null +++ b/library/Zend/Dojo/Form/Decorator/SplitContainer.php @@ -0,0 +1,43 @@ +addPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator'); + } + + /** + * Set the view object + * + * Ensures that the view object has the dojo view helper path set. + * + * @param Zend_View_Interface $view + * @return Zend_Dojo_Form_Element_Dijit + */ + public function setView(Zend_View_Interface $view = null) + { + if (null !== $view) { + if (false === $view->getPluginLoader('helper')->getPaths('Zend_Dojo_View_Helper')) { + $view->addHelperPath('Zend/Dojo/View/Helper', 'Zend_Dojo_View_Helper'); + } + } + return parent::setView($view); + } +} diff --git a/library/Zend/Dojo/Form/Element/Button.php b/library/Zend/Dojo/Form/Element/Button.php new file mode 100644 index 00000000..2abbec94 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/Button.php @@ -0,0 +1,121 @@ + $options); + } + + parent::__construct($spec, $options); + } + + /** + * Return label + * + * If no label is present, returns the currently set name. + * + * If a translator is present, returns the translated label. + * + * @return string + */ + public function getLabel() + { + $value = parent::getLabel(); + + if (null === $value) { + $value = $this->getName(); + } + + if (null !== ($translator = $this->getTranslator())) { + return $translator->translate($value); + } + + return $value; + } + + /** + * Has this submit button been selected? + * + * @return bool + */ + public function isChecked() + { + $value = $this->getValue(); + + if (empty($value)) { + return false; + } + if ($value != $this->getLabel()) { + return false; + } + + return true; + } + + /** + * Default decorators + * + * Uses only 'DijitElement' and 'DtDdWrapper' decorators by default. + * + * @return void + */ + public function loadDefaultDecorators() + { + if ($this->loadDefaultDecoratorsIsDisabled()) { + return; + } + + $decorators = $this->getDecorators(); + if (empty($decorators)) { + $this->addDecorator('DijitElement') + ->addDecorator('DtDdWrapper'); + } + } +} diff --git a/library/Zend/Dojo/Form/Element/CheckBox.php b/library/Zend/Dojo/Form/Element/CheckBox.php new file mode 100644 index 00000000..8e091c12 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/CheckBox.php @@ -0,0 +1,206 @@ + '1', + 'uncheckedValue' => '0', + ); + + /** + * Value when checked + * @var string + */ + protected $_checkedValue = '1'; + + /** + * Value when not checked + * @var string + */ + protected $_uncheckedValue = '0'; + + /** + * Current value + * @var string 0 or 1 + */ + protected $_value = '0'; + + /** + * Set options + * + * Intercept checked and unchecked values and set them early; test stored + * value against checked and unchecked values after configuration. + * + * @param array $options + * @return Zend_Form_Element_Checkbox + */ + public function setOptions(array $options) + { + if (array_key_exists('checkedValue', $options)) { + $this->setCheckedValue($options['checkedValue']); + unset($options['checkedValue']); + } + if (array_key_exists('uncheckedValue', $options)) { + $this->setUncheckedValue($options['uncheckedValue']); + unset($options['uncheckedValue']); + } + parent::setOptions($options); + + $curValue = $this->getValue(); + $test = array($this->getCheckedValue(), $this->getUncheckedValue()); + if (!in_array($curValue, $test)) { + $this->setValue($curValue); + } + + return $this; + } + + /** + * Set value + * + * If value matches checked value, sets to that value, and sets the checked + * flag to true. + * + * Any other value causes the unchecked value to be set as the current + * value, and the checked flag to be set as false. + * + * + * @param mixed $value + * @return Zend_Form_Element_Checkbox + */ + public function setValue($value) + { + if ($value == $this->getCheckedValue()) { + parent::setValue($value); + $this->checked = true; + } else { + parent::setValue($this->getUncheckedValue()); + $this->checked = false; + } + return $this; + } + + /** + * Set checked value + * + * @param string $value + * @return Zend_Form_Element_Checkbox + */ + public function setCheckedValue($value) + { + $this->_checkedValue = (string) $value; + $this->options['checkedValue'] = $value; + return $this; + } + + /** + * Get value when checked + * + * @return string + */ + public function getCheckedValue() + { + return $this->_checkedValue; + } + + /** + * Set unchecked value + * + * @param string $value + * @return Zend_Form_Element_Checkbox + */ + public function setUncheckedValue($value) + { + $this->_uncheckedValue = (string) $value; + $this->options['uncheckedValue'] = $value; + return $this; + } + + /** + * Get value when not checked + * + * @return string + */ + public function getUncheckedValue() + { + return $this->_uncheckedValue; + } + + /** + * Set checked flag + * + * @param bool $flag + * @return Zend_Form_Element_Checkbox + */ + public function setChecked($flag) + { + $this->checked = (bool) $flag; + if ($this->checked) { + $this->setValue($this->getCheckedValue()); + } else { + $this->setValue($this->getUncheckedValue()); + } + return $this; + } + + /** + * Get checked flag + * + * @return bool + */ + public function isChecked() + { + return $this->checked; + } +} + diff --git a/library/Zend/Dojo/Form/Element/ComboBox.php b/library/Zend/Dojo/Form/Element/ComboBox.php new file mode 100644 index 00000000..1d3e9b30 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/ComboBox.php @@ -0,0 +1,186 @@ +hasDijitParam('store')) { + $this->dijitParams['store'] = array(); + } + return $this->dijitParams['store']; + } + + /** + * Set datastore identifier + * + * @param string $identifier + * @return Zend_Dojo_Form_Element_ComboBox + */ + public function setStoreId($identifier) + { + $store = $this->getStoreInfo(); + $store['store'] = (string) $identifier; + $this->setDijitParam('store', $store); + return $this; + } + + /** + * Get datastore identifier + * + * @return string|null + */ + public function getStoreId() + { + $store = $this->getStoreInfo(); + if (array_key_exists('store', $store)) { + return $store['store']; + } + return null; + } + + /** + * Set datastore dijit type + * + * @param string $dojoType + * @return Zend_Dojo_Form_Element_ComboBox + */ + public function setStoreType($dojoType) + { + $store = $this->getStoreInfo(); + $store['type'] = (string) $dojoType; + $this->setDijitParam('store', $store); + return $this; + } + + /** + * Get datastore dijit type + * + * @return string|null + */ + public function getStoreType() + { + $store = $this->getStoreInfo(); + if (array_key_exists('type', $store)) { + return $store['type']; + } + return null; + } + + /** + * Set datastore parameters + * + * @param array $params + * @return Zend_Dojo_Form_Element_ComboBox + */ + public function setStoreParams(array $params) + { + $store = $this->getStoreInfo(); + $store['params'] = $params; + $this->setDijitParam('store', $store); + return $this; + } + + /** + * Get datastore params + * + * @return array + */ + public function getStoreParams() + { + $store = $this->getStoreInfo(); + if (array_key_exists('params', $store)) { + return $store['params']; + } + return array(); + } + + /** + * Set autocomplete flag + * + * @param bool $flag + * @return Zend_Dojo_Form_Element_ComboBox + */ + public function setAutocomplete($flag) + { + $this->setDijitParam('autocomplete', (bool) $flag); + return $this; + } + + /** + * Get autocomplete flag + * + * @return bool + */ + public function getAutocomplete() + { + if (!$this->hasDijitParam('autocomplete')) { + return false; + } + return $this->getDijitParam('autocomplete'); + } + + /** + * Is the value valid? + * + * @param string $value + * @param mixed $context + * @return bool + */ + public function isValid($value, $context = null) + { + $storeInfo = $this->getStoreInfo(); + if (!empty($storeInfo)) { + $this->setRegisterInArrayValidator(false); + } + return parent::isValid($value, $context); + } +} diff --git a/library/Zend/Dojo/Form/Element/CurrencyTextBox.php b/library/Zend/Dojo/Form/Element/CurrencyTextBox.php new file mode 100644 index 00000000..f8bc65c9 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/CurrencyTextBox.php @@ -0,0 +1,120 @@ +setDijitParam('currency', (string) $currency); + return $this; + } + + /** + * Retrieve currency + * + * @return string|null + */ + public function getCurrency() + { + return $this->getDijitParam('currency'); + } + + /** + * Set currency symbol + * + * Casts to string, uppercases, and trims to three characters. + * + * @param string $symbol + * @return Zend_Dojo_Form_Element_CurrencyTextBox + */ + public function setSymbol($symbol) + { + $symbol = strtoupper((string) $symbol); + $length = strlen($symbol); + if (3 > $length) { + require_once 'Zend/Form/Element/Exception.php'; + throw new Zend_Form_Element_Exception('Invalid symbol provided; please provide ISO 4217 alphabetic currency code'); + } + if (3 < $length) { + $symbol = substr($symbol, 0, 3); + } + + $this->setConstraint('symbol', $symbol); + return $this; + } + + /** + * Retrieve symbol + * + * @return string|null + */ + public function getSymbol() + { + return $this->getConstraint('symbol'); + } + + /** + * Set whether currency is fractional + * + * @param bool $flag + * @return Zend_Dojo_Form_Element_CurrencyTextBox + */ + public function setFractional($flag) + { + $this->setConstraint('fractional', (bool) $flag); + return $this; + } + + /** + * Get whether or not to present fractional values + * + * @return bool + */ + public function getFractional() + { + return ('true' == $this->getConstraint('fractional')); + } +} diff --git a/library/Zend/Dojo/Form/Element/DateTextBox.php b/library/Zend/Dojo/Form/Element/DateTextBox.php new file mode 100644 index 00000000..36783173 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/DateTextBox.php @@ -0,0 +1,214 @@ +setConstraint('am,pm', (bool) $flag); + return $this; + } + + /** + * Retrieve am,pm flag + * + * @return bool + */ + public function getAmPm() + { + if (!$this->hasConstraint('am,pm')) { + return false; + } + return ('true' ==$this->getConstraint('am,pm')); + } + + /** + * Set strict flag + * + * @param bool $strict + * @return Zend_Dojo_Form_Element_DateTextBox + */ + public function setStrict($flag) + { + $this->setConstraint('strict', (bool) $flag); + return $this; + } + + /** + * Retrieve strict flag + * + * @return bool + */ + public function getStrict() + { + if (!$this->hasConstraint('strict')) { + return false; + } + return ('true' == $this->getConstraint('strict')); + } + + /** + * Set locale + * + * @param string $locale + * @return Zend_Dojo_Form_Element_DateTextBox + */ + public function setLocale($locale) + { + $this->setConstraint('locale', (string) $locale); + return $this; + } + + /** + * Retrieve locale + * + * @return string|null + */ + public function getLocale() + { + return $this->getConstraint('locale'); + } + + /** + * Set date format pattern + * + * @param string $pattern + * @return Zend_Dojo_Form_Element_NumberTextBox + */ + public function setDatePattern($pattern) + { + $this->setConstraint('datePattern', (string) $pattern); + return $this; + } + + /** + * Retrieve date format pattern + * + * @return string|null + */ + public function getDatePattern() + { + return $this->getConstraint('datePattern'); + } + + /** + * Set numeric format formatLength + * + * @see $_allowedFormatTypes + * @param string $formatLength + * @return Zend_Dojo_Form_Element_NumberTextBox + */ + public function setFormatLength($formatLength) + { + $formatLength = strtolower($formatLength); + if (!in_array($formatLength, $this->_allowedFormatTypes)) { + require_once 'Zend/Form/Element/Exception.php'; + throw new Zend_Form_Element_Exception(sprintf('Invalid formatLength "%s" specified', $formatLength)); + } + + $this->setConstraint('formatLength', $formatLength); + return $this; + } + + /** + * Retrieve formatLength + * + * @return string|null + */ + public function getFormatLength() + { + return $this->getConstraint('formatLength'); + } + + /** + * Set numeric format Selector + * + * @see $_allowedSelectorTypes + * @param string $selector + * @return Zend_Dojo_Form_Element_NumberTextBox + */ + public function setSelector($selector) + { + $selector = strtolower($selector); + if (!in_array($selector, $this->_allowedSelectorTypes)) { + require_once 'Zend/Form/Element/Exception.php'; + throw new Zend_Form_Element_Exception(sprintf('Invalid Selector "%s" specified', $selector)); + } + + $this->setConstraint('selector', $selector); + return $this; + } + + /** + * Retrieve selector + * + * @return string|null + */ + public function getSelector() + { + return $this->getConstraint('selector'); + } +} diff --git a/library/Zend/Dojo/Form/Element/Dijit.php b/library/Zend/Dojo/Form/Element/Dijit.php new file mode 100644 index 00000000..5506bc98 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/Dijit.php @@ -0,0 +1,189 @@ +addPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator', 'decorator'); + parent::__construct($spec, $options); + } + + /** + * Set a dijit parameter + * + * @param string $key + * @param mixed $value + * @return Zend_Dojo_Form_Element_Dijit + */ + public function setDijitParam($key, $value) + { + $key = (string) $key; + $this->dijitParams[$key] = $value; + return $this; + } + + /** + * Set multiple dijit params at once + * + * @param array $params + * @return Zend_Dojo_Form_Element_Dijit + */ + public function setDijitParams(array $params) + { + $this->dijitParams = array_merge($this->dijitParams, $params); + return $this; + } + + /** + * Does the given dijit parameter exist? + * + * @param string $key + * @return bool + */ + public function hasDijitParam($key) + { + return array_key_exists($key, $this->dijitParams); + } + + /** + * Get a single dijit parameter + * + * @param string $key + * @return mixed + */ + public function getDijitParam($key) + { + $key = (string) $key; + if ($this->hasDijitParam($key)) { + return $this->dijitParams[$key]; + } + return null; + } + + /** + * Retrieve all dijit parameters + * + * @return array + */ + public function getDijitParams() + { + return $this->dijitParams; + } + + /** + * Remove a single dijit parameter + * + * @param string $key + * @return Zend_Dojo_Form_Element_Dijit + */ + public function removeDijitParam($key) + { + $key = (string) $key; + if (array_key_exists($key, $this->dijitParams)) { + unset($this->dijitParams[$key]); + } + return $this; + } + + /** + * Clear all dijit parameters + * + * @return Zend_Dojo_Form_Element_Dijit + */ + public function clearDijitParams() + { + $this->dijitParams = array(); + return $this; + } + + /** + * Load default decorators + * + * @return void + */ + public function loadDefaultDecorators() + { + if ($this->loadDefaultDecoratorsIsDisabled()) { + return; + } + + $decorators = $this->getDecorators(); + if (empty($decorators)) { + $this->addDecorator('DijitElement') + ->addDecorator('Errors') + ->addDecorator('Description', array('tag' => 'p', 'class' => 'description')) + ->addDecorator('HtmlTag', array('tag' => 'dd')) + ->addDecorator('Label', array('tag' => 'dt')); + } + } + + /** + * Set the view object + * + * Ensures that the view object has the dojo view helper path set. + * + * @param Zend_View_Interface $view + * @return Zend_Dojo_Form_Element_Dijit + */ + public function setView(Zend_View_Interface $view = null) + { + if (null !== $view) { + if (false === $view->getPluginLoader('helper')->getPaths('Zend_Dojo_View_Helper')) { + $view->addHelperPath('Zend/Dojo/View/Helper', 'Zend_Dojo_View_Helper'); + } + } + return parent::setView($view); + } +} diff --git a/library/Zend/Dojo/Form/Element/DijitMulti.php b/library/Zend/Dojo/Form/Element/DijitMulti.php new file mode 100644 index 00000000..57eb019e --- /dev/null +++ b/library/Zend/Dojo/Form/Element/DijitMulti.php @@ -0,0 +1,303 @@ +'. + * @var string + */ + protected $_separator = '
'; + + /** + * Which values are translated already? + * @var array + */ + protected $_translated = array(); + + /** + * Retrieve separator + * + * @return mixed + */ + public function getSeparator() + { + return $this->_separator; + } + + /** + * Set separator + * + * @param mixed $separator + * @return self + */ + public function setSeparator($separator) + { + $this->_separator = $separator; + return $this; + } + + /** + * Retrieve options array + * + * @return array + */ + protected function _getMultiOptions() + { + if (null === $this->options || !is_array($this->options)) { + $this->options = array(); + } + + return $this->options; + } + + /** + * Add an option + * + * @param string $option + * @param string $value + * @return Zend_Form_Element_Multi + */ + public function addMultiOption($option, $value = '') + { + $option = (string) $option; + $this->_getMultiOptions(); + if (!$this->_translateOption($option, $value)) { + $this->options[$option] = $value; + } + + return $this; + } + + /** + * Add many options at once + * + * @param array $options + * @return Zend_Form_Element_Multi + */ + public function addMultiOptions(array $options) + { + foreach ($options as $option => $value) { + if (is_array($value) + && array_key_exists('key', $value) + && array_key_exists('value', $value) + ) { + $this->addMultiOption($value['key'], $value['value']); + } else { + $this->addMultiOption($option, $value); + } + } + return $this; + } + + /** + * Set all options at once (overwrites) + * + * @param array $options + * @return Zend_Form_Element_Multi + */ + public function setMultiOptions(array $options) + { + $this->clearMultiOptions(); + return $this->addMultiOptions($options); + } + + /** + * Retrieve single multi option + * + * @param string $option + * @return mixed + */ + public function getMultiOption($option) + { + $option = (string) $option; + $this->_getMultiOptions(); + if (isset($this->options[$option])) { + $this->_translateOption($option, $this->options[$option]); + return $this->options[$option]; + } + + return null; + } + + /** + * Retrieve options + * + * @return array + */ + public function getMultiOptions() + { + $this->_getMultiOptions(); + foreach ($this->options as $option => $value) { + $this->_translateOption($option, $value); + } + return $this->options; + } + + /** + * Remove a single multi option + * + * @param string $option + * @return bool + */ + public function removeMultiOption($option) + { + $option = (string) $option; + $this->_getMultiOptions(); + if (isset($this->options[$option])) { + unset($this->options[$option]); + if (isset($this->_translated[$option])) { + unset($this->_translated[$option]); + } + return true; + } + + return false; + } + + /** + * Clear all options + * + * @return Zend_Form_Element_Multi + */ + public function clearMultiOptions() + { + $this->options = array(); + $this->_translated = array(); + return $this; + } + + /** + * Set flag indicating whether or not to auto-register inArray validator + * + * @param bool $flag + * @return Zend_Form_Element_Multi + */ + public function setRegisterInArrayValidator($flag) + { + $this->_registerInArrayValidator = (bool) $flag; + return $this; + } + + /** + * Get status of auto-register inArray validator flag + * + * @return bool + */ + public function registerInArrayValidator() + { + return $this->_registerInArrayValidator; + } + + /** + * Is the value provided valid? + * + * Autoregisters InArray validator if necessary. + * + * @param string $value + * @param mixed $context + * @return bool + */ + public function isValid($value, $context = null) + { + if ($this->registerInArrayValidator()) { + if (!$this->getValidator('InArray')) { + $options = $this->getMultiOptions(); + $this->addValidator( + 'InArray', + true, + array(array_keys($options)) + ); + } + } + return parent::isValid($value, $context); + } + + /** + * Translate an option + * + * @param string $option + * @param string $value + * @return bool + */ + protected function _translateOption($option, $value) + { + if (!isset($this->_translated[$option])) { + $this->options[$option] = $this->_translateValue($value); + if ($this->options[$option] === $value) { + return false; + } + $this->_translated[$option] = true; + return true; + } + + return false; + } + + /** + * Translate a value + * + * @param array|string $value + * @return array|string + */ + protected function _translateValue($value) + { + if (is_array($value)) { + foreach ($value as $key => $val) { + $value[$key] = $this->_translateValue($val); + } + return $value; + } else { + if (null !== ($translator = $this->getTranslator())) { + return $translator->translate($value); + } + + return $value; + } + } +} diff --git a/library/Zend/Dojo/Form/Element/Editor.php b/library/Zend/Dojo/Form/Element/Editor.php new file mode 100644 index 00000000..86bd99a6 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/Editor.php @@ -0,0 +1,599 @@ +getCaptureEvents(); + if (in_array($event, $captureEvents)) { + return $this; + } + + $captureEvents[] = (string) $event; + $this->setDijitParam('captureEvents', $captureEvents); + return $this; + } + + /** + * Add multiple capture events + * + * @param array $events + * @return Zend_Dojo_Form_Element_Editor + */ + public function addCaptureEvents(array $events) + { + foreach ($events as $event) { + $this->addCaptureEvent($event); + } + return $this; + } + + /** + * Overwrite many capture events at once + * + * @param array $events + * @return Zend_Dojo_Form_Element_Editor + */ + public function setCaptureEvents(array $events) + { + $this->clearCaptureEvents(); + $this->addCaptureEvents($events); + return $this; + } + + /** + * Get all capture events + * + * @return array + */ + public function getCaptureEvents() + { + if (!$this->hasDijitParam('captureEvents')) { + return array(); + } + return $this->getDijitParam('captureEvents'); + } + + /** + * Is a given capture event registered? + * + * @param string $event + * @return bool + */ + public function hasCaptureEvent($event) + { + $captureEvents = $this->getCaptureEvents(); + return in_array((string) $event, $captureEvents); + } + + /** + * Remove a given capture event + * + * @param string $event + * @return Zend_Dojo_Form_Element_Editor + */ + public function removeCaptureEvent($event) + { + $event = (string) $event; + $captureEvents = $this->getCaptureEvents(); + if (false === ($index = array_search($event, $captureEvents))) { + return $this; + } + unset($captureEvents[$index]); + $this->setDijitParam('captureEvents', $captureEvents); + return $this; + } + + /** + * Clear all capture events + * + * @return Zend_Dojo_Form_Element_Editor + */ + public function clearCaptureEvents() + { + return $this->removeDijitParam('captureEvents'); + } + + /** + * Add a single event to the dijit + * + * @param string $event + * @return Zend_Dojo_Form_Element_Editor + */ + public function addEvent($event) + { + $event = (string) $event; + $events = $this->getEvents(); + if (in_array($event, $events)) { + return $this; + } + + $events[] = (string) $event; + $this->setDijitParam('events', $events); + return $this; + } + + /** + * Add multiple events + * + * @param array $events + * @return Zend_Dojo_Form_Element_Editor + */ + public function addEvents(array $events) + { + foreach ($events as $event) { + $this->addEvent($event); + } + return $this; + } + + /** + * Overwrite many events at once + * + * @param array $events + * @return Zend_Dojo_Form_Element_Editor + */ + public function setEvents(array $events) + { + $this->clearEvents(); + $this->addEvents($events); + return $this; + } + + /** + * Get all events + * + * @return array + */ + public function getEvents() + { + if (!$this->hasDijitParam('events')) { + return array(); + } + return $this->getDijitParam('events'); + } + + /** + * Is a given event registered? + * + * @param string $event + * @return bool + */ + public function hasEvent($event) + { + $events = $this->getEvents(); + return in_array((string) $event, $events); + } + + /** + * Remove a given event + * + * @param string $event + * @return Zend_Dojo_Form_Element_Editor + */ + public function removeEvent($event) + { + $events = $this->getEvents(); + if (false === ($index = array_search($event, $events))) { + return $this; + } + unset($events[$index]); + $this->setDijitParam('events', $events); + return $this; + } + + /** + * Clear all events + * + * @return Zend_Dojo_Form_Element_Editor + */ + public function clearEvents() + { + return $this->removeDijitParam('events'); + } + + /** + * Add a single editor plugin + * + * @param string $plugin + * @return Zend_Dojo_Form_Element_Editor + */ + public function addPlugin($plugin) + { + $plugin = (string) $plugin; + $plugins = $this->getPlugins(); + if (in_array($plugin, $plugins) && $plugin !== '|') { + return $this; + } + + $plugins[] = (string) $plugin; + $this->setDijitParam('plugins', $plugins); + return $this; + } + + /** + * Add multiple plugins + * + * @param array $plugins + * @return Zend_Dojo_Form_Element_Editor + */ + public function addPlugins(array $plugins) + { + foreach ($plugins as $plugin) { + $this->addPlugin($plugin); + } + return $this; + } + + /** + * Overwrite many plugins at once + * + * @param array $plugins + * @return Zend_Dojo_Form_Element_Editor + */ + public function setPlugins(array $plugins) + { + $this->clearPlugins(); + $this->addPlugins($plugins); + return $this; + } + + /** + * Get all plugins + * + * @return array + */ + public function getPlugins() + { + if (!$this->hasDijitParam('plugins')) { + return array(); + } + return $this->getDijitParam('plugins'); + } + + /** + * Is a given plugin registered? + * + * @param string $plugin + * @return bool + */ + public function hasPlugin($plugin) + { + $plugins = $this->getPlugins(); + return in_array((string) $plugin, $plugins); + } + + /** + * Remove a given plugin + * + * @param string $plugin + * @return Zend_Dojo_Form_Element_Editor + */ + public function removePlugin($plugin) + { + $plugins = $this->getPlugins(); + if (false === ($index = array_search($plugin, $plugins))) { + return $this; + } + unset($plugins[$index]); + $this->setDijitParam('plugins', $plugins); + return $this; + } + + /** + * Clear all plugins + * + * @return Zend_Dojo_Form_Element_Editor + */ + public function clearPlugins() + { + return $this->removeDijitParam('plugins'); + } + + /** + * Set edit action interval + * + * @param int $interval + * @return Zend_Dojo_Form_Element_Editor + */ + public function setEditActionInterval($interval) + { + return $this->setDijitParam('editActionInterval', (int) $interval); + } + + /** + * Get edit action interval; defaults to 3 + * + * @return int + */ + public function getEditActionInterval() + { + if (!$this->hasDijitParam('editActionInterval')) { + $this->setEditActionInterval(3); + } + return $this->getDijitParam('editActionInterval'); + } + + /** + * Set focus on load flag + * + * @param bool $flag + * @return Zend_Dojo_Form_Element_Editor + */ + public function setFocusOnLoad($flag) + { + return $this->setDijitParam('focusOnLoad', (bool) $flag); + } + + /** + * Retrieve focus on load flag + * + * @return bool + */ + public function getFocusOnLoad() + { + if (!$this->hasDijitParam('focusOnLoad')) { + return false; + } + return $this->getDijitParam('focusOnLoad'); + } + + /** + * Set editor height + * + * @param string|int $height + * @return Zend_Dojo_Form_Element_Editor + */ + public function setHeight($height) + { + if (!preg_match('/^\d+(em|px|%)?$/i', $height)) { + require_once 'Zend/Form/Element/Exception.php'; + throw new Zend_Form_Element_Exception('Invalid height provided; must be integer or CSS measurement'); + } + if (!preg_match('/(em|px|%)$/', $height)) { + $height .= 'px'; + } + return $this->setDijitParam('height', $height); + } + + /** + * Retrieve height + * + * @return string + */ + public function getHeight() + { + if (!$this->hasDijitParam('height')) { + return '300px'; + } + return $this->getDijitParam('height'); + } + + /** + * Set whether or not to inherit parent's width + * + * @param bool $flag + * @return Zend_Dojo_Form_Element_Editor + */ + public function setInheritWidth($flag) + { + return $this->setDijitParam('inheritWidth', (bool) $flag); + } + + /** + * Whether or not to inherit the parent's width + * + * @return bool + */ + public function getInheritWidth() + { + if (!$this->hasDijitParam('inheritWidth')) { + return false; + } + return $this->getDijitParam('inheritWidth'); + } + + /** + * Set minimum height of editor + * + * @param string|int $minHeight + * @return Zend_Dojo_Form_Element_Editor + */ + public function setMinHeight($minHeight) + { + if (!preg_match('/^\d+(em|px|%)?$/i', $minHeight)) { + require_once 'Zend/Form/Element/Exception.php'; + throw new Zend_Form_Element_Exception('Invalid minHeight provided; must be integer or CSS measurement'); + } + if (!preg_match('/(em|px|%)$/', $minHeight)) { + $minHeight .= 'em'; + } + return $this->setDijitParam('minHeight', $minHeight); + } + + /** + * Get minimum height of editor + * + * @return string + */ + public function getMinHeight() + { + if (!$this->hasDijitParam('minHeight')) { + return '1em'; + } + return $this->getDijitParam('minHeight'); + } + + /** + * Add a custom stylesheet + * + * @param string $styleSheet + * @return Zend_Dojo_Form_Element_Editor + */ + public function addStyleSheet($styleSheet) + { + $stylesheets = $this->getStyleSheets(); + if (strstr($stylesheets, ';')) { + $stylesheets = explode(';', $stylesheets); + } elseif (!empty($stylesheets)) { + $stylesheets = (array) $stylesheets; + } else { + $stylesheets = array(); + } + if (!in_array($styleSheet, $stylesheets)) { + $stylesheets[] = (string) $styleSheet; + } + return $this->setDijitParam('styleSheets', implode(';', $stylesheets)); + } + + /** + * Add multiple custom stylesheets + * + * @param array $styleSheets + * @return Zend_Dojo_Form_Element_Editor + */ + public function addStyleSheets(array $styleSheets) + { + foreach ($styleSheets as $styleSheet) { + $this->addStyleSheet($styleSheet); + } + return $this; + } + + /** + * Overwrite all stylesheets + * + * @param array $styleSheets + * @return Zend_Dojo_Form_Element_Editor + */ + public function setStyleSheets(array $styleSheets) + { + $this->clearStyleSheets(); + return $this->addStyleSheets($styleSheets); + } + + /** + * Get all stylesheets + * + * @return string + */ + public function getStyleSheets() + { + if (!$this->hasDijitParam('styleSheets')) { + return ''; + } + return $this->getDijitParam('styleSheets'); + } + + /** + * Is a given stylesheet registered? + * + * @param string $styleSheet + * @return bool + */ + public function hasStyleSheet($styleSheet) + { + $styleSheets = $this->getStyleSheets(); + $styleSheets = explode(';', $styleSheets); + return in_array($styleSheet, $styleSheets); + } + + /** + * Remove a single stylesheet + * + * @param string $styleSheet + * @return Zend_Dojo_Form_Element_Editor + */ + public function removeStyleSheet($styleSheet) + { + $styleSheets = $this->getStyleSheets(); + $styleSheets = explode(';', $styleSheets); + if (false !== ($index = array_search($styleSheet, $styleSheets))) { + unset($styleSheets[$index]); + $this->setDijitParam('styleSheets', implode(';', $styleSheets)); + } + return $this; + } + + /** + * Clear all stylesheets + * + * @return Zend_Dojo_Form_Element_Editor + */ + public function clearStyleSheets() + { + if ($this->hasDijitParam('styleSheets')) { + $this->removeDijitParam('styleSheets'); + } + return $this; + } + + /** + * Set update interval + * + * @param int $interval + * @return Zend_Dojo_Form_Element_Editor + */ + public function setUpdateInterval($interval) + { + return $this->setDijitParam('updateInterval', (int) $interval); + } + + /** + * Get update interval + * + * @return int + */ + public function getUpdateInterval() + { + if (!$this->hasDijitParam('updateInterval')) { + return 200; + } + return $this->getDijitParam('updateInterval'); + } +} diff --git a/library/Zend/Dojo/Form/Element/FilteringSelect.php b/library/Zend/Dojo/Form/Element/FilteringSelect.php new file mode 100644 index 00000000..a9eda226 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/FilteringSelect.php @@ -0,0 +1,48 @@ +hasDijitParam('topDecoration')) { + return $this->getDijitParam('topDecoration'); + } + return array(); + } + + /** + * Set dijit to use with top decoration + * + * @param mixed $dijit + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setTopDecorationDijit($dijit) + { + $decoration = $this->getTopDecoration(); + $decoration['dijit'] = (string) $dijit; + $this->setDijitParam('topDecoration', $decoration); + return $this; + } + + /** + * Set container to use with top decoration + * + * @param mixed $container + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setTopDecorationContainer($container) + { + $decoration = $this->getTopDecoration(); + $decoration['container'] = (string) $container; + $this->setDijitParam('topDecoration', $decoration); + return $this; + } + + /** + * Set labels to use with top decoration + * + * @param array $labels + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setTopDecorationLabels(array $labels) + { + $decoration = $this->getTopDecoration(); + $decoration['labels'] = array_values($labels); + $this->setDijitParam('topDecoration', $decoration); + return $this; + } + + /** + * Set params to use with top decoration + * + * @param array $params + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setTopDecorationParams(array $params) + { + $decoration = $this->getTopDecoration(); + $decoration['params'] = $params; + $this->setDijitParam('topDecoration', $decoration); + return $this; + } + + /** + * Set attribs to use with top decoration + * + * @param array $attribs + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setTopDecorationAttribs(array $attribs) + { + $decoration = $this->getTopDecoration(); + $decoration['attribs'] = $attribs; + $this->setDijitParam('topDecoration', $decoration); + return $this; + } + + /** + * Get bottom decoration data + * + * @return array + */ + public function getBottomDecoration() + { + if ($this->hasDijitParam('bottomDecoration')) { + return $this->getDijitParam('bottomDecoration'); + } + return array(); + } + + /** + * Set dijit to use with bottom decoration + * + * @param mixed $dijit + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setBottomDecorationDijit($dijit) + { + $decoration = $this->getBottomDecoration(); + $decoration['dijit'] = (string) $dijit; + $this->setDijitParam('bottomDecoration', $decoration); + return $this; + } + + /** + * Set container to use with bottom decoration + * + * @param mixed $container + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setBottomDecorationContainer($container) + { + $decoration = $this->getBottomDecoration(); + $decoration['container'] = (string) $container; + $this->setDijitParam('bottomDecoration', $decoration); + return $this; + } + + /** + * Set labels to use with bottom decoration + * + * @param array $labels + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setBottomDecorationLabels(array $labels) + { + $decoration = $this->getBottomDecoration(); + $decoration['labels'] = array_values($labels); + $this->setDijitParam('bottomDecoration', $decoration); + return $this; + } + + /** + * Set params to use with bottom decoration + * + * @param array $params + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setBottomDecorationParams(array $params) + { + $decoration = $this->getBottomDecoration(); + $decoration['params'] = $params; + $this->setDijitParam('bottomDecoration', $decoration); + return $this; + } + + /** + * Set attribs to use with bottom decoration + * + * @param array $attribs + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setBottomDecorationAttribs(array $attribs) + { + $decoration = $this->getBottomDecoration(); + $decoration['attribs'] = $attribs; + $this->setDijitParam('bottomDecoration', $decoration); + return $this; + } +} diff --git a/library/Zend/Dojo/Form/Element/NumberSpinner.php b/library/Zend/Dojo/Form/Element/NumberSpinner.php new file mode 100644 index 00000000..099a4e26 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/NumberSpinner.php @@ -0,0 +1,245 @@ +setDijitParam('defaultTimeout', (int) $timeout); + return $this; + } + + /** + * Retrieve defaultTimeout + * + * @return int|null + */ + public function getDefaultTimeout() + { + return $this->getDijitParam('defaultTimeout'); + } + + /** + * Set timeoutChangeRate + * + * @param int $rate + * @return Zend_Dojo_Form_Element_NumberSpinner + */ + public function setTimeoutChangeRate($rate) + { + $this->setDijitParam('timeoutChangeRate', (int) $rate); + return $this; + } + + /** + * Retrieve timeoutChangeRate + * + * @return int|null + */ + public function getTimeoutChangeRate() + { + return $this->getDijitParam('timeoutChangeRate'); + } + + /** + * Set largeDelta + * + * @param int $delta + * @return Zend_Dojo_Form_Element_NumberSpinner + */ + public function setLargeDelta($delta) + { + $this->setDijitParam('largeDelta', (float) $delta); + return $this; + } + + /** + * Retrieve largeDelta + * + * @return int|null + */ + public function getLargeDelta() + { + return $this->getDijitParam('largeDelta'); + } + + /** + * Set smallDelta + * + * @param int $delta + * @return Zend_Dojo_Form_Element_NumberSpinner + */ + public function setSmallDelta($delta) + { + $this->setDijitParam('smallDelta', (float) $delta); + return $this; + } + + /** + * Retrieve smallDelta + * + * @return int|null + */ + public function getSmallDelta() + { + return $this->getDijitParam('smallDelta'); + } + + /** + * Set intermediateChanges flag + * + * @param bool $flag + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setIntermediateChanges($flag) + { + $this->setDijitParam('intermediateChanges', (bool) $flag); + return $this; + } + + /** + * Retrieve intermediateChanges flag + * + * @return bool + */ + public function getIntermediateChanges() + { + if (!$this->hasDijitParam('intermediateChanges')) { + return false; + } + return $this->getDijitParam('intermediateChanges'); + } + + /** + * Set rangeMessage + * + * @param string $message + * @return Zend_Dojo_Form_Element_NumberSpinner + */ + public function setRangeMessage($message) + { + $this->setDijitParam('rangeMessage', (string) $message); + return $this; + } + + /** + * Retrieve rangeMessage + * + * @return string|null + */ + public function getRangeMessage() + { + return $this->getDijitParam('rangeMessage'); + } + + /** + * Set minimum value + * + * @param int $value + * @return Zend_Dojo_Form_Element_NumberSpinner + */ + public function setMin($value) + { + $constraints = array(); + if ($this->hasDijitParam('constraints')) { + $constraints = $this->getDijitParam('constraints'); + } + $constraints['min'] = (float) $value; + $this->setDijitParam('constraints', $constraints); + return $this; + } + + /** + * Get minimum value + * + * @return null|int + */ + public function getMin() + { + if (!$this->hasDijitParam('constraints')) { + return null; + } + $constraints = $this->getDijitParam('constraints'); + if (!array_key_exists('min', $constraints)) { + return null; + } + return $constraints['min']; + } + + /** + * Set maximum value + * + * @param int $value + * @return Zend_Dojo_Form_Element_NumberSpinner + */ + public function setMax($value) + { + $constraints = array(); + if ($this->hasDijitParam('constraints')) { + $constraints = $this->getDijitParam('constraints'); + } + $constraints['max'] = (float) $value; + $this->setDijitParam('constraints', $constraints); + return $this; + } + + /** + * Get maximum value + * + * @return null|int + */ + public function getMax() + { + if (!$this->hasDijitParam('constraints')) { + return null; + } + $constraints = $this->getDijitParam('constraints'); + if (!array_key_exists('max', $constraints)) { + return null; + } + return $constraints['max']; + } +} diff --git a/library/Zend/Dojo/Form/Element/NumberTextBox.php b/library/Zend/Dojo/Form/Element/NumberTextBox.php new file mode 100644 index 00000000..023c4de4 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/NumberTextBox.php @@ -0,0 +1,173 @@ +setConstraint('locale', (string) $locale); + return $this; + } + + /** + * Retrieve locale + * + * @return string|null + */ + public function getLocale() + { + return $this->getConstraint('locale'); + } + + /** + * Set numeric format pattern + * + * @param string $pattern + * @return Zend_Dojo_Form_Element_NumberTextBox + */ + public function setPattern($pattern) + { + $this->setConstraint('pattern', (string) $pattern); + return $this; + } + + /** + * Retrieve numeric format pattern + * + * @return string|null + */ + public function getPattern() + { + return $this->getConstraint('pattern'); + } + + /** + * Set numeric format type + * + * @see $_allowedTypes + * @param string $type + * @return Zend_Dojo_Form_Element_NumberTextBox + */ + public function setType($type) + { + $type = strtolower($type); + if (!in_array($type, $this->_allowedTypes)) { + require_once 'Zend/Form/Element/Exception.php'; + throw new Zend_Form_Element_Exception(sprintf('Invalid numeric type "%s" specified', $type)); + } + + $this->setConstraint('type', $type); + return $this; + } + + /** + * Retrieve type + * + * @return string|null + */ + public function getType() + { + return $this->getConstraint('type'); + } + + /** + * Set decimal places + * + * @param int $places + * @return Zend_Dojo_Form_Element_NumberTextBox + */ + public function setPlaces($places) + { + $this->setConstraint('places', (int) $places); + return $this; + } + + /** + * Retrieve decimal places + * + * @return int|null + */ + public function getPlaces() + { + return $this->getConstraint('places'); + } + + /** + * Set strict flag + * + * @param bool $strict + * @return Zend_Dojo_Form_Element_NumberTextBox + */ + public function setStrict($flag) + { + $this->setConstraint('strict', (bool) $flag); + return $this; + } + + /** + * Retrieve strict flag + * + * @return bool + */ + public function getStrict() + { + if (!$this->hasConstraint('strict')) { + return false; + } + return ('true' == $this->getConstraint('strict')); + } +} diff --git a/library/Zend/Dojo/Form/Element/PasswordTextBox.php b/library/Zend/Dojo/Form/Element/PasswordTextBox.php new file mode 100644 index 00000000..1034c09b --- /dev/null +++ b/library/Zend/Dojo/Form/Element/PasswordTextBox.php @@ -0,0 +1,42 @@ +setDijitParam('clickSelect', (bool) $flag); + return $this; + } + + /** + * Retrieve clickSelect flag + * + * @return bool + */ + public function getClickSelect() + { + if (!$this->hasDijitParam('clickSelect')) { + return false; + } + return $this->getDijitParam('clickSelect'); + } + + /** + * Set intermediateChanges flag + * + * @param bool $intermediateChanges + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setIntermediateChanges($flag) + { + $this->setDijitParam('intermediateChanges', (bool) $flag); + return $this; + } + + /** + * Retrieve intermediateChanges flag + * + * @return bool + */ + public function getIntermediateChanges() + { + if (!$this->hasDijitParam('intermediateChanges')) { + return false; + } + return $this->getDijitParam('intermediateChanges'); + } + + /** + * Set showButtons flag + * + * @param bool $showButtons + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setShowButtons($flag) + { + $this->setDijitParam('showButtons', (bool) $flag); + return $this; + } + + /** + * Retrieve showButtons flag + * + * @return bool + */ + public function getShowButtons() + { + if (!$this->hasDijitParam('showButtons')) { + return false; + } + return $this->getDijitParam('showButtons'); + } + + /** + * Set discreteValues + * + * @param int $value + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setDiscreteValues($value) + { + $this->setDijitParam('discreteValues', (int) $value); + return $this; + } + + /** + * Retrieve discreteValues + * + * @return int|null + */ + public function getDiscreteValues() + { + return $this->getDijitParam('discreteValues'); + } + + /** + * Set maximum + * + * @param int $value + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setMaximum($value) + { + $this->setDijitParam('maximum', (int) $value); + return $this; + } + + /** + * Retrieve maximum + * + * @return int|null + */ + public function getMaximum() + { + return $this->getDijitParam('maximum'); + } + + /** + * Set minimum + * + * @param int $value + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setMinimum($value) + { + $this->setDijitParam('minimum', (int) $value); + return $this; + } + + /** + * Retrieve minimum + * + * @return int|null + */ + public function getMinimum() + { + return $this->getDijitParam('minimum'); + } + + /** + * Set pageIncrement + * + * @param int $value + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setPageIncrement($value) + { + $this->setDijitParam('pageIncrement', (int) $value); + return $this; + } + + /** + * Retrieve pageIncrement + * + * @return int|null + */ + public function getPageIncrement() + { + return $this->getDijitParam('pageIncrement'); + } +} diff --git a/library/Zend/Dojo/Form/Element/SubmitButton.php b/library/Zend/Dojo/Form/Element/SubmitButton.php new file mode 100644 index 00000000..92d47480 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/SubmitButton.php @@ -0,0 +1,42 @@ +setDijitParam('lowercase', (bool) $flag); + return $this; + } + + /** + * Retrieve lowercase flag + * + * @return bool + */ + public function getLowercase() + { + if (!$this->hasDijitParam('lowercase')) { + return false; + } + return $this->getDijitParam('lowercase'); + } + + /** + * Set propercase flag + * + * @param bool $propercase + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setPropercase($flag) + { + $this->setDijitParam('propercase', (bool) $flag); + return $this; + } + + /** + * Retrieve propercase flag + * + * @return bool + */ + public function getPropercase() + { + if (!$this->hasDijitParam('propercase')) { + return false; + } + return $this->getDijitParam('propercase'); + } + + /** + * Set uppercase flag + * + * @param bool $uppercase + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setUppercase($flag) + { + $this->setDijitParam('uppercase', (bool) $flag); + return $this; + } + + /** + * Retrieve uppercase flag + * + * @return bool + */ + public function getUppercase() + { + if (!$this->hasDijitParam('uppercase')) { + return false; + } + return $this->getDijitParam('uppercase'); + } + + /** + * Set trim flag + * + * @param bool $trim + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setTrim($flag) + { + $this->setDijitParam('trim', (bool) $flag); + return $this; + } + + /** + * Retrieve trim flag + * + * @return bool + */ + public function getTrim() + { + if (!$this->hasDijitParam('trim')) { + return false; + } + return $this->getDijitParam('trim'); + } + + /** + * Set maxLength + * + * @param int $length + * @return Zend_Dojo_Form_Element_TextBox + */ + public function setMaxLength($length) + { + $this->setDijitParam('maxLength', (int) $length); + return $this; + } + + /** + * Retrieve maxLength + * + * @return int|null + */ + public function getMaxLength() + { + return $this->getDijitParam('maxLength'); + } +} diff --git a/library/Zend/Dojo/Form/Element/Textarea.php b/library/Zend/Dojo/Form/Element/Textarea.php new file mode 100644 index 00000000..9881113c --- /dev/null +++ b/library/Zend/Dojo/Form/Element/Textarea.php @@ -0,0 +1,42 @@ +setConstraint('timePattern', (string) $pattern); + return $this; + } + + /** + * Retrieve time format pattern + * + * @return string|null + */ + public function getTimePattern() + { + return $this->getConstraint('timePattern'); + } + + /** + * Set clickableIncrement + * + * @param string $format + * @return Zend_Dojo_Form_Element_NumberTextBox + */ + public function setClickableIncrement($format) + { + $format = (string) $format; + $this->_validateIso8601($format); + $this->setConstraint('clickableIncrement', $format); + return $this; + } + + /** + * Retrieve clickableIncrement + * + * @return string|null + */ + public function getClickableIncrement() + { + return $this->getConstraint('clickableIncrement'); + } + + /** + * Set visibleIncrement + * + * @param string $format + * @return Zend_Dojo_Form_Element_NumberTextBox + */ + public function setVisibleIncrement($format) + { + $format = (string) $format; + $this->_validateIso8601($format); + $this->setConstraint('visibleIncrement', $format); + return $this; + } + + /** + * Retrieve visibleIncrement + * + * @return string|null + */ + public function getVisibleIncrement() + { + return $this->getConstraint('visibleIncrement'); + } + + /** + * Set visibleRange + * + * @param string $format + * @return Zend_Dojo_Form_Element_NumberTextBox + */ + public function setVisibleRange($format) + { + $format = (string) $format; + $this->_validateIso8601($format); + $this->setConstraint('visibleRange', $format); + return $this; + } + + /** + * Retrieve visibleRange + * + * @return string|null + */ + public function getVisibleRange() + { + return $this->getConstraint('visibleRange'); + } +} diff --git a/library/Zend/Dojo/Form/Element/ValidationTextBox.php b/library/Zend/Dojo/Form/Element/ValidationTextBox.php new file mode 100644 index 00000000..6822004f --- /dev/null +++ b/library/Zend/Dojo/Form/Element/ValidationTextBox.php @@ -0,0 +1,220 @@ +setDijitParam('invalidMessage', (string) $message); + return $this; + } + + /** + * Retrieve invalidMessage + * + * @return string|null + */ + public function getInvalidMessage() + { + return $this->getDijitParam('invalidMessage'); + } + + /** + * Set promptMessage + * + * @param string $message + * @return Zend_Dojo_Form_Element_ValidationTextBox + */ + public function setPromptMessage($message) + { + $this->setDijitParam('promptMessage', (string) $message); + return $this; + } + + /** + * Retrieve promptMessage + * + * @return string|null + */ + public function getPromptMessage() + { + return $this->getDijitParam('promptMessage'); + } + + /** + * Set regExp + * + * @param string $regexp + * @return Zend_Dojo_Form_Element_ValidationTextBox + */ + public function setRegExp($regexp) + { + $this->setDijitParam('regExp', (string) $regexp); + return $this; + } + + /** + * Retrieve regExp + * + * @return string|null + */ + public function getRegExp() + { + return $this->getDijitParam('regExp'); + } + + /** + * Set an individual constraint + * + * @param string $key + * @param mixed $value + * @return Zend_Dojo_Form_Element_ValidationTextBox + */ + public function setConstraint($key, $value) + { + $constraints = $this->getConstraints(); + $constraints[(string) $key] = $value; + $this->setConstraints($constraints); + return $this; + } + + /** + * Set validation constraints + * + * Refer to Dojo dijit.form.ValidationTextBox documentation for valid + * structure. + * + * @param array $constraints + * @return Zend_Dojo_Form_Element_ValidationTextBox + */ + public function setConstraints(array $constraints) + { + $tmp = $this->getConstraints(); + $constraints = array_merge($tmp, $constraints); + array_walk_recursive($constraints, array($this, '_castBoolToString')); + $this->setDijitParam('constraints', $constraints); + return $this; + } + + /** + * Is the given constraint set? + * + * @param string $key + * @return bool + */ + public function hasConstraint($key) + { + $constraints = $this->getConstraints(); + return array_key_exists((string)$key, $constraints); + } + + /** + * Get an individual constraint + * + * @param string $key + * @return mixed + */ + public function getConstraint($key) + { + $key = (string) $key; + if (!$this->hasConstraint($key)) { + return null; + } + return $this->dijitParams['constraints'][$key]; + } + + /** + * Get constraints + * + * @return array + */ + public function getConstraints() + { + if ($this->hasDijitParam('constraints')) { + return $this->getDijitParam('constraints'); + } + return array(); + } + + /** + * Remove a single constraint + * + * @param string $key + * @return Zend_Dojo_Form_Element_ValidationTextBox + */ + public function removeConstraint($key) + { + $key = (string) $key; + if ($this->hasConstraint($key)) { + unset($this->dijitParams['constraints'][$key]); + } + return $this; + } + + /** + * Clear all constraints + * + * @return Zend_Dojo_Form_Element_ValidationTextBox + */ + public function clearConstraints() + { + return $this->removeDijitParam('constraints'); + } + + /** + * Cast a boolean value to a string + * + * @param mixed $item + * @param string $key + * @return void + */ + protected function _castBoolToString(&$item, $key) + { + if (is_bool($item)) { + $item = ($item) ? 'true' : 'false'; + } + } +} diff --git a/library/Zend/Dojo/Form/Element/VerticalSlider.php b/library/Zend/Dojo/Form/Element/VerticalSlider.php new file mode 100644 index 00000000..a1b4b177 --- /dev/null +++ b/library/Zend/Dojo/Form/Element/VerticalSlider.php @@ -0,0 +1,208 @@ +hasDijitParam('leftDecoration')) { + return $this->getDijitParam('leftDecoration'); + } + return array(); + } + + /** + * Set dijit to use with left decoration + * + * @param mixed $dijit + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setLeftDecorationDijit($dijit) + { + $decoration = $this->getLeftDecoration(); + $decoration['dijit'] = (string) $dijit; + $this->setDijitParam('leftDecoration', $decoration); + return $this; + } + + /** + * Set container to use with left decoration + * + * @param mixed $container + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setLeftDecorationContainer($container) + { + $decoration = $this->getLeftDecoration(); + $decoration['container'] = (string) $container; + $this->setDijitParam('leftDecoration', $decoration); + return $this; + } + + /** + * Set labels to use with left decoration + * + * @param array $labels + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setLeftDecorationLabels(array $labels) + { + $decoration = $this->getLeftDecoration(); + $decoration['labels'] = array_values($labels); + $this->setDijitParam('leftDecoration', $decoration); + return $this; + } + + /** + * Set params to use with left decoration + * + * @param array $params + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setLeftDecorationParams(array $params) + { + $decoration = $this->getLeftDecoration(); + $decoration['params'] = $params; + $this->setDijitParam('leftDecoration', $decoration); + return $this; + } + + /** + * Set attribs to use with left decoration + * + * @param array $attribs + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setLeftDecorationAttribs(array $attribs) + { + $decoration = $this->getLeftDecoration(); + $decoration['attribs'] = $attribs; + $this->setDijitParam('leftDecoration', $decoration); + return $this; + } + + /** + * Get right decoration data + * + * @return array + */ + public function getRightDecoration() + { + if ($this->hasDijitParam('rightDecoration')) { + return $this->getDijitParam('rightDecoration'); + } + return array(); + } + + /** + * Set dijit to use with right decoration + * + * @param mixed $dijit + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setRightDecorationDijit($dijit) + { + $decoration = $this->getRightDecoration(); + $decoration['dijit'] = (string) $dijit; + $this->setDijitParam('rightDecoration', $decoration); + return $this; + } + + /** + * Set container to use with right decoration + * + * @param mixed $container + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setRightDecorationContainer($container) + { + $decoration = $this->getRightDecoration(); + $decoration['container'] = (string) $container; + $this->setDijitParam('rightDecoration', $decoration); + return $this; + } + + /** + * Set labels to use with right decoration + * + * @param array $labels + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setRightDecorationLabels(array $labels) + { + $decoration = $this->getRightDecoration(); + $decoration['labels'] = array_values($labels); + $this->setDijitParam('rightDecoration', $decoration); + return $this; + } + + /** + * Set params to use with right decoration + * + * @param array $params + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setRightDecorationParams(array $params) + { + $decoration = $this->getRightDecoration(); + $decoration['params'] = $params; + $this->setDijitParam('rightDecoration', $decoration); + return $this; + } + + /** + * Set attribs to use with right decoration + * + * @param array $attribs + * @return Zend_Dojo_Form_Element_HorizontalSlider + */ + public function setRightDecorationAttribs(array $attribs) + { + $decoration = $this->getRightDecoration(); + $decoration['attribs'] = $attribs; + $this->setDijitParam('rightDecoration', $decoration); + return $this; + } +} diff --git a/library/Zend/Dojo/Form/SubForm.php b/library/Zend/Dojo/Form/SubForm.php new file mode 100644 index 00000000..fbba4373 --- /dev/null +++ b/library/Zend/Dojo/Form/SubForm.php @@ -0,0 +1,94 @@ +addPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator', 'decorator') + ->addPrefixPath('Zend_Dojo_Form_Element', 'Zend/Dojo/Form/Element', 'element') + ->addElementPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator', 'decorator') + ->addDisplayGroupPrefixPath('Zend_Dojo_Form_Decorator', 'Zend/Dojo/Form/Decorator') + ->setDefaultDisplayGroupClass('Zend_Dojo_Form_DisplayGroup'); + parent::__construct($options); + } + + /** + * Load the default decorators + * + * @return void + */ + public function loadDefaultDecorators() + { + if ($this->loadDefaultDecoratorsIsDisabled()) { + return; + } + + $decorators = $this->getDecorators(); + if (empty($decorators)) { + $this->addDecorator('FormElements') + ->addDecorator('HtmlTag', array('tag' => 'dl')) + ->addDecorator('ContentPane'); + } + } + + /** + * Get view + * + * @return Zend_View_Interface + */ + public function getView() + { + $view = parent::getView(); + if (!$this->_dojoViewPathRegistered) { + if (false === $view->getPluginLoader('helper')->getPaths('Zend_Dojo_View_Helper')) { + $view->addHelperPath('Zend/Dojo/View/Helper', 'Zend_Dojo_View_Helper'); + } + $this->_dojoViewPathRegistered = true; + } + return $view; + } +} diff --git a/library/Zend/Dojo/View/Exception.php b/library/Zend/Dojo/View/Exception.php new file mode 100644 index 00000000..0bf71f39 --- /dev/null +++ b/library/Zend/Dojo/View/Exception.php @@ -0,0 +1,37 @@ +_createLayoutContainer($id, $content, $params, $attribs); + } +} diff --git a/library/Zend/Dojo/View/Helper/AccordionPane.php b/library/Zend/Dojo/View/Helper/AccordionPane.php new file mode 100644 index 00000000..15a115a3 --- /dev/null +++ b/library/Zend/Dojo/View/Helper/AccordionPane.php @@ -0,0 +1,66 @@ +_createLayoutContainer($id, $content, $params, $attribs); + } +} diff --git a/library/Zend/Dojo/View/Helper/BorderContainer.php b/library/Zend/Dojo/View/Helper/BorderContainer.php new file mode 100644 index 00000000..f0e6a62f --- /dev/null +++ b/library/Zend/Dojo/View/Helper/BorderContainer.php @@ -0,0 +1,79 @@ +_styleIsRegistered) { + $this->view->headStyle()->appendStyle('html, body { height: 100%; width: 100%; margin: 0; padding: 0; }'); + $this->_styleIsRegistered = true; + } + + // and now we create it: + return $this->_createLayoutContainer($id, $content, $params, $attribs); + } +} diff --git a/library/Zend/Dojo/View/Helper/Button.php b/library/Zend/Dojo/View/Helper/Button.php new file mode 100644 index 00000000..00018729 --- /dev/null +++ b/library/Zend/Dojo/View/Helper/Button.php @@ -0,0 +1,68 @@ +_prepareDijit($attribs, $params, 'element'); + + return $this->view->formButton($id, $value, $attribs); + } +} diff --git a/library/Zend/Dojo/View/Helper/CheckBox.php b/library/Zend/Dojo/View/Helper/CheckBox.php new file mode 100644 index 00000000..821163b5 --- /dev/null +++ b/library/Zend/Dojo/View/Helper/CheckBox.php @@ -0,0 +1,100 @@ +_prepareDijit($attribs, $params, 'element'); + + // strip options so they don't show up in markup + if (array_key_exists('options', $attribs)) { + unset($attribs['options']); + } + + // and now we create it: + $html = ''; + if (!strstr($id, '[]')) { + // hidden element for unchecked value + $html .= $this->_renderHiddenElement($id, $checkboxInfo['uncheckedValue']); + } + + // and final element + $html .= $this->_createFormElement($id, $checkboxInfo['checkedValue'], $params, $attribs); + + return $html; + } +} diff --git a/library/Zend/Dojo/View/Helper/ComboBox.php b/library/Zend/Dojo/View/Helper/ComboBox.php new file mode 100644 index 00000000..077f1ac6 --- /dev/null +++ b/library/Zend/Dojo/View/Helper/ComboBox.php @@ -0,0 +1,155 @@ +_renderStore($params['store'], $id))) { + $params['store'] = $params['store']['store']; + if (is_string($store)) { + $html .= $store; + } + $html .= $this->_createFormElement($id, $value, $params, $attribs); + return $html; + } + unset($params['store']); + } elseif (array_key_exists('store', $params)) { + if (array_key_exists('storeType', $params)) { + $storeParams = array( + 'store' => $params['store'], + 'type' => $params['storeType'], + ); + unset($params['storeType']); + if (array_key_exists('storeParams', $params)) { + $storeParams['params'] = $params['storeParams']; + unset($params['storeParams']); + } + if (false !== ($store = $this->_renderStore($storeParams, $id))) { + if (is_string($store)) { + $html .= $store; + } + } + } + $html .= $this->_createFormElement($id, $value, $params, $attribs); + return $html; + } + + // required for correct type casting in declerative mode + if (isset($params['autocomplete'])) { + $params['autocomplete'] = ($params['autocomplete']) ? 'true' : 'false'; + } + // do as normal select + $attribs = $this->_prepareDijit($attribs, $params, 'element'); + return $this->view->formSelect($id, $value, $attribs, $options); + } + + /** + * Render data store element + * + * Renders to dojo view helper + * + * @param array $params + * @return string|false + */ + protected function _renderStore(array $params, $id) + { + if (!array_key_exists('store', $params) || !array_key_exists('type', $params)) { + return false; + } + + $this->dojo->requireModule($params['type']); + + $extraParams = array(); + $storeParams = array( + 'dojoType' => $params['type'], + 'jsId' => $params['store'], + ); + + if (array_key_exists('params', $params)) { + $storeParams = array_merge($storeParams, $params['params']); + $extraParams = $params['params']; + } + + if ($this->_useProgrammatic()) { + if (!$this->_useProgrammaticNoScript()) { + require_once 'Zend/Json.php'; + $this->dojo->addJavascript('var ' . $storeParams['jsId'] . ";\n"); + $js = $storeParams['jsId'] . ' = ' + . 'new ' . $storeParams['dojoType'] . '(' + . Zend_Json::encode($extraParams) + . ");\n"; + $js = "function() {\n$js\n}"; + $this->dojo->_addZendLoad($js); + } + return true; + } + + return '_htmlAttribs($storeParams) . '>'; + } +} diff --git a/library/Zend/Dojo/View/Helper/ContentPane.php b/library/Zend/Dojo/View/Helper/ContentPane.php new file mode 100644 index 00000000..6e15d17c --- /dev/null +++ b/library/Zend/Dojo/View/Helper/ContentPane.php @@ -0,0 +1,66 @@ +_createLayoutContainer($id, $content, $params, $attribs); + } +} diff --git a/library/Zend/Dojo/View/Helper/CurrencyTextBox.php b/library/Zend/Dojo/View/Helper/CurrencyTextBox.php new file mode 100644 index 00000000..79eb4c30 --- /dev/null +++ b/library/Zend/Dojo/View/Helper/CurrencyTextBox.php @@ -0,0 +1,68 @@ +_createFormElement($id, $value, $params, $attribs); + } +} diff --git a/library/Zend/Dojo/View/Helper/CustomDijit.php b/library/Zend/Dojo/View/Helper/CustomDijit.php new file mode 100644 index 00000000..d0c26d27 --- /dev/null +++ b/library/Zend/Dojo/View/Helper/CustomDijit.php @@ -0,0 +1,112 @@ +_defaultDojoType) + ) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception('No dojoType specified; cannot create dijit'); + } elseif (array_key_exists('dojoType', $params)) { + $this->_dijit = $params['dojoType']; + $this->_module = $params['dojoType']; + unset($params['dojoType']); + } else { + $this->_dijit = $this->_defaultDojoType; + $this->_module = $this->_defaultDojoType; + } + + if (array_key_exists('rootNode', $params)) { + $this->setRootNode($params['rootNode']); + unset($params['rootNode']); + } + + return $this->_createLayoutContainer($id, $value, $params, $attribs); + } + + /** + * Begin capturing content. + * + * Requires that either the {@link $_defaultDojotype} property is set, or + * that you pass a value to the "dojoType" key of the $params argument. + * + * @param string $id + * @param array $params + * @param array $attribs + * @return void + */ + public function captureStart($id, array $params = array(), array $attribs = array()) + { + if (!array_key_exists('dojoType', $params) + && (null === $this->_defaultDojoType) + ) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception('No dojoType specified; cannot create dijit'); + } elseif (array_key_exists('dojoType', $params)) { + $this->_dijit = $params['dojoType']; + $this->_module = $params['dojoType']; + unset($params['dojoType']); + } else { + $this->_dijit = $this->_defaultDojoType; + $this->_module = $this->_defaultDojoType; + } + + return parent::captureStart($id, $params, $attribs); + } +} diff --git a/library/Zend/Dojo/View/Helper/DateTextBox.php b/library/Zend/Dojo/View/Helper/DateTextBox.php new file mode 100644 index 00000000..2b383f96 --- /dev/null +++ b/library/Zend/Dojo/View/Helper/DateTextBox.php @@ -0,0 +1,68 @@ +_createFormElement($id, $value, $params, $attribs); + } +} diff --git a/library/Zend/Dojo/View/Helper/Dijit.php b/library/Zend/Dojo/View/Helper/Dijit.php new file mode 100644 index 00000000..bb898248 --- /dev/null +++ b/library/Zend/Dojo/View/Helper/Dijit.php @@ -0,0 +1,344 @@ +dojo = $this->view->dojo(); + $this->dojo->enable(); + return $this; + } + + + /** + * Get root node type + * + * @return string + */ + public function getRootNode() + { + return $this->_rootNode; + } + + /** + * Set root node type + * + * @param string $value + * @return Zend_Dojo_View_Helper_Dijit + */ + public function setRootNode($value) + { + $this->_rootNode = $value; + return $this; + } + + /** + * Whether or not to use declarative dijit creation + * + * @return bool + */ + protected function _useDeclarative() + { + return Zend_Dojo_View_Helper_Dojo::useDeclarative(); + } + + /** + * Whether or not to use programmatic dijit creation + * + * @return bool + */ + protected function _useProgrammatic() + { + return Zend_Dojo_View_Helper_Dojo::useProgrammatic(); + } + + /** + * Whether or not to use programmatic dijit creation w/o script creation + * + * @return bool + */ + protected function _useProgrammaticNoScript() + { + return Zend_Dojo_View_Helper_Dojo::useProgrammaticNoScript(); + } + + /** + * Create a layout container + * + * @param int $id + * @param string $content + * @param array $params + * @param array $attribs + * @param string|null $dijit + * @return string + */ + protected function _createLayoutContainer($id, $content, array $params, array $attribs, $dijit = null) + { + $attribs['id'] = $id; + $attribs = $this->_prepareDijit($attribs, $params, 'layout', $dijit); + + $nodeType = $this->getRootNode(); + $html = '<' . $nodeType . $this->_htmlAttribs($attribs) . '>' + . $content + . "\n"; + + return $html; + } + + /** + * Create HTML representation of a dijit form element + * + * @param string $id + * @param string $value + * @param array $params + * @param array $attribs + * @param string|null $dijit + * @return string + */ + public function _createFormElement($id, $value, array $params, array $attribs, $dijit = null) + { + if (!array_key_exists('id', $attribs)) { + $attribs['id'] = $id; + } + $attribs['name'] = $id; + $attribs['value'] = (string) $value; + $attribs['type'] = $this->_elementType; + + $attribs = $this->_prepareDijit($attribs, $params, 'element', $dijit); + + $html = '_htmlAttribs($attribs) + . $this->getClosingBracket(); + return $html; + } + + /** + * Merge attributes and parameters + * + * Also sets up requires + * + * @param array $attribs + * @param array $params + * @param string $type + * @param string $dijit Dijit type to use (otherwise, pull from $_dijit) + * @return array + */ + protected function _prepareDijit(array $attribs, array $params, $type, $dijit = null) + { + $this->dojo->requireModule($this->_module); + + switch ($type) { + case 'layout': + $stripParams = array('id'); + break; + case 'element': + $stripParams = array('id', 'name', 'value', 'type'); + foreach (array('checked', 'disabled', 'readonly') as $attrib) { + if (array_key_exists($attrib, $attribs)) { + if ($attribs[$attrib]) { + $attribs[$attrib] = $attrib; + } else { + unset($attribs[$attrib]); + } + } + } + break; + case 'textarea': + $stripParams = array('id', 'name', 'type', 'degrade'); + break; + default: + } + + foreach ($stripParams as $param) { + if (array_key_exists($param, $params)) { + unset($params[$param]); + } + } + + // Normalize constraints, if present + foreach ($this->_jsonParams as $param) { + if (array_key_exists($param, $params)) { + require_once 'Zend/Json.php'; + + if (is_array($params[$param])) { + $values = array(); + foreach ($params[$param] as $key => $value) { + if (!is_scalar($value)) { + continue; + } + $values[$key] = $value; + } + } elseif (is_string($params[$param])) { + $values = (array) $params[$param]; + } else { + $values = array(); + } + $values = Zend_Json::encode($values); + if ($this->_useDeclarative()) { + $values = str_replace('"', "'", $values); + } + $params[$param] = $values; + } + } + + $dijit = (null === $dijit) ? $this->_dijit : $dijit; + if ($this->_useDeclarative()) { + $attribs = array_merge($attribs, $params); + if (isset($attribs['required'])) { + $attribs['required'] = ($attribs['required']) ? 'true' : 'false'; + } + $attribs['dojoType'] = $dijit; + } elseif (!$this->_useProgrammaticNoScript()) { + $this->_createDijit($dijit, $attribs['id'], $params); + } + + return $attribs; + } + + /** + * Create a dijit programmatically + * + * @param string $dijit + * @param string $id + * @param array $params + * @return void + */ + protected function _createDijit($dijit, $id, array $params) + { + $params['dojoType'] = $dijit; + + array_walk_recursive($params, array($this, '_castBoolToString')); + + $this->dojo->setDijit($id, $params); + } + + /** + * Cast a boolean to a string value + * + * @param mixed $item + * @param string $key + * @return void + */ + protected function _castBoolToString(&$item, $key) + { + if (!is_bool($item)) { + return; + } + $item = ($item) ? "true" : "false"; + } + + /** + * Render a hidden element to hold a value + * + * @param string $id + * @param string|int|float $value + * @return string + */ + protected function _renderHiddenElement($id, $value) + { + $hiddenAttribs = array( + 'name' => $id, + 'value' => (string) $value, + 'type' => 'hidden', + ); + return '_htmlAttribs($hiddenAttribs) . $this->getClosingBracket(); + } + + /** + * Create JS function for retrieving parent form + * + * @return void + */ + protected function _createGetParentFormFunction() + { + $function =<<dojo->addJavascript($function); + } +} diff --git a/library/Zend/Dojo/View/Helper/DijitContainer.php b/library/Zend/Dojo/View/Helper/DijitContainer.php new file mode 100644 index 00000000..412a07cb --- /dev/null +++ b/library/Zend/Dojo/View/Helper/DijitContainer.php @@ -0,0 +1,92 @@ +_captureLock)) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception(sprintf('Lock already exists for id "%s"', $id)); + } + + $this->_captureLock[$id] = true; + $this->_captureInfo[$id] = array( + 'params' => $params, + 'attribs' => $attribs, + ); + + ob_start(); + return; + } + + /** + * Finish capturing content for layout container + * + * @param string $id + * @return string + */ + public function captureEnd($id) + { + if (!array_key_exists($id, $this->_captureLock)) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception(sprintf('No capture lock exists for id "%s"; nothing to capture', $id)); + } + + $content = ob_get_clean(); + extract($this->_captureInfo[$id]); + unset($this->_captureLock[$id], $this->_captureInfo[$id]); + return $this->_createLayoutContainer($id, $content, $params, $attribs); + } +} diff --git a/library/Zend/Dojo/View/Helper/Dojo.php b/library/Zend/Dojo/View/Helper/Dojo.php new file mode 100644 index 00000000..7ef0e877 --- /dev/null +++ b/library/Zend/Dojo/View/Helper/Dojo.php @@ -0,0 +1,176 @@ +_container = $registry[__CLASS__]; + } + + /** + * Set view object + * + * @param Zend_Dojo_View_Interface $view + * @return void + */ + public function setView(Zend_View_Interface $view) + { + $this->view = $view; + $this->_container->setView($view); + } + + /** + * Return dojo container + * + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function dojo() + { + return $this->_container; + } + + /** + * Proxy to container methods + * + * @param string $method + * @param array $args + * @return mixed + * @throws Zend_Dojo_View_Exception For invalid method calls + */ + public function __call($method, $args) + { + if (!method_exists($this->_container, $method)) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception(sprintf('Invalid method "%s" called on dojo view helper', $method)); + } + + return call_user_func_array(array($this->_container, $method), $args); + } + + /** + * Set whether or not dijits should be created declaratively + * + * @return void + */ + public static function setUseDeclarative() + { + self::$_useProgrammatic = false; + } + + /** + * Set whether or not dijits should be created programmatically + * + * Optionally, specifiy whether or not dijit helpers should generate the + * programmatic dojo. + * + * @param int $style + * @return void + */ + public static function setUseProgrammatic($style = self::PROGRAMMATIC_SCRIPT) + { + if (!in_array($style, array(self::PROGRAMMATIC_SCRIPT, self::PROGRAMMATIC_NOSCRIPT))) { + $style = self::PROGRAMMATIC_SCRIPT; + } + self::$_useProgrammatic = $style; + } + + /** + * Should dijits be created declaratively? + * + * @return bool + */ + public static function useDeclarative() + { + return (false === self::$_useProgrammatic); + } + + /** + * Should dijits be created programmatically? + * + * @return bool + */ + public static function useProgrammatic() + { + return (false !== self::$_useProgrammatic); + } + + /** + * Should dijits be created programmatically but without scripts? + * + * @return bool + */ + public static function useProgrammaticNoScript() + { + return (self::PROGRAMMATIC_NOSCRIPT === self::$_useProgrammatic); + } +} diff --git a/library/Zend/Dojo/View/Helper/Dojo/Container.php b/library/Zend/Dojo/View/Helper/Dojo/Container.php new file mode 100644 index 00000000..2e7cf7a8 --- /dev/null +++ b/library/Zend/Dojo/View/Helper/Dojo/Container.php @@ -0,0 +1,1205 @@ +view = $view; + } + + /** + * Enable dojo + * + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function enable() + { + $this->_enabled = true; + return $this; + } + + /** + * Disable dojo + * + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function disable() + { + $this->_enabled = false; + return $this; + } + + /** + * Is dojo enabled? + * + * @return bool + */ + public function isEnabled() + { + return $this->_enabled; + } + + /** + * Add options for the Dojo Container to use + * + * @param array|Zend_Config Array or Zend_Config object with options to use + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function setOptions($options) + { + if($options instanceof Zend_Config) { + $options = $options->toArray(); + } + + foreach($options as $key => $value) { + $key = strtolower($key); + switch($key) { + case 'requiremodules': + $this->requireModule($value); + break; + case 'modulepaths': + foreach($value as $module => $path) { + $this->registerModulePath($module, $path); + } + break; + case 'layers': + $value = (array) $value; + foreach($value as $layer) { + $this->addLayer($layer); + } + break; + case 'cdnbase': + $this->setCdnBase($value); + break; + case 'cdnversion': + $this->setCdnVersion($value); + break; + case 'cdndojopath': + $this->setCdnDojoPath($value); + break; + case 'localpath': + $this->setLocalPath($value); + break; + case 'djconfig': + $this->setDjConfig($value); + break; + case 'stylesheetmodules': + $value = (array) $value; + foreach($value as $module) { + $this->addStylesheetModule($module); + } + break; + case 'stylesheets': + $value = (array) $value; + foreach($value as $stylesheet) { + $this->addStylesheet($stylesheet); + } + break; + case 'registerdojostylesheet': + $this->registerDojoStylesheet($value); + break; + case 'enable': + if($value) { + $this->enable(); + } else { + $this->disable(); + } + } + } + + return $this; + } + + /** + * Specify one or multiple modules to require + * + * @param string|array $modules + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function requireModule($modules) + { + if (!is_string($modules) && !is_array($modules)) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception('Invalid module name specified; must be a string or an array of strings'); + } + + $modules = (array) $modules; + + foreach ($modules as $mod) { + if (!preg_match('/^[a-z][a-z0-9._-]+$/i', $mod)) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception(sprintf('Module name specified, "%s", contains invalid characters', (string) $mod)); + } + + if (!in_array($mod, $this->_modules)) { + $this->_modules[] = $mod; + } + } + + return $this; + } + + /** + * Retrieve list of modules to require + * + * @return array + */ + public function getModules() + { + return $this->_modules; + } + + /** + * Register a module path + * + * @param string $module The module to register a path for + * @param string $path The path to register for the module + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function registerModulePath($module, $path) + { + $path = (string) $path; + if (!in_array($module, $this->_modulePaths)) { + $this->_modulePaths[$module] = $path; + } + + return $this; + } + + /** + * List registered module paths + * + * @return array + */ + public function getModulePaths() + { + return $this->_modulePaths; + } + + /** + * Add layer (custom build) path + * + * @param string $path + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function addLayer($path) + { + $path = (string) $path; + if (!in_array($path, $this->_layers)) { + $this->_layers[] = $path; + } + + return $this; + } + + /** + * Get registered layers + * + * @return array + */ + public function getLayers() + { + return $this->_layers; + } + + /** + * Remove a registered layer + * + * @param string $path + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function removeLayer($path) + { + $path = (string) $path; + $layers = array_flip($this->_layers); + if (array_key_exists($path, $layers)) { + unset($layers[$path]); + $this->_layers = array_keys($layers); + } + return $this; + } + + /** + * Clear all registered layers + * + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function clearLayers() + { + $this->_layers = array(); + return $this; + } + + /** + * Set CDN base path + * + * @param string $url + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function setCdnBase($url) + { + $this->_cdnBase = (string) $url; + return $this; + } + + /** + * Return CDN base URL + * + * @return string + */ + public function getCdnBase() + { + return $this->_cdnBase; + } + + /** + * Use CDN, using version specified + * + * @param string $version + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function setCdnVersion($version = null) + { + $this->enable(); + if (preg_match('/^[1-9]\.[0-9](\.[0-9])?$/', $version)) { + $this->_cdnVersion = $version; + } + return $this; + } + + /** + * Get CDN version + * + * @return string + */ + public function getCdnVersion() + { + return $this->_cdnVersion; + } + + /** + * Set CDN path to dojo (relative to CDN base + version) + * + * @param string $path + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function setCdnDojoPath($path) + { + $this->_cdnDojoPath = (string) $path; + return $this; + } + + /** + * Get CDN path to dojo (relative to CDN base + version) + * + * @return string + */ + public function getCdnDojoPath() + { + return $this->_cdnDojoPath; + } + + /** + * Are we using the CDN? + * + * @return bool + */ + public function useCdn() + { + return !$this->useLocalPath(); + } + + /** + * Set path to local dojo + * + * @param string $path + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function setLocalPath($path) + { + $this->enable(); + $this->_localPath = (string) $path; + return $this; + } + + /** + * Get local path to dojo + * + * @return string + */ + public function getLocalPath() + { + return $this->_localPath; + } + + /** + * Are we using a local path? + * + * @return bool + */ + public function useLocalPath() + { + return (null === $this->_localPath) ? false : true; + } + + /** + * Set Dojo configuration + * + * @param string $option + * @param mixed $value + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function setDjConfig(array $config) + { + $this->_djConfig = $config; + return $this; + } + + /** + * Set Dojo configuration option + * + * @param string $option + * @param mixed $value + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function setDjConfigOption($option, $value) + { + $option = (string) $option; + $this->_djConfig[$option] = $value; + return $this; + } + + /** + * Retrieve dojo configuration values + * + * @return array + */ + public function getDjConfig() + { + return $this->_djConfig; + } + + /** + * Get dojo configuration value + * + * @param string $option + * @param mixed $default + * @return mixed + */ + public function getDjConfigOption($option, $default = null) + { + $option = (string) $option; + if (array_key_exists($option, $this->_djConfig)) { + return $this->_djConfig[$option]; + } + return $default; + } + + /** + * Add a stylesheet by module name + * + * @param string $module + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function addStylesheetModule($module) + { + if (!preg_match('/^[a-z0-9]+\.[a-z0-9_-]+(\.[a-z0-9_-]+)*$/i', $module)) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception('Invalid stylesheet module specified'); + } + if (!in_array($module, $this->_stylesheetModules)) { + $this->_stylesheetModules[] = $module; + } + return $this; + } + + /** + * Get all stylesheet modules currently registered + * + * @return array + */ + public function getStylesheetModules() + { + return $this->_stylesheetModules; + } + + /** + * Add a stylesheet + * + * @param string $path + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function addStylesheet($path) + { + $path = (string) $path; + if (!in_array($path, $this->_stylesheets)) { + $this->_stylesheets[] = (string) $path; + } + return $this; + } + + /** + * Register the dojo.css stylesheet? + * + * With no arguments, returns the status of the flag; with arguments, sets + * the flag and returns the object. + * + * @param null|bool $flag + * @return Zend_Dojo_View_Helper_Dojo_Container|bool + */ + public function registerDojoStylesheet($flag = null) + { + if (null === $flag) { + return $this->_registerDojoStylesheet; + } + + $this->_registerDojoStylesheet = (bool) $flag; + return $this; + } + + /** + * Retrieve registered stylesheets + * + * @return array + */ + public function getStylesheets() + { + return $this->_stylesheets; + } + + /** + * Add a script to execute onLoad + * + * dojo.addOnLoad accepts: + * - function name + * - lambda + * + * @param string $callback Lambda + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function addOnLoad($callback) + { + if (!in_array($callback, $this->_onLoadActions, true)) { + $this->_onLoadActions[] = $callback; + } + return $this; + } + + /** + * Prepend an onLoad event to the list of onLoad actions + * + * @param string $callback Lambda + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function prependOnLoad($callback) + { + if (!in_array($callback, $this->_onLoadActions, true)) { + array_unshift($this->_onLoadActions, $callback); + } + return $this; + } + + /** + * Retrieve all registered onLoad actions + * + * @return array + */ + public function getOnLoadActions() + { + return $this->_onLoadActions; + } + + /** + * Start capturing routines to run onLoad + * + * @return bool + */ + public function onLoadCaptureStart() + { + if ($this->_captureLock) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception('Cannot nest onLoad captures'); + } + + $this->_captureLock = true; + ob_start(); + return; + } + + /** + * Stop capturing routines to run onLoad + * + * @return bool + */ + public function onLoadCaptureEnd() + { + $data = ob_get_clean(); + $this->_captureLock = false; + + $this->addOnLoad($data); + return true; + } + + /** + * Add a programmatic dijit + * + * @param string $id + * @param array $params + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function addDijit($id, array $params) + { + if (array_key_exists($id, $this->_dijits)) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception(sprintf('Duplicate dijit with id "%s" already registered', $id)); + } + + $this->_dijits[$id] = array( + 'id' => $id, + 'params' => $params, + ); + + return $this; + } + + /** + * Set a programmatic dijit (overwrites) + * + * @param string $id + * @param array $params + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function setDijit($id, array $params) + { + $this->removeDijit($id); + return $this->addDijit($id, $params); + } + + /** + * Add multiple dijits at once + * + * Expects an array of id => array $params pairs + * + * @param array $dijits + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function addDijits(array $dijits) + { + foreach ($dijits as $id => $params) { + $this->addDijit($id, $params); + } + return $this; + } + + /** + * Set multiple dijits at once (overwrites) + * + * Expects an array of id => array $params pairs + * + * @param array $dijits + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function setDijits(array $dijits) + { + $this->clearDijits(); + return $this->addDijits($dijits); + } + + /** + * Is the given programmatic dijit already registered? + * + * @param string $id + * @return bool + */ + public function hasDijit($id) + { + return array_key_exists($id, $this->_dijits); + } + + /** + * Retrieve a dijit by id + * + * @param string $id + * @return array|null + */ + public function getDijit($id) + { + if ($this->hasDijit($id)) { + return $this->_dijits[$id]['params']; + } + return null; + } + + /** + * Retrieve all dijits + * + * Returns dijits as an array of assoc arrays + * + * @return array + */ + public function getDijits() + { + return array_values($this->_dijits); + } + + /** + * Remove a programmatic dijit if it exists + * + * @param string $id + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function removeDijit($id) + { + if (array_key_exists($id, $this->_dijits)) { + unset($this->_dijits[$id]); + } + + return $this; + } + + /** + * Clear all dijits + * + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function clearDijits() + { + $this->_dijits = array(); + return $this; + } + + /** + * Render dijits as JSON structure + * + * @return string + */ + public function dijitsToJson() + { + require_once 'Zend/Json.php'; + return Zend_Json::encode($this->getDijits(), false, array('enableJsonExprFinder' => true)); + } + + /** + * Create dijit loader functionality + * + * @return void + */ + public function registerDijitLoader() + { + if (!$this->_dijitLoaderRegistered) { + $js =<<requireModule('dojo.parser'); + $this->_addZendLoad($js); + $this->addJavascript('var zendDijits = ' . $this->dijitsToJson() . ';'); + $this->_dijitLoaderRegistered = true; + } + } + + /** + * Add arbitrary javascript to execute in dojo JS container + * + * @param string $js + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function addJavascript($js) + { + $js = trim($js); + if (!in_array(substr($js, -1), array(';', '}'))) { + $js .= ';'; + } + + if (in_array($js, $this->_javascriptStatements)) { + return $this; + } + + $this->_javascriptStatements[] = $js; + return $this; + } + + /** + * Return all registered javascript statements + * + * @return array + */ + public function getJavascript() + { + return $this->_javascriptStatements; + } + + /** + * Clear arbitrary javascript stack + * + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function clearJavascript() + { + $this->_javascriptStatements = array(); + return $this; + } + + /** + * Capture arbitrary javascript to include in dojo script + * + * @return void + */ + public function javascriptCaptureStart() + { + if ($this->_captureLock) { + require_once 'Zend/Dojo/View/Exception.php'; + throw new Zend_Dojo_View_Exception('Cannot nest captures'); + } + + $this->_captureLock = true; + ob_start(); + return; + } + + /** + * Finish capturing arbitrary javascript to include in dojo script + * + * @return true + */ + public function javascriptCaptureEnd() + { + $data = ob_get_clean(); + $this->_captureLock = false; + + $this->addJavascript($data); + return true; + } + + /** + * String representation of dojo environment + * + * @return string + */ + public function __toString() + { + if (!$this->isEnabled()) { + return ''; + } + + $this->_isXhtml = $this->view->doctype()->isXhtml(); + + if (Zend_Dojo_View_Helper_Dojo::useDeclarative()) { + if (null === $this->getDjConfigOption('parseOnLoad')) { + $this->setDjConfigOption('parseOnLoad', true); + } + } + + if (!empty($this->_dijits)) { + $this->registerDijitLoader(); + } + + $html = $this->_renderStylesheets() . PHP_EOL + . $this->_renderDjConfig() . PHP_EOL + . $this->_renderDojoScriptTag() . PHP_EOL + . $this->_renderLayers() . PHP_EOL + . $this->_renderExtras(); + return $html; + } + + /** + * Retrieve local path to dojo resources for building relative paths + * + * @return string + */ + protected function _getLocalRelativePath() + { + if (null === $this->_localRelativePath) { + $localPath = $this->getLocalPath(); + $localPath = preg_replace('|[/\\\\]dojo[/\\\\]dojo.js[^/\\\\]*$|i', '', $localPath); + $this->_localRelativePath = $localPath; + } + return $this->_localRelativePath; + } + + /** + * Render dojo stylesheets + * + * @return string + */ + protected function _renderStylesheets() + { + if ($this->useCdn()) { + $base = $this->getCdnBase() + . $this->getCdnVersion(); + } else { + $base = $this->_getLocalRelativePath(); + } + + $registeredStylesheets = $this->getStylesheetModules(); + foreach ($registeredStylesheets as $stylesheet) { + $themeName = substr($stylesheet, strrpos($stylesheet, '.') + 1); + $stylesheet = str_replace('.', '/', $stylesheet); + $stylesheets[] = $base . '/' . $stylesheet . '/' . $themeName . '.css'; + } + + foreach ($this->getStylesheets() as $stylesheet) { + $stylesheets[] = $stylesheet; + } + + if ($this->_registerDojoStylesheet) { + $stylesheets[] = $base . '/dojo/resources/dojo.css'; + } + + if (empty($stylesheets)) { + return ''; + } + + array_reverse($stylesheets); + $style = ''; + + return $style; + } + + /** + * Render DjConfig values + * + * @return string + */ + protected function _renderDjConfig() + { + $djConfigValues = $this->getDjConfig(); + if (empty($djConfigValues)) { + return ''; + } + + require_once 'Zend/Json.php'; + $scriptTag = ''; + + return $scriptTag; + } + + /** + * Render dojo script tag + * + * Renders Dojo script tag by utilizing either local path provided or the + * CDN. If any djConfig values were set, they will be serialized and passed + * with that attribute. + * + * @return string + */ + protected function _renderDojoScriptTag() + { + if ($this->useCdn()) { + $source = $this->getCdnBase() + . $this->getCdnVersion() + . $this->getCdnDojoPath(); + } else { + $source = $this->getLocalPath(); + } + + $scriptTag = ''; + return $scriptTag; + } + + /** + * Render layers (custom builds) as script tags + * + * @return string + */ + protected function _renderLayers() + { + $layers = $this->getLayers(); + if (empty($layers)) { + return ''; + } + + $enc = 'UTF-8'; + if ($this->view instanceof Zend_View_Interface + && method_exists($this->view, 'getEncoding') + ) { + $enc = $this->view->getEncoding(); + } + + $html = array(); + foreach ($layers as $path) { + $html[] = sprintf( + '', + htmlspecialchars($path, ENT_QUOTES, $enc) + ); + } + + return implode("\n", $html); + } + + /** + * Render dojo module paths and requires + * + * @return string + */ + protected function _renderExtras() + { + $js = array(); + $modulePaths = $this->getModulePaths(); + if (!empty($modulePaths)) { + foreach ($modulePaths as $module => $path) { + $js[] = 'dojo.registerModulePath("' . $this->view->escape($module) . '", "' . $this->view->escape($path) . '");'; + } + } + + $modules = $this->getModules(); + if (!empty($modules)) { + foreach ($modules as $module) { + $js[] = 'dojo.require("' . $this->view->escape($module) . '");'; + } + } + + $onLoadActions = array(); + // Get Zend specific onLoad actions; these will always be first to + // ensure that dijits are created in the correct order + foreach ($this->_getZendLoadActions() as $callback) { + $onLoadActions[] = 'dojo.addOnLoad(' . $callback . ');'; + } + + // Get all other onLoad actions + foreach ($this->getOnLoadActions() as $callback) { + $onLoadActions[] = 'dojo.addOnLoad(' . $callback . ');'; + } + + $javascript = implode("\n ", $this->getJavascript()); + + $content = ''; + if (!empty($js)) { + $content .= implode("\n ", $js) . "\n"; + } + + if (!empty($onLoadActions)) { + $content .= implode("\n ", $onLoadActions) . "\n"; + } + + if (!empty($javascript)) { + $content .= $javascript . "\n"; + } + + if (preg_match('/^\s*$/s', $content)) { + return ''; + } + + $html = ''; + return $html; + } + + /** + * Add an onLoad action related to ZF dijit creation + * + * This method is public, but prefixed with an underscore to indicate that + * it should not normally be called by userland code. It is pertinent to + * ensuring that the correct order of operations occurs during dijit + * creation. + * + * @param string $callback + * @return Zend_Dojo_View_Helper_Dojo_Container + */ + public function _addZendLoad($callback) + { + if (!in_array($callback, $this->_zendLoadActions, true)) { + $this->_zendLoadActions[] = $callback; + } + return $this; + } + + /** + * Retrieve all ZF dijit callbacks + * + * @return array + */ + public function _getZendLoadActions() + { + return $this->_zendLoadActions; + } +} diff --git a/library/Zend/Dojo/View/Helper/Editor.php b/library/Zend/Dojo/View/Helper/Editor.php new file mode 100644 index 00000000..e40cf8de --- /dev/null +++ b/library/Zend/Dojo/View/Helper/Editor.php @@ -0,0 +1,199 @@ + 'LinkDialog', + 'insertImage' => 'LinkDialog', + 'fontName' => 'FontChoice', + 'fontSize' => 'FontChoice', + 'formatBlock' => 'FontChoice', + 'foreColor' => 'TextColor', + 'hiliteColor' => 'TextColor', + 'enterKeyHandling' => 'EnterKeyHandling', + 'fullScreen' => 'FullScreen', + 'newPage' => 'NewPage', + 'print' => 'Print', + 'tabIndent' => 'TabIndent', + 'toggleDir' => 'ToggleDir', + 'viewSource' => 'ViewSource' + ); + + /** + * JSON-encoded parameters + * @var array + */ + protected $_jsonParams = array('captureEvents', 'events', 'plugins', 'extraPlugins'); + + /** + * dijit.Editor + * + * @param string $id + * @param string $value + * @param array $params + * @param array $attribs + * @return string + */ + public function editor($id, $value = null, $params = array(), $attribs = array()) + { + if (isset($params['plugins'])) { + foreach ($this->_getRequiredModules($params['plugins']) as $module) { + $this->dojo->requireModule($module); + } + } + + // Previous versions allowed specifying "degrade" to allow using a + // textarea instead of a div -- but this is insecure. Removing the + // parameter if set to prevent its injection in the dijit. + if (isset($params['degrade'])) { + unset($params['degrade']); + } + + $hiddenName = $id; + if (array_key_exists('id', $attribs)) { + $hiddenId = $attribs['id']; + } else { + $hiddenId = $hiddenName; + } + $hiddenId = $this->_normalizeId($hiddenId); + + $textareaName = $this->_normalizeEditorName($hiddenName); + $textareaId = $hiddenId . '-Editor'; + + $hiddenAttribs = array( + 'id' => $hiddenId, + 'name' => $hiddenName, + 'value' => $value, + 'type' => 'hidden', + ); + $attribs['id'] = $textareaId; + + $this->_createGetParentFormFunction(); + $this->_createEditorOnSubmit($hiddenId, $textareaId); + + $attribs = $this->_prepareDijit($attribs, $params, 'textarea'); + + $html = '_htmlAttribs($attribs) . '>' + . $value + . "\n"; + + // Embed a textarea in a