File indexing completed on 2024-05-12 06:03:17

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_XmlRpc
0017  * @subpackage Value
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  * @version    $Id$
0021  */
0022 
0023 /**
0024  * Represent a native XML-RPC value entity, used as parameters for the methods
0025  * called by the Zend_XmlRpc_Client object and as the return value for those calls.
0026  *
0027  * This object as a very important static function Zend_XmlRpc_Value::getXmlRpcValue, this
0028  * function acts likes a factory for the Zend_XmlRpc_Value objects
0029  *
0030  * Using this function, users/Zend_XmlRpc_Client object can create the Zend_XmlRpc_Value objects
0031  * from PHP variables, XML string or by specifing the exact XML-RPC natvie type
0032  *
0033  * @package    Zend_XmlRpc
0034  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0035  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0036  */
0037 abstract class Zend_XmlRpc_Value
0038 {
0039     /**
0040      * The native XML-RPC representation of this object's value
0041      *
0042      * If the native type of this object is array or struct, this will be an array
0043      * of Zend_XmlRpc_Value objects
0044      */
0045     protected $_value;
0046 
0047     /**
0048      * The native XML-RPC type of this object
0049      * One of the XMLRPC_TYPE_* constants
0050      */
0051     protected $_type;
0052 
0053     /**
0054      * XML code representation of this object (will be calculated only once)
0055      */
0056     protected $_xml;
0057 
0058     /**
0059      * @var Zend_XmlRpc_Generator_GeneratorAbstract
0060      */
0061     protected static $_generator;
0062 
0063     /**
0064      * Specify that the XML-RPC native type will be auto detected from a PHP variable type
0065      */
0066     const AUTO_DETECT_TYPE = 'auto_detect';
0067 
0068     /**
0069      * Specify that the XML-RPC value will be parsed out from a given XML code
0070      */
0071     const XML_STRING = 'xml';
0072 
0073     /**
0074      * All the XML-RPC native types
0075      */
0076     const XMLRPC_TYPE_I4        = 'i4';
0077     const XMLRPC_TYPE_INTEGER   = 'int';
0078     const XMLRPC_TYPE_I8        = 'i8';
0079     const XMLRPC_TYPE_APACHEI8  = 'ex:i8';
0080     const XMLRPC_TYPE_DOUBLE    = 'double';
0081     const XMLRPC_TYPE_BOOLEAN   = 'boolean';
0082     const XMLRPC_TYPE_STRING    = 'string';
0083     const XMLRPC_TYPE_DATETIME  = 'dateTime.iso8601';
0084     const XMLRPC_TYPE_BASE64    = 'base64';
0085     const XMLRPC_TYPE_ARRAY     = 'array';
0086     const XMLRPC_TYPE_STRUCT    = 'struct';
0087     const XMLRPC_TYPE_NIL       = 'nil';
0088     const XMLRPC_TYPE_APACHENIL = 'ex:nil';
0089 
0090     /**
0091      * Get the native XML-RPC type (the type is one of the Zend_XmlRpc_Value::XMLRPC_TYPE_* constants)
0092      *
0093      * @return string
0094      */
0095     public function getType()
0096     {
0097         return $this->_type;
0098     }
0099 
0100     /**
0101      * Get XML generator instance
0102      *
0103      * @return Zend_XmlRpc_Generator_GeneratorAbstract
0104      */
0105     public static function getGenerator()
0106     {
0107         if (!self::$_generator) {
0108             if (extension_loaded('xmlwriter')) {
0109                 // require_once 'Zend/XmlRpc/Generator/XmlWriter.php';
0110                 self::$_generator = new Zend_XmlRpc_Generator_XmlWriter();
0111             } else {
0112                 // require_once 'Zend/XmlRpc/Generator/DomDocument.php';
0113                 self::$_generator = new Zend_XmlRpc_Generator_DomDocument();
0114             }
0115         }
0116 
0117         return self::$_generator;
0118     }
0119 
0120     /**
0121      * Sets XML generator instance
0122      *
0123      * @param Zend_XmlRpc_Generator_GeneratorAbstract $generator
0124      * @return void
0125      */
0126     public static function setGenerator(Zend_XmlRpc_Generator_GeneratorAbstract $generator)
0127     {
0128         self::$_generator = $generator;
0129     }
0130 
0131     /**
0132      * Changes the encoding of the generator
0133      *
0134      * @param string $encoding
0135      * @return void
0136      */
0137     public static function setEncoding($encoding)
0138     {
0139         $generator = self::getGenerator();
0140         $newGenerator = new $generator($encoding);
0141         self::setGenerator($newGenerator);
0142     }
0143 
0144     /**
0145      * Return the value of this object, convert the XML-RPC native value into a PHP variable
0146      *
0147      * @return mixed
0148      */
0149     abstract public function getValue();
0150 
0151 
0152     /**
0153      * Return the XML code that represent a native MXL-RPC value
0154      *
0155      * @return string
0156      */
0157     public function saveXml()
0158     {
0159         if (!$this->_xml) {
0160             $this->generateXml();
0161             $this->_xml = (string) $this->getGenerator();
0162         }
0163         return $this->_xml;
0164     }
0165 
0166     /**
0167      * Generate XML code that represent a native XML/RPC value
0168      *
0169      * @return void
0170      */
0171     public function generateXml()
0172     {
0173         $this->_generateXml();
0174     }
0175 
0176     /**
0177      * Creates a Zend_XmlRpc_Value* object, representing a native XML-RPC value
0178      * A XmlRpcValue object can be created in 3 ways:
0179      * 1. Autodetecting the native type out of a PHP variable
0180      *    (if $type is not set or equal to Zend_XmlRpc_Value::AUTO_DETECT_TYPE)
0181      * 2. By specifing the native type ($type is one of the Zend_XmlRpc_Value::XMLRPC_TYPE_* constants)
0182      * 3. From a XML string ($type is set to Zend_XmlRpc_Value::XML_STRING)
0183      *
0184      * By default the value type is autodetected according to it's PHP type
0185      *
0186      * @param mixed $value
0187      * @param Zend_XmlRpc_Value::constant $type
0188      *
0189      * @return Zend_XmlRpc_Value
0190      * @static
0191      */
0192     public static function getXmlRpcValue($value, $type = self::AUTO_DETECT_TYPE)
0193     {
0194         switch ($type) {
0195             case self::AUTO_DETECT_TYPE:
0196                 // Auto detect the XML-RPC native type from the PHP type of $value
0197                 return self::_phpVarToNativeXmlRpc($value);
0198 
0199             case self::XML_STRING:
0200                 // Parse the XML string given in $value and get the XML-RPC value in it
0201                 return self::_xmlStringToNativeXmlRpc($value);
0202 
0203             case self::XMLRPC_TYPE_I4:
0204                 // fall through to the next case
0205             case self::XMLRPC_TYPE_INTEGER:
0206                 // require_once 'Zend/XmlRpc/Value/Integer.php';
0207                 return new Zend_XmlRpc_Value_Integer($value);
0208 
0209             case self::XMLRPC_TYPE_I8:
0210                 // fall through to the next case
0211             case self::XMLRPC_TYPE_APACHEI8:
0212                 // require_once 'Zend/XmlRpc/Value/BigInteger.php';
0213                 return new Zend_XmlRpc_Value_BigInteger($value);
0214 
0215             case self::XMLRPC_TYPE_DOUBLE:
0216                 // require_once 'Zend/XmlRpc/Value/Double.php';
0217                 return new Zend_XmlRpc_Value_Double($value);
0218 
0219             case self::XMLRPC_TYPE_BOOLEAN:
0220                 // require_once 'Zend/XmlRpc/Value/Boolean.php';
0221                 return new Zend_XmlRpc_Value_Boolean($value);
0222 
0223             case self::XMLRPC_TYPE_STRING:
0224                 // require_once 'Zend/XmlRpc/Value/String.php';
0225                 return new Zend_XmlRpc_Value_String($value);
0226 
0227             case self::XMLRPC_TYPE_BASE64:
0228                 // require_once 'Zend/XmlRpc/Value/Base64.php';
0229                 return new Zend_XmlRpc_Value_Base64($value);
0230 
0231             case self::XMLRPC_TYPE_NIL:
0232                 // fall through to the next case
0233             case self::XMLRPC_TYPE_APACHENIL:
0234                 // require_once 'Zend/XmlRpc/Value/Nil.php';
0235                 return new Zend_XmlRpc_Value_Nil();
0236 
0237             case self::XMLRPC_TYPE_DATETIME:
0238                 // require_once 'Zend/XmlRpc/Value/DateTime.php';
0239                 return new Zend_XmlRpc_Value_DateTime($value);
0240 
0241             case self::XMLRPC_TYPE_ARRAY:
0242                 // require_once 'Zend/XmlRpc/Value/Array.php';
0243                 return new Zend_XmlRpc_Value_Array($value);
0244 
0245             case self::XMLRPC_TYPE_STRUCT:
0246                 // require_once 'Zend/XmlRpc/Value/Struct.php';
0247                 return new Zend_XmlRpc_Value_Struct($value);
0248 
0249             default:
0250                 // require_once 'Zend/XmlRpc/Value/Exception.php';
0251                 throw new Zend_XmlRpc_Value_Exception('Given type is not a '. __CLASS__ .' constant');
0252         }
0253     }
0254 
0255     /**
0256      * Get XML-RPC type for a PHP native variable
0257      *
0258      * @static
0259      * @param mixed $value
0260      * @return string
0261      */
0262     public static function getXmlRpcTypeByValue($value)
0263     {
0264         if (is_object($value)) {
0265             if ($value instanceof Zend_XmlRpc_Value) {
0266                 return $value->getType();
0267             } elseif (($value instanceof Zend_Date) || ($value instanceof DateTime)) {
0268                 return self::XMLRPC_TYPE_DATETIME;
0269             }
0270             return self::getXmlRpcTypeByValue(get_object_vars($value));
0271         } elseif (is_array($value)) {
0272             if (!empty($value) && is_array($value) && (array_keys($value) !== range(0, count($value) - 1))) {
0273                 return self::XMLRPC_TYPE_STRUCT;
0274             }
0275             return self::XMLRPC_TYPE_ARRAY;
0276         } elseif (is_int($value)) {
0277             return ($value > PHP_INT_MAX) ? self::XMLRPC_TYPE_I8 : self::XMLRPC_TYPE_INTEGER;
0278         } elseif (is_double($value)) {
0279             return self::XMLRPC_TYPE_DOUBLE;
0280         } elseif (is_bool($value)) {
0281             return self::XMLRPC_TYPE_BOOLEAN;
0282         } elseif (is_null($value)) {
0283             return self::XMLRPC_TYPE_NIL;
0284         } elseif (is_string($value)) {
0285             return self::XMLRPC_TYPE_STRING;
0286         }
0287         throw new Zend_XmlRpc_Value_Exception(sprintf(
0288             'No matching XMLRPC type found for php type %s.',
0289             gettype($value)
0290         ));
0291     }
0292 
0293     /**
0294      * Transform a PHP native variable into a XML-RPC native value
0295      *
0296      * @param mixed $value The PHP variable for convertion
0297      *
0298      * @return Zend_XmlRpc_Value
0299      * @static
0300      */
0301     protected static function _phpVarToNativeXmlRpc($value)
0302     {
0303         // @see http://framework.zend.com/issues/browse/ZF-8623
0304         if (is_object($value)) {
0305             if ($value instanceof Zend_XmlRpc_Value) {
0306                 return $value;
0307             }
0308             if ($value instanceof Zend_Crypt_Math_BigInteger) {
0309                 // require_once 'Zend/XmlRpc/Value/Exception.php';
0310                 throw new Zend_XmlRpc_Value_Exception(
0311                     'Using Zend_Crypt_Math_BigInteger to get an ' .
0312                     'instance of Zend_XmlRpc_Value_BigInteger is not ' .
0313                     'available anymore.'
0314                 );
0315             }
0316         }
0317 
0318         switch (self::getXmlRpcTypeByValue($value))
0319         {
0320             case self::XMLRPC_TYPE_DATETIME:
0321                 // require_once 'Zend/XmlRpc/Value/DateTime.php';
0322                 return new Zend_XmlRpc_Value_DateTime($value);
0323 
0324             case self::XMLRPC_TYPE_ARRAY:
0325                 // require_once 'Zend/XmlRpc/Value/Array.php';
0326                 return new Zend_XmlRpc_Value_Array($value);
0327 
0328             case self::XMLRPC_TYPE_STRUCT:
0329                 // require_once 'Zend/XmlRpc/Value/Struct.php';
0330                 return new Zend_XmlRpc_Value_Struct($value);
0331 
0332             case self::XMLRPC_TYPE_INTEGER:
0333                 // require_once 'Zend/XmlRpc/Value/Integer.php';
0334                 return new Zend_XmlRpc_Value_Integer($value);
0335 
0336             case self::XMLRPC_TYPE_DOUBLE:
0337                 // require_once 'Zend/XmlRpc/Value/Double.php';
0338                 return new Zend_XmlRpc_Value_Double($value);
0339 
0340             case self::XMLRPC_TYPE_BOOLEAN:
0341                 // require_once 'Zend/XmlRpc/Value/Boolean.php';
0342                 return new Zend_XmlRpc_Value_Boolean($value);
0343 
0344             case self::XMLRPC_TYPE_NIL:
0345                 // require_once 'Zend/XmlRpc/Value/Nil.php';
0346                 return new Zend_XmlRpc_Value_Nil;
0347 
0348             case self::XMLRPC_TYPE_STRING:
0349                 // Fall through to the next case
0350             default:
0351                 // If type isn't identified (or identified as string), it treated as string
0352                 // require_once 'Zend/XmlRpc/Value/String.php';
0353                 return new Zend_XmlRpc_Value_String($value);
0354         }
0355     }
0356 
0357 
0358     /**
0359      * Transform an XML string into a XML-RPC native value
0360      *
0361      * @param string|SimpleXMLElement $xml A SimpleXMLElement object represent the XML string
0362      *                                            It can be also a valid XML string for convertion
0363      *
0364      * @return Zend_XmlRpc_Value
0365      * @static
0366      */
0367     protected static function _xmlStringToNativeXmlRpc($xml)
0368     {
0369         self::_createSimpleXMLElement($xml);
0370 
0371         self::_extractTypeAndValue($xml, $type, $value);
0372 
0373         switch ($type) {
0374             // All valid and known XML-RPC native values
0375             case self::XMLRPC_TYPE_I4:
0376                 // Fall through to the next case
0377             case self::XMLRPC_TYPE_INTEGER:
0378                 // require_once 'Zend/XmlRpc/Value/Integer.php';
0379                 $xmlrpcValue = new Zend_XmlRpc_Value_Integer($value);
0380                 break;
0381             case self::XMLRPC_TYPE_APACHEI8:
0382                 // Fall through to the next case
0383             case self::XMLRPC_TYPE_I8:
0384                 // require_once 'Zend/XmlRpc/Value/BigInteger.php';
0385                 $xmlrpcValue = new Zend_XmlRpc_Value_BigInteger($value);
0386                 break;
0387             case self::XMLRPC_TYPE_DOUBLE:
0388                 // require_once 'Zend/XmlRpc/Value/Double.php';
0389                 $xmlrpcValue = new Zend_XmlRpc_Value_Double($value);
0390                 break;
0391             case self::XMLRPC_TYPE_BOOLEAN:
0392                 // require_once 'Zend/XmlRpc/Value/Boolean.php';
0393                 $xmlrpcValue = new Zend_XmlRpc_Value_Boolean($value);
0394                 break;
0395             case self::XMLRPC_TYPE_STRING:
0396                 // require_once 'Zend/XmlRpc/Value/String.php';
0397                 $xmlrpcValue = new Zend_XmlRpc_Value_String($value);
0398                 break;
0399             case self::XMLRPC_TYPE_DATETIME:  // The value should already be in a iso8601 format
0400                 // require_once 'Zend/XmlRpc/Value/DateTime.php';
0401                 $xmlrpcValue = new Zend_XmlRpc_Value_DateTime($value);
0402                 break;
0403             case self::XMLRPC_TYPE_BASE64:    // The value should already be base64 encoded
0404                 // require_once 'Zend/XmlRpc/Value/Base64.php';
0405                 $xmlrpcValue = new Zend_XmlRpc_Value_Base64($value, true);
0406                 break;
0407             case self::XMLRPC_TYPE_NIL:
0408                 // Fall through to the next case
0409             case self::XMLRPC_TYPE_APACHENIL:
0410                 // The value should always be NULL
0411                 // require_once 'Zend/XmlRpc/Value/Nil.php';
0412                 $xmlrpcValue = new Zend_XmlRpc_Value_Nil();
0413                 break;
0414             case self::XMLRPC_TYPE_ARRAY:
0415                 // PHP 5.2.4 introduced a regression in how empty($xml->value)
0416                 // returns; need to look for the item specifically
0417                 $data = null;
0418                 foreach ($value->children() as $key => $value) {
0419                     if ('data' == $key) {
0420                         $data = $value;
0421                         break;
0422                     }
0423                 }
0424 
0425                 if (null === $data) {
0426                     // require_once 'Zend/XmlRpc/Value/Exception.php';
0427                     throw new Zend_XmlRpc_Value_Exception('Invalid XML for XML-RPC native '. self::XMLRPC_TYPE_ARRAY .' type: ARRAY tag must contain DATA tag');
0428                 }
0429                 $values = array();
0430                 // Parse all the elements of the array from the XML string
0431                 // (simple xml element) to Zend_XmlRpc_Value objects
0432                 foreach ($data->value as $element) {
0433                     $values[] = self::_xmlStringToNativeXmlRpc($element);
0434                 }
0435                 // require_once 'Zend/XmlRpc/Value/Array.php';
0436                 $xmlrpcValue = new Zend_XmlRpc_Value_Array($values);
0437                 break;
0438             case self::XMLRPC_TYPE_STRUCT:
0439                 $values = array();
0440                 // Parse all the memebers of the struct from the XML string
0441                 // (simple xml element) to Zend_XmlRpc_Value objects
0442                 foreach ($value->member as $member) {
0443                     // @todo? If a member doesn't have a <value> tag, we don't add it to the struct
0444                     // Maybe we want to throw an exception here ?
0445                     if (!isset($member->value) or !isset($member->name)) {
0446                         continue;
0447                         //throw new Zend_XmlRpc_Value_Exception('Member of the '. self::XMLRPC_TYPE_STRUCT .' XML-RPC native type must contain a VALUE tag');
0448                     }
0449                     $values[(string)$member->name] = self::_xmlStringToNativeXmlRpc($member->value);
0450                 }
0451                 // require_once 'Zend/XmlRpc/Value/Struct.php';
0452                 $xmlrpcValue = new Zend_XmlRpc_Value_Struct($values);
0453                 break;
0454             default:
0455                 // require_once 'Zend/XmlRpc/Value/Exception.php';
0456                 throw new Zend_XmlRpc_Value_Exception('Value type \''. $type .'\' parsed from the XML string is not a known XML-RPC native type');
0457                 break;
0458         }
0459         $xmlrpcValue->_setXML($xml->asXML());
0460 
0461         return $xmlrpcValue;
0462     }
0463 
0464     protected static function _createSimpleXMLElement(&$xml)
0465     {
0466         if ($xml instanceof SimpleXMLElement) {
0467             return;
0468         }
0469 
0470         try {
0471             $xml = new SimpleXMLElement($xml);
0472         } catch (Exception $e) {
0473             // The given string is not a valid XML
0474             // require_once 'Zend/XmlRpc/Value/Exception.php';
0475             throw new Zend_XmlRpc_Value_Exception('Failed to create XML-RPC value from XML string: ' . $e->getMessage(), $e->getCode(), $e);
0476         }
0477     }
0478 
0479     /**
0480      * Extract XML/RPC type and value from SimpleXMLElement object
0481      *
0482      * @param SimpleXMLElement $xml
0483      * @param string &$type Type bind variable
0484      * @param string &$value Value bind variable
0485      * @return void
0486      */
0487     protected static function _extractTypeAndValue(SimpleXMLElement $xml, &$type, &$value)
0488     {
0489         list($type, $value) = each($xml);
0490 
0491         if (!$type and $value === null) {
0492             $namespaces = array('ex' => 'http://ws.apache.org/xmlrpc/namespaces/extensions');
0493             foreach ($namespaces as $namespaceName => $namespaceUri) {
0494                 $namespaceXml = $xml->children($namespaceUri);
0495                 list($type, $value) = each($namespaceXml);
0496                 if ($type !== null) {
0497                     $type = $namespaceName . ':' . $type;
0498                     break;
0499                 }
0500             }
0501         }
0502 
0503         //if there is a child element, try to parse type for it
0504         if (!$type && $value instanceof SimpleXMLElement) {
0505             self::_extractTypeAndValue($value->children(), $type, $value);
0506         }
0507 
0508         // If no type was specified, the default is string
0509         if (!$type) {
0510             $type = self::XMLRPC_TYPE_STRING;
0511             if (preg_match('#^<value>.*</value>$#', $xml->asXML())) {
0512                 $value = str_replace(array('<value>', '</value>'), '', $xml->asXML());
0513             }
0514         }
0515     }
0516 
0517     /**
0518      * @param string $xml
0519      * @return void
0520      */
0521     protected function _setXML($xml)
0522     {
0523         $this->_xml = $this->getGenerator()->stripDeclaration($xml);
0524     }
0525 }