├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json └── src ├── AbstractValue.php ├── Client.php ├── Client ├── Exception │ ├── ExceptionInterface.php │ ├── FaultException.php │ ├── HttpException.php │ ├── IntrospectException.php │ ├── InvalidArgumentException.php │ └── RuntimeException.php ├── ServerIntrospection.php └── ServerProxy.php ├── Exception ├── BadMethodCallException.php ├── ExceptionInterface.php ├── InvalidArgumentException.php ├── RuntimeException.php └── ValueException.php ├── Fault.php ├── Generator ├── AbstractGenerator.php ├── DomDocument.php ├── GeneratorInterface.php └── XmlWriter.php ├── Request.php ├── Request ├── Http.php └── Stdin.php ├── Response.php ├── Response └── Http.php ├── Server.php ├── Server ├── Cache.php ├── Exception │ ├── BadMethodCallException.php │ ├── ExceptionInterface.php │ ├── InvalidArgumentException.php │ └── RuntimeException.php ├── Fault.php └── System.php └── Value ├── AbstractCollection.php ├── AbstractScalar.php ├── ArrayValue.php ├── Base64.php ├── BigInteger.php ├── Boolean.php ├── DateTime.php ├── Double.php ├── Integer.php ├── Nil.php ├── Struct.php └── Text.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file, in reverse chronological order by release. 4 | 5 | ## 2.9.1 - TBD 6 | 7 | ### Added 8 | 9 | - Nothing. 10 | 11 | ### Changed 12 | 13 | - Nothing. 14 | 15 | ### Deprecated 16 | 17 | - Nothing. 18 | 19 | ### Removed 20 | 21 | - Nothing. 22 | 23 | ### Fixed 24 | 25 | - Nothing. 26 | 27 | ## 2.9.0 - 2019-12-27 28 | 29 | ### Added 30 | 31 | - Nothing. 32 | 33 | ### Changed 34 | 35 | - [#40](https://github.com/zendframework/zend-xmlrpc/pull/40) modifies detection of integer values on 64-bit systems. Previously, i8 values parsed by the client were always cast to BigInteger values. Now, on 64-bit systems, they are cast to integers. 36 | 37 | Disables use of BigInteger for XMLRPC i8 type if host machine is 64-bit. 38 | 39 | ### Deprecated 40 | 41 | - Nothing. 42 | 43 | ### Removed 44 | 45 | - Nothing. 46 | 47 | ### Fixed 48 | 49 | - Nothing. 50 | 51 | ## 2.8.0 - 2019-10-19 52 | 53 | ### Added 54 | 55 | - [#38](https://github.com/zendframework/zend-xmlrpc/pull/38) adds support for PHP 7.3. 56 | 57 | ### Changed 58 | 59 | - Nothing. 60 | 61 | ### Deprecated 62 | 63 | - Nothing. 64 | 65 | ### Removed 66 | 67 | - [#38](https://github.com/zendframework/zend-xmlrpc/pull/38) removes support for zend-stdlib v2 releases. 68 | 69 | ### Fixed 70 | 71 | - Nothing. 72 | 73 | ## 2.7.0 - 2018-05-14 74 | 75 | ### Added 76 | 77 | - Nothing. 78 | 79 | ### Changed 80 | 81 | - Nothing. 82 | 83 | ### Deprecated 84 | 85 | - Nothing. 86 | 87 | ### Removed 88 | 89 | - [#32](https://github.com/zendframework/zend-xmlrpc/pull/32) removes support for HHVM. 90 | 91 | ### Fixed 92 | 93 | - Nothing. 94 | 95 | ## 2.6.2 - 2018-01-25 96 | 97 | ### Added 98 | 99 | - [#29](https://github.com/zendframework/zend-xmlrpc/pull/29) adds support for 100 | PHP 7.2, by replacing deprecated `list`/`each` syntax with a functional 101 | equivalent. 102 | 103 | ### Deprecated 104 | 105 | - Nothing. 106 | 107 | ### Removed 108 | 109 | - Nothing. 110 | 111 | ### Fixed 112 | 113 | - Nothing. 114 | 115 | ## 2.6.1 - 2017-08-11 116 | 117 | ### Added 118 | 119 | - Nothing. 120 | 121 | ### Deprecated 122 | 123 | - Nothing. 124 | 125 | ### Removed 126 | 127 | - Nothing. 128 | 129 | ### Fixed 130 | 131 | - [#27](https://github.com/zendframework/zend-xmlrpc/pull/27) fixed a memory leak 132 | caused by repetitive addition of `Accept` and `Content-Type` headers on subsequent 133 | HTTP requests produced by the `Zend\XmlRpc\Client`. 134 | 135 | ## 2.6.0 - 2016-06-21 136 | 137 | ### Added 138 | 139 | - [#19](https://github.com/zendframework/zend-xmlrpc/pull/19) adds support for 140 | zend-math v3. 141 | 142 | ### Deprecated 143 | 144 | - Nothing. 145 | 146 | ### Removed 147 | 148 | - Nothing. 149 | 150 | ### Fixed 151 | 152 | - Nothing. 153 | 154 | ## 2.5.2 - 2016-04-21 155 | 156 | ### Added 157 | 158 | - [#11](https://github.com/zendframework/zend-xmlrpc/pull/11), 159 | [#12](https://github.com/zendframework/zend-xmlrpc/pull/12), 160 | [#13](https://github.com/zendframework/zend-xmlrpc/pull/13), 161 | [#14](https://github.com/zendframework/zend-xmlrpc/pull/14), 162 | [#15](https://github.com/zendframework/zend-xmlrpc/pull/15), and 163 | [#16](https://github.com/zendframework/zend-xmlrpc/pull/16) 164 | added and prepared the documentation for publication at 165 | https://zendframework.github.io/zend-xmlrpc/ 166 | 167 | ### Deprecated 168 | 169 | - Nothing. 170 | 171 | ### Removed 172 | 173 | - Nothing. 174 | 175 | ### Fixed 176 | 177 | - [#17](https://github.com/zendframework/zend-xmlrpc/pull/17) updates 178 | dependencies to allow zend-stdlib v3 releases. 179 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2005-2019, Zend Technologies USA, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | - Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | - Neither the name of Zend Technologies USA, Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zend-xmlrpc 2 | 3 | > ## Repository abandoned 2019-12-31 4 | > 5 | > This repository has moved to [laminas/laminas-xmlrpc](https://github.com/laminas/laminas-xmlrpc). 6 | 7 | [![Build Status](https://secure.travis-ci.org/zendframework/zend-xmlrpc.svg?branch=master)](https://secure.travis-ci.org/zendframework/zend-xmlrpc) 8 | [![Coverage Status](https://coveralls.io/repos/github/zendframework/zend-xmlrpc/badge.svg?branch=master)](https://coveralls.io/github/zendframework/zend-xmlrpc?branch=master) 9 | 10 | From its home page, XML-RPC is described as a ”...remote procedure calling using 11 | HTTP as the transport and XML as the encoding. XML-RPC is designed to be as 12 | simple as possible, while allowing complex data structures to be transmitted, 13 | processed and returned.” 14 | 15 | `Zend\XmlRpc` provides support for both consuming remote XML-RPC services and 16 | building new XML-RPC servers. 17 | 18 | - File issues at https://github.com/zendframework/zend-xmlrpc/issues 19 | - Documentation is at https://docs.zendframework.com/zend-xmlrpc/ 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zendframework/zend-xmlrpc", 3 | "description": "Fully-featured XML-RPC server and client implementations", 4 | "license": "BSD-3-Clause", 5 | "keywords": [ 6 | "zf", 7 | "zendframework", 8 | "xmlrpc" 9 | ], 10 | "support": { 11 | "docs": "https://docs.zendframework.com/zend-xmlrpc/", 12 | "issues": "https://github.com/zendframework/zend-xmlrpc/issues", 13 | "source": "https://github.com/zendframework/zend-xmlrpc", 14 | "rss": "https://github.com/zendframework/zend-xmlrpc/releases.atom", 15 | "chat": "https://zendframework-slack.herokuapp.com", 16 | "forum": "https://discourse.zendframework.com/c/questions/components" 17 | }, 18 | "require": { 19 | "php": "^5.6 || ^7.0", 20 | "zendframework/zend-http": "^2.5.4", 21 | "zendframework/zend-math": "^2.7 || ^3.0", 22 | "zendframework/zend-server": "^2.7", 23 | "zendframework/zend-stdlib": "^3.2.1", 24 | "zendframework/zendxml": "^1.0.2" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", 28 | "zendframework/zend-coding-standard": "~1.0.0" 29 | }, 30 | "suggest": { 31 | "zendframework/zend-cache": "To support Zend\\XmlRpc\\Server\\Cache usage" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Zend\\XmlRpc\\": "src/" 36 | } 37 | }, 38 | "autoload-dev": { 39 | "psr-4": { 40 | "ZendTest\\XmlRpc\\": "test/" 41 | }, 42 | "files": [ 43 | "test/autoload.php", 44 | "test/TestAsset/functions.php" 45 | ] 46 | }, 47 | "config": { 48 | "sort-packages": true 49 | }, 50 | "extra": { 51 | "branch-alias": { 52 | "dev-master": "2.9.x-dev", 53 | "dev-develop": "2.10.x-dev" 54 | } 55 | }, 56 | "scripts": { 57 | "check": [ 58 | "@cs-check", 59 | "@test" 60 | ], 61 | "cs-check": "phpcs", 62 | "cs-fix": "phpcbf", 63 | "test": "phpunit --colors=always", 64 | "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/AbstractValue.php: -------------------------------------------------------------------------------- 1 | type; 94 | } 95 | 96 | /** 97 | * Get XML generator instance 98 | * 99 | * @return \Zend\XmlRpc\Generator\GeneratorInterface 100 | */ 101 | public static function getGenerator() 102 | { 103 | if (! static::$generator) { 104 | if (extension_loaded('xmlwriter')) { 105 | static::$generator = new Generator\XmlWriter(); 106 | } else { 107 | static::$generator = new Generator\DomDocument(); 108 | } 109 | } 110 | 111 | return static::$generator; 112 | } 113 | 114 | /** 115 | * Sets XML generator instance 116 | * 117 | * @param null|Generator\GeneratorInterface $generator 118 | * @return void 119 | */ 120 | public static function setGenerator(Generator\GeneratorInterface $generator = null) 121 | { 122 | static::$generator = $generator; 123 | } 124 | 125 | /** 126 | * Changes the encoding of the generator 127 | * 128 | * @param string $encoding 129 | * @return void 130 | */ 131 | public static function setEncoding($encoding) 132 | { 133 | $generator = static::getGenerator(); 134 | $newGenerator = new $generator($encoding); 135 | static::setGenerator($newGenerator); 136 | } 137 | 138 | /** 139 | * Return the value of this object, convert the XML-RPC native value into a PHP variable 140 | * 141 | * @return mixed 142 | */ 143 | abstract public function getValue(); 144 | 145 | 146 | /** 147 | * Return the XML code that represent a native MXL-RPC value 148 | * 149 | * @return string 150 | */ 151 | public function saveXml() 152 | { 153 | if (! $this->xml) { 154 | $this->generateXml(); 155 | $this->xml = (string) $this->getGenerator(); 156 | } 157 | return $this->xml; 158 | } 159 | 160 | /** 161 | * Generate XML code that represent a native XML/RPC value 162 | * 163 | * @return void 164 | */ 165 | public function generateXml() 166 | { 167 | $this->generate(); 168 | } 169 | 170 | /** 171 | * Creates a Value* object, representing a native XML-RPC value 172 | * A XmlRpcValue object can be created in 3 ways: 173 | * 1. Autodetecting the native type out of a PHP variable 174 | * (if $type is not set or equal to Value::AUTO_DETECT_TYPE) 175 | * 2. By specifying the native type ($type is one of the Value::XMLRPC_TYPE_* constants) 176 | * 3. From a XML string ($type is set to Value::XML_STRING) 177 | * 178 | * By default the value type is autodetected according to it's PHP type 179 | * 180 | * @param mixed $value 181 | * @param Zend\XmlRpc\Value::constant $type 182 | * @throws Exception\ValueException 183 | * @return AbstractValue 184 | */ 185 | public static function getXmlRpcValue($value, $type = self::AUTO_DETECT_TYPE) 186 | { 187 | switch ($type) { 188 | case self::AUTO_DETECT_TYPE: 189 | // Auto detect the XML-RPC native type from the PHP type of $value 190 | return static::phpVarToNativeXmlRpc($value); 191 | 192 | case self::XML_STRING: 193 | // Parse the XML string given in $value and get the XML-RPC value in it 194 | return static::xmlStringToNativeXmlRpc($value); 195 | 196 | case self::XMLRPC_TYPE_I4: 197 | // fall through to the next case 198 | case self::XMLRPC_TYPE_INTEGER: 199 | return new Value\Integer($value); 200 | 201 | case self::XMLRPC_TYPE_I8: 202 | // fall through to the next case 203 | case self::XMLRPC_TYPE_APACHEI8: 204 | return self::$USE_BIGINT_FOR_I8 ? new Value\BigInteger($value) : new Value\Integer($value); 205 | 206 | case self::XMLRPC_TYPE_DOUBLE: 207 | return new Value\Double($value); 208 | 209 | case self::XMLRPC_TYPE_BOOLEAN: 210 | return new Value\Boolean($value); 211 | 212 | case self::XMLRPC_TYPE_STRING: 213 | return new Value\Text($value); 214 | 215 | case self::XMLRPC_TYPE_BASE64: 216 | return new Value\Base64($value); 217 | 218 | case self::XMLRPC_TYPE_NIL: 219 | // fall through to the next case 220 | case self::XMLRPC_TYPE_APACHENIL: 221 | return new Value\Nil(); 222 | 223 | case self::XMLRPC_TYPE_DATETIME: 224 | return new Value\DateTime($value); 225 | 226 | case self::XMLRPC_TYPE_ARRAY: 227 | return new Value\ArrayValue($value); 228 | 229 | case self::XMLRPC_TYPE_STRUCT: 230 | return new Value\Struct($value); 231 | 232 | default: 233 | throw new Exception\ValueException('Given type is not a '. __CLASS__ .' constant'); 234 | } 235 | } 236 | 237 | /** 238 | * Get XML-RPC type for a PHP native variable 239 | * 240 | * @static 241 | * @param mixed $value 242 | * @throws Exception\InvalidArgumentException 243 | * @return string 244 | */ 245 | public static function getXmlRpcTypeByValue($value) 246 | { 247 | if (is_object($value)) { 248 | if ($value instanceof AbstractValue) { 249 | return $value->getType(); 250 | } elseif ($value instanceof DateTime) { 251 | return self::XMLRPC_TYPE_DATETIME; 252 | } 253 | return static::getXmlRpcTypeByValue(get_object_vars($value)); 254 | } elseif (is_array($value)) { 255 | if (! empty($value) && is_array($value) && (array_keys($value) !== range(0, count($value) - 1))) { 256 | return self::XMLRPC_TYPE_STRUCT; 257 | } 258 | return self::XMLRPC_TYPE_ARRAY; 259 | } elseif (is_int($value)) { 260 | return ($value > PHP_INT_MAX) ? self::XMLRPC_TYPE_I8 : self::XMLRPC_TYPE_INTEGER; 261 | } elseif (is_double($value)) { 262 | return self::XMLRPC_TYPE_DOUBLE; 263 | } elseif (is_bool($value)) { 264 | return self::XMLRPC_TYPE_BOOLEAN; 265 | } elseif (null === $value) { 266 | return self::XMLRPC_TYPE_NIL; 267 | } elseif (is_string($value)) { 268 | return self::XMLRPC_TYPE_STRING; 269 | } 270 | throw new Exception\InvalidArgumentException(sprintf( 271 | 'No matching XMLRPC type found for php type %s.', 272 | gettype($value) 273 | )); 274 | } 275 | 276 | /** 277 | * Transform a PHP native variable into a XML-RPC native value 278 | * 279 | * @param mixed $value The PHP variable for conversion 280 | * 281 | * @throws Exception\InvalidArgumentException 282 | * @return AbstractValue 283 | * @static 284 | */ 285 | protected static function phpVarToNativeXmlRpc($value) 286 | { 287 | // @see http://framework.zend.com/issues/browse/ZF-8623 288 | if ($value instanceof AbstractValue) { 289 | return $value; 290 | } 291 | 292 | switch (static::getXmlRpcTypeByValue($value)) { 293 | case self::XMLRPC_TYPE_DATETIME: 294 | return new Value\DateTime($value); 295 | 296 | case self::XMLRPC_TYPE_ARRAY: 297 | return new Value\ArrayValue($value); 298 | 299 | case self::XMLRPC_TYPE_STRUCT: 300 | return new Value\Struct($value); 301 | 302 | case self::XMLRPC_TYPE_INTEGER: 303 | return new Value\Integer($value); 304 | 305 | case self::XMLRPC_TYPE_DOUBLE: 306 | return new Value\Double($value); 307 | 308 | case self::XMLRPC_TYPE_BOOLEAN: 309 | return new Value\Boolean($value); 310 | 311 | case self::XMLRPC_TYPE_NIL: 312 | return new Value\Nil; 313 | 314 | case self::XMLRPC_TYPE_STRING: 315 | // Fall through to the next case 316 | default: 317 | // If type isn't identified (or identified as string), it treated as string 318 | return new Value\Text($value); 319 | } 320 | } 321 | 322 | /** 323 | * Transform an XML string into a XML-RPC native value 324 | * 325 | * @param string|\SimpleXMLElement $xml A SimpleXMLElement object represent the XML string 326 | * It can be also a valid XML string for conversion 327 | * 328 | * @throws Exception\ValueException 329 | * @return \Zend\XmlRpc\AbstractValue 330 | * @static 331 | */ 332 | protected static function xmlStringToNativeXmlRpc($xml) 333 | { 334 | static::createSimpleXMLElement($xml); 335 | 336 | static::extractTypeAndValue($xml, $type, $value); 337 | 338 | switch ($type) { 339 | // All valid and known XML-RPC native values 340 | case self::XMLRPC_TYPE_I4: 341 | // Fall through to the next case 342 | case self::XMLRPC_TYPE_INTEGER: 343 | $xmlrpcValue = new Value\Integer($value); 344 | break; 345 | case self::XMLRPC_TYPE_APACHEI8: 346 | // Fall through to the next case 347 | case self::XMLRPC_TYPE_I8: 348 | $xmlrpcValue = self::$USE_BIGINT_FOR_I8 ? new Value\BigInteger($value) : new Value\Integer($value); 349 | break; 350 | case self::XMLRPC_TYPE_DOUBLE: 351 | $xmlrpcValue = new Value\Double($value); 352 | break; 353 | case self::XMLRPC_TYPE_BOOLEAN: 354 | $xmlrpcValue = new Value\Boolean($value); 355 | break; 356 | case self::XMLRPC_TYPE_STRING: 357 | $xmlrpcValue = new Value\Text($value); 358 | break; 359 | case self::XMLRPC_TYPE_DATETIME: // The value should already be in an iso8601 format 360 | $xmlrpcValue = new Value\DateTime($value); 361 | break; 362 | case self::XMLRPC_TYPE_BASE64: // The value should already be base64 encoded 363 | $xmlrpcValue = new Value\Base64($value, true); 364 | break; 365 | case self::XMLRPC_TYPE_NIL: 366 | // Fall through to the next case 367 | case self::XMLRPC_TYPE_APACHENIL: 368 | // The value should always be NULL 369 | $xmlrpcValue = new Value\Nil(); 370 | break; 371 | case self::XMLRPC_TYPE_ARRAY: 372 | // PHP 5.2.4 introduced a regression in how empty($xml->value) 373 | // returns; need to look for the item specifically 374 | $data = null; 375 | foreach ($value->children() as $key => $value) { 376 | if ('data' == $key) { 377 | $data = $value; 378 | break; 379 | } 380 | } 381 | 382 | if (null === $data) { 383 | throw new Exception\ValueException( 384 | 'Invalid XML for XML-RPC native ' 385 | . self::XMLRPC_TYPE_ARRAY 386 | . ' type: ARRAY tag must contain DATA tag' 387 | ); 388 | } 389 | $values = []; 390 | // Parse all the elements of the array from the XML string 391 | // (simple xml element) to Value objects 392 | foreach ($data->value as $element) { 393 | $values[] = static::xmlStringToNativeXmlRpc($element); 394 | } 395 | $xmlrpcValue = new Value\ArrayValue($values); 396 | break; 397 | case self::XMLRPC_TYPE_STRUCT: 398 | $values = []; 399 | // Parse all the members of the struct from the XML string 400 | // (simple xml element) to Value objects 401 | foreach ($value->member as $member) { 402 | // @todo? If a member doesn't have a tag, we don't add it to the struct 403 | // Maybe we want to throw an exception here ? 404 | if (! isset($member->value) or ! isset($member->name)) { 405 | continue; 406 | } 407 | $values[(string) $member->name] = static::xmlStringToNativeXmlRpc($member->value); 408 | } 409 | $xmlrpcValue = new Value\Struct($values); 410 | break; 411 | default: 412 | throw new Exception\ValueException( 413 | 'Value type \'' . $type . '\' parsed from the XML string is not a known XML-RPC native type' 414 | ); 415 | break; 416 | } 417 | $xmlrpcValue->setXML($xml->asXML()); 418 | 419 | return $xmlrpcValue; 420 | } 421 | 422 | protected static function createSimpleXMLElement(&$xml) 423 | { 424 | if ($xml instanceof SimpleXMLElement) { 425 | return; 426 | } 427 | 428 | try { 429 | $xml = new SimpleXMLElement($xml); 430 | } catch (\Exception $e) { 431 | // The given string is not a valid XML 432 | throw new Exception\ValueException( 433 | 'Failed to create XML-RPC value from XML string: ' . $e->getMessage(), 434 | $e->getCode(), 435 | $e 436 | ); 437 | } 438 | } 439 | 440 | /** 441 | * Extract XML/RPC type and value from SimpleXMLElement object 442 | * 443 | * @param \SimpleXMLElement $xml 444 | * @param string &$type Type bind variable 445 | * @param string &$value Value bind variable 446 | * @return void 447 | */ 448 | protected static function extractTypeAndValue(\SimpleXMLElement $xml, &$type, &$value) 449 | { 450 | // Casting is necessary to work with strict-typed systems 451 | foreach ((array) $xml as $type => $value) { 452 | break; 453 | } 454 | if (! $type and $value === null) { 455 | $namespaces = ['ex' => 'http://ws.apache.org/xmlrpc/namespaces/extensions']; 456 | foreach ($namespaces as $namespaceName => $namespaceUri) { 457 | foreach ((array)$xml->children($namespaceUri) as $type => $value) { 458 | break; 459 | } 460 | if ($type !== null) { 461 | $type = $namespaceName . ':' . $type; 462 | break; 463 | } 464 | } 465 | } 466 | 467 | // If no type was specified, the default is string 468 | if (! $type) { 469 | $type = self::XMLRPC_TYPE_STRING; 470 | if (empty($value) and preg_match('#^.*$#', $xml->asXML())) { 471 | $value = str_replace(['', ''], '', $xml->asXML()); 472 | } 473 | } 474 | } 475 | 476 | /** 477 | * @param $xml 478 | * @return void 479 | */ 480 | protected function setXML($xml) 481 | { 482 | $this->xml = $this->getGenerator()->stripDeclaration($xml); 483 | } 484 | } 485 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | httpClient = new Http\Client(); 74 | } else { 75 | $this->httpClient = $httpClient; 76 | } 77 | 78 | $this->introspector = new Client\ServerIntrospection($this); 79 | $this->serverAddress = $server; 80 | } 81 | 82 | /** 83 | * Sets the HTTP client object to use for connecting the XML-RPC server. 84 | * 85 | * @param \Zend\Http\Client $httpClient 86 | * @return \Zend\Http\Client 87 | */ 88 | public function setHttpClient(Http\Client $httpClient) 89 | { 90 | return $this->httpClient = $httpClient; 91 | } 92 | 93 | /** 94 | * Gets the HTTP client object. 95 | * 96 | * @return \Zend\Http\Client 97 | */ 98 | public function getHttpClient() 99 | { 100 | return $this->httpClient; 101 | } 102 | 103 | /** 104 | * Sets the object used to introspect remote servers 105 | * 106 | * @param \Zend\XmlRpc\Client\ServerIntrospection 107 | * @return \Zend\XmlRpc\Client\ServerIntrospection 108 | */ 109 | public function setIntrospector(Client\ServerIntrospection $introspector) 110 | { 111 | return $this->introspector = $introspector; 112 | } 113 | 114 | /** 115 | * Gets the introspection object. 116 | * 117 | * @return \Zend\XmlRpc\Client\ServerIntrospection 118 | */ 119 | public function getIntrospector() 120 | { 121 | return $this->introspector; 122 | } 123 | 124 | /** 125 | * The request of the last method call 126 | * 127 | * @return \Zend\XmlRpc\Request 128 | */ 129 | public function getLastRequest() 130 | { 131 | return $this->lastRequest; 132 | } 133 | 134 | /** 135 | * The response received from the last method call 136 | * 137 | * @return \Zend\XmlRpc\Response 138 | */ 139 | public function getLastResponse() 140 | { 141 | return $this->lastResponse; 142 | } 143 | 144 | /** 145 | * Returns a proxy object for more convenient method calls 146 | * 147 | * @param string $namespace Namespace to proxy or empty string for none 148 | * @return \Zend\XmlRpc\Client\ServerProxy 149 | */ 150 | public function getProxy($namespace = '') 151 | { 152 | if (empty($this->proxyCache[$namespace])) { 153 | $proxy = new Client\ServerProxy($this, $namespace); 154 | $this->proxyCache[$namespace] = $proxy; 155 | } 156 | return $this->proxyCache[$namespace]; 157 | } 158 | 159 | /** 160 | * Set skip system lookup flag 161 | * 162 | * @param bool $flag 163 | * @return \Zend\XmlRpc\Client 164 | */ 165 | public function setSkipSystemLookup($flag = true) 166 | { 167 | $this->skipSystemLookup = (bool) $flag; 168 | return $this; 169 | } 170 | 171 | /** 172 | * Skip system lookup when determining if parameter should be array or struct? 173 | * 174 | * @return bool 175 | */ 176 | public function skipSystemLookup() 177 | { 178 | return $this->skipSystemLookup; 179 | } 180 | 181 | /** 182 | * Perform an XML-RPC request and return a response. 183 | * 184 | * @param \Zend\XmlRpc\Request $request 185 | * @param null|\Zend\XmlRpc\Response $response 186 | * 187 | * @throws \Zend\Http\Exception\InvalidArgumentException 188 | * @throws \Zend\Http\Client\Exception\RuntimeException 189 | * @throws \Zend\XmlRpc\Client\Exception\HttpException 190 | * @throws \Zend\XmlRpc\Exception\ValueException 191 | * 192 | * @return void 193 | */ 194 | public function doRequest($request, $response = null) 195 | { 196 | $this->lastRequest = $request; 197 | 198 | if (PHP_VERSION_ID < 50600) { 199 | iconv_set_encoding('input_encoding', 'UTF-8'); 200 | iconv_set_encoding('output_encoding', 'UTF-8'); 201 | iconv_set_encoding('internal_encoding', 'UTF-8'); 202 | } else { 203 | ini_set('default_charset', 'UTF-8'); 204 | } 205 | 206 | $http = $this->getHttpClient(); 207 | $httpRequest = $http->getRequest(); 208 | if ($httpRequest->getUriString() === null) { 209 | $http->setUri($this->serverAddress); 210 | } 211 | 212 | $headers = $httpRequest->getHeaders(); 213 | 214 | if (! $headers->has('Content-Type')) { 215 | $headers->addHeaderLine('Content-Type', 'text/xml; charset=utf-8'); 216 | } 217 | 218 | if (! $headers->has('Accept')) { 219 | $headers->addHeaderLine('Accept', 'text/xml'); 220 | } 221 | 222 | if (! $headers->has('user-agent')) { 223 | $headers->addHeaderLine('user-agent', 'Zend_XmlRpc_Client'); 224 | } 225 | 226 | $xml = $this->lastRequest->__toString(); 227 | $http->setRawBody($xml); 228 | $httpResponse = $http->setMethod('POST')->send(); 229 | 230 | if (! $httpResponse->isSuccess()) { 231 | /** 232 | * Exception thrown when an HTTP error occurs 233 | */ 234 | throw new Client\Exception\HttpException( 235 | $httpResponse->getReasonPhrase(), 236 | $httpResponse->getStatusCode() 237 | ); 238 | } 239 | 240 | if ($response === null) { 241 | $response = new Response(); 242 | } 243 | 244 | $this->lastResponse = $response; 245 | $this->lastResponse->loadXml(trim($httpResponse->getBody())); 246 | } 247 | 248 | /** 249 | * Send an XML-RPC request to the service (for a specific method) 250 | * 251 | * @param string $method Name of the method we want to call 252 | * @param array $params Array of parameters for the method 253 | * @return mixed 254 | * @throws \Zend\XmlRpc\Client\Exception\FaultException 255 | */ 256 | public function call($method, $params = []) 257 | { 258 | if (! $this->skipSystemLookup() && ('system.' != substr($method, 0, 7))) { 259 | // Ensure empty array/struct params are cast correctly 260 | // If system.* methods are not available, bypass. (ZF-2978) 261 | $success = true; 262 | try { 263 | $signatures = $this->getIntrospector()->getMethodSignature($method); 264 | } catch (\Zend\XmlRpc\Exception\ExceptionInterface $e) { 265 | $success = false; 266 | } 267 | if ($success) { 268 | $validTypes = [ 269 | AbstractValue::XMLRPC_TYPE_ARRAY, 270 | AbstractValue::XMLRPC_TYPE_BASE64, 271 | AbstractValue::XMLRPC_TYPE_BOOLEAN, 272 | AbstractValue::XMLRPC_TYPE_DATETIME, 273 | AbstractValue::XMLRPC_TYPE_DOUBLE, 274 | AbstractValue::XMLRPC_TYPE_I4, 275 | AbstractValue::XMLRPC_TYPE_INTEGER, 276 | AbstractValue::XMLRPC_TYPE_NIL, 277 | AbstractValue::XMLRPC_TYPE_STRING, 278 | AbstractValue::XMLRPC_TYPE_STRUCT, 279 | ]; 280 | 281 | if (! is_array($params)) { 282 | $params = [$params]; 283 | } 284 | foreach ($params as $key => $param) { 285 | if ($param instanceof AbstractValue) { 286 | continue; 287 | } 288 | 289 | if (count($signatures) > 1) { 290 | $type = AbstractValue::getXmlRpcTypeByValue($param); 291 | foreach ($signatures as $signature) { 292 | if (! is_array($signature)) { 293 | continue; 294 | } 295 | if (isset($signature['parameters'][$key])) { 296 | if ($signature['parameters'][$key] == $type) { 297 | break; 298 | } 299 | } 300 | } 301 | } elseif (isset($signatures[0]['parameters'][$key])) { 302 | $type = $signatures[0]['parameters'][$key]; 303 | } else { 304 | $type = null; 305 | } 306 | 307 | if (empty($type) || ! in_array($type, $validTypes)) { 308 | $type = AbstractValue::AUTO_DETECT_TYPE; 309 | } 310 | 311 | $params[$key] = AbstractValue::getXmlRpcValue($param, $type); 312 | } 313 | } 314 | } 315 | 316 | $request = $this->createRequest($method, $params); 317 | 318 | $this->doRequest($request); 319 | 320 | if ($this->lastResponse->isFault()) { 321 | $fault = $this->lastResponse->getFault(); 322 | /** 323 | * Exception thrown when an XML-RPC fault is returned 324 | */ 325 | throw new Client\Exception\FaultException( 326 | $fault->getMessage(), 327 | $fault->getCode() 328 | ); 329 | } 330 | 331 | return $this->lastResponse->getReturnValue(); 332 | } 333 | 334 | /** 335 | * Create request object 336 | * 337 | * @param string $method 338 | * @param array $params 339 | * @return \Zend\XmlRpc\Request 340 | */ 341 | protected function createRequest($method, $params) 342 | { 343 | return new Request($method, $params); 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /src/Client/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | system = $client->getProxy('system'); 30 | } 31 | 32 | /** 33 | * Returns the signature for each method on the server, 34 | * autodetecting whether system.multicall() is supported and 35 | * using it if so. 36 | * 37 | * @return array 38 | */ 39 | public function getSignatureForEachMethod() 40 | { 41 | $methods = $this->listMethods(); 42 | 43 | try { 44 | $signatures = $this->getSignatureForEachMethodByMulticall($methods); 45 | } catch (Exception\FaultException $e) { 46 | // degrade to looping 47 | } 48 | 49 | if (empty($signatures)) { 50 | $signatures = $this->getSignatureForEachMethodByLooping($methods); 51 | } 52 | 53 | return $signatures; 54 | } 55 | 56 | /** 57 | * Attempt to get the method signatures in one request via system.multicall(). 58 | * This is a boxcar feature of XML-RPC and is found on fewer servers. However, 59 | * can significantly improve performance if present. 60 | * 61 | * @param array $methods 62 | * @throws Exception\IntrospectException 63 | * @return array array(array(return, param, param, param...)) 64 | */ 65 | public function getSignatureForEachMethodByMulticall($methods = null) 66 | { 67 | if ($methods === null) { 68 | $methods = $this->listMethods(); 69 | } 70 | 71 | $multicallParams = []; 72 | foreach ($methods as $method) { 73 | $multicallParams[] = ['methodName' => 'system.methodSignature', 74 | 'params' => [$method]]; 75 | } 76 | 77 | $serverSignatures = $this->system->multicall($multicallParams); 78 | 79 | if (! is_array($serverSignatures)) { 80 | $type = gettype($serverSignatures); 81 | $error = "Multicall return is malformed. Expected array, got $type"; 82 | throw new Exception\IntrospectException($error); 83 | } 84 | 85 | if (count($serverSignatures) != count($methods)) { 86 | $error = 'Bad number of signatures received from multicall'; 87 | throw new Exception\IntrospectException($error); 88 | } 89 | 90 | // Create a new signatures array with the methods name as keys and the signature as value 91 | $signatures = []; 92 | foreach ($serverSignatures as $i => $signature) { 93 | $signatures[$methods[$i]] = $signature; 94 | } 95 | 96 | return $signatures; 97 | } 98 | 99 | /** 100 | * Get the method signatures for every method by 101 | * successively calling system.methodSignature 102 | * 103 | * @param array $methods 104 | * @return array 105 | */ 106 | public function getSignatureForEachMethodByLooping($methods = null) 107 | { 108 | if ($methods === null) { 109 | $methods = $this->listMethods(); 110 | } 111 | 112 | $signatures = []; 113 | foreach ($methods as $method) { 114 | $signatures[$method] = $this->getMethodSignature($method); 115 | } 116 | 117 | return $signatures; 118 | } 119 | 120 | /** 121 | * Call system.methodSignature() for the given method 122 | * 123 | * @param array $method 124 | * @throws Exception\IntrospectException 125 | * @return array array(array(return, param, param, param...)) 126 | */ 127 | public function getMethodSignature($method) 128 | { 129 | $signature = $this->system->methodSignature($method); 130 | if (! is_array($signature)) { 131 | $error = 'Invalid signature for method "' . $method . '"'; 132 | throw new Exception\IntrospectException($error); 133 | } 134 | return $signature; 135 | } 136 | 137 | /** 138 | * Call system.listMethods() 139 | * 140 | * @return array array(method, method, method...) 141 | */ 142 | public function listMethods() 143 | { 144 | return $this->system->listMethods(); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/Client/ServerProxy.php: -------------------------------------------------------------------------------- 1 | foo->bar->baz()". 18 | */ 19 | class ServerProxy 20 | { 21 | /** 22 | * @var \Zend\XmlRpc\Client 23 | */ 24 | private $client = null; 25 | 26 | /** 27 | * @var string 28 | */ 29 | private $namespace = ''; 30 | 31 | /** 32 | * @var array of \Zend\XmlRpc\Client\ServerProxy 33 | */ 34 | private $cache = []; 35 | 36 | /** 37 | * Class constructor 38 | * 39 | * @param \Zend\XmlRpc\Client $client 40 | * @param string $namespace 41 | */ 42 | public function __construct(XMLRPCClient $client, $namespace = '') 43 | { 44 | $this->client = $client; 45 | $this->namespace = $namespace; 46 | } 47 | 48 | /** 49 | * Get the next successive namespace 50 | * 51 | * @param string $namespace 52 | * @return \Zend\XmlRpc\Client\ServerProxy 53 | */ 54 | public function __get($namespace) 55 | { 56 | $namespace = ltrim("$this->namespace.$namespace", '.'); 57 | if (! isset($this->cache[$namespace])) { 58 | $this->cache[$namespace] = new $this($this->client, $namespace); 59 | } 60 | return $this->cache[$namespace]; 61 | } 62 | 63 | /** 64 | * Call a method in this namespace. 65 | * 66 | * @param string $method 67 | * @param array $args 68 | * @return mixed 69 | */ 70 | public function __call($method, $args) 71 | { 72 | $method = ltrim("{$this->namespace}.{$method}", '.'); 73 | return $this->client->call($method, $args); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Exception/BadMethodCallException.php: -------------------------------------------------------------------------------- 1 | messages 47 | * @var array 48 | */ 49 | protected $internal = [ 50 | 404 => 'Unknown Error', 51 | 52 | // 610 - 619 reflection errors 53 | 610 => 'Invalid method class', 54 | 611 => 'Unable to attach function or callback; not callable', 55 | 612 => 'Unable to load array; not an array', 56 | 613 => 'One or more method records are corrupt or otherwise unusable', 57 | 58 | // 620 - 629 dispatch errors 59 | 620 => 'Method does not exist', 60 | 621 => 'Error instantiating class to invoke method', 61 | 622 => 'Method missing implementation', 62 | 623 => 'Calling parameters do not match signature', 63 | 64 | // 630 - 639 request errors 65 | 630 => 'Unable to read request', 66 | 631 => 'Failed to parse request', 67 | 632 => 'Invalid request, no method passed; request must contain a \'methodName\' tag', 68 | 633 => 'Param must contain a value', 69 | 634 => 'Invalid method name', 70 | 635 => 'Invalid XML provided to request', 71 | 636 => 'Error creating xmlrpc value', 72 | 73 | // 640 - 649 system.* errors 74 | 640 => 'Method does not exist', 75 | 76 | // 650 - 659 response errors 77 | 650 => 'Invalid XML provided for response', 78 | 651 => 'Failed to parse response', 79 | 652 => 'Invalid response', 80 | 653 => 'Invalid XMLRPC value in response', 81 | ]; 82 | 83 | /** 84 | * Constructor 85 | * 86 | * @param int $code 87 | * @param string $message 88 | */ 89 | public function __construct($code = 404, $message = '') 90 | { 91 | $this->setCode($code); 92 | $code = $this->getCode(); 93 | 94 | if (empty($message) && isset($this->internal[$code])) { 95 | $message = $this->internal[$code]; 96 | } elseif (empty($message)) { 97 | $message = $this->internal[404]; 98 | } 99 | $this->setMessage($message); 100 | } 101 | 102 | /** 103 | * Set the fault code 104 | * 105 | * @param int $code 106 | * @return Fault 107 | */ 108 | public function setCode($code) 109 | { 110 | $this->code = (int) $code; 111 | return $this; 112 | } 113 | 114 | /** 115 | * Return fault code 116 | * 117 | * @return int 118 | */ 119 | public function getCode() 120 | { 121 | return $this->code; 122 | } 123 | 124 | /** 125 | * Retrieve fault message 126 | * 127 | * @param string 128 | * @return Fault 129 | */ 130 | public function setMessage($message) 131 | { 132 | $this->message = (string) $message; 133 | return $this; 134 | } 135 | 136 | /** 137 | * Retrieve fault message 138 | * 139 | * @return string 140 | */ 141 | public function getMessage() 142 | { 143 | return $this->message; 144 | } 145 | 146 | /** 147 | * Set encoding to use in fault response 148 | * 149 | * @param string $encoding 150 | * @return Fault 151 | */ 152 | public function setEncoding($encoding) 153 | { 154 | $this->encoding = $encoding; 155 | AbstractValue::setEncoding($encoding); 156 | return $this; 157 | } 158 | 159 | /** 160 | * Retrieve current fault encoding 161 | * 162 | * @return string 163 | */ 164 | public function getEncoding() 165 | { 166 | return $this->encoding; 167 | } 168 | 169 | /** 170 | * Load an XMLRPC fault from XML 171 | * 172 | * @param string $fault 173 | * @return bool Returns true if successfully loaded fault response, false 174 | * if response was not a fault response 175 | * @throws Exception\ExceptionInterface if no or faulty XML provided, or if fault 176 | * response does not contain either code or message 177 | */ 178 | public function loadXml($fault) 179 | { 180 | if (! is_string($fault)) { 181 | throw new Exception\InvalidArgumentException('Invalid XML provided to fault'); 182 | } 183 | 184 | $xmlErrorsFlag = libxml_use_internal_errors(true); 185 | try { 186 | $xml = XmlSecurity::scan($fault); 187 | } catch (\ZendXml\Exception\RuntimeException $e) { 188 | // Unsecure XML 189 | throw new Exception\RuntimeException('Failed to parse XML fault: ' . $e->getMessage(), 500, $e); 190 | } 191 | if (! $xml instanceof SimpleXMLElement) { 192 | $errors = libxml_get_errors(); 193 | $errors = array_reduce($errors, function ($result, $item) { 194 | if (empty($result)) { 195 | return $item->message; 196 | } 197 | return $result . '; ' . $item->message; 198 | }, ''); 199 | libxml_use_internal_errors($xmlErrorsFlag); 200 | throw new Exception\InvalidArgumentException('Failed to parse XML fault: ' . $errors, 500); 201 | } 202 | libxml_use_internal_errors($xmlErrorsFlag); 203 | 204 | // Check for fault 205 | if (! $xml->fault) { 206 | // Not a fault 207 | return false; 208 | } 209 | 210 | if (! $xml->fault->value->struct) { 211 | // not a proper fault 212 | throw new Exception\InvalidArgumentException('Invalid fault structure', 500); 213 | } 214 | 215 | $structXml = $xml->fault->value->asXML(); 216 | $struct = AbstractValue::getXmlRpcValue($structXml, AbstractValue::XML_STRING); 217 | $struct = $struct->getValue(); 218 | 219 | if (isset($struct['faultCode'])) { 220 | $code = $struct['faultCode']; 221 | } 222 | if (isset($struct['faultString'])) { 223 | $message = $struct['faultString']; 224 | } 225 | 226 | if (empty($code) && empty($message)) { 227 | throw new Exception\InvalidArgumentException('Fault code and string required'); 228 | } 229 | 230 | if (empty($code)) { 231 | $code = '404'; 232 | } 233 | 234 | if (empty($message)) { 235 | if (isset($this->internal[$code])) { 236 | $message = $this->internal[$code]; 237 | } else { 238 | $message = $this->internal[404]; 239 | } 240 | } 241 | 242 | $this->setCode($code); 243 | $this->setMessage($message); 244 | 245 | return true; 246 | } 247 | 248 | /** 249 | * Determine if an XML response is an XMLRPC fault 250 | * 251 | * @param string $xml 252 | * @return bool 253 | */ 254 | public static function isFault($xml) 255 | { 256 | $fault = new static(); 257 | try { 258 | $isFault = $fault->loadXml($xml); 259 | } catch (Exception\ExceptionInterface $e) { 260 | $isFault = false; 261 | } 262 | 263 | return $isFault; 264 | } 265 | 266 | /** 267 | * Serialize fault to XML 268 | * 269 | * @return string 270 | */ 271 | public function saveXml() 272 | { 273 | // Create fault value 274 | $faultStruct = [ 275 | 'faultCode' => $this->getCode(), 276 | 'faultString' => $this->getMessage() 277 | ]; 278 | $value = AbstractValue::getXmlRpcValue($faultStruct); 279 | 280 | $generator = AbstractValue::getGenerator(); 281 | $generator->openElement('methodResponse') 282 | ->openElement('fault'); 283 | $value->generateXml(); 284 | $generator->closeElement('fault') 285 | ->closeElement('methodResponse'); 286 | 287 | return $generator->flush(); 288 | } 289 | 290 | /** 291 | * Return XML fault response 292 | * 293 | * @return string 294 | */ 295 | public function __toString() 296 | { 297 | return $this->saveXML(); 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /src/Generator/AbstractGenerator.php: -------------------------------------------------------------------------------- 1 | setEncoding($encoding); 32 | $this->init(); 33 | } 34 | 35 | /** 36 | * Initialize internal objects 37 | * 38 | * @return void 39 | */ 40 | abstract protected function init(); 41 | 42 | /** 43 | * Start XML element 44 | * 45 | * Method opens a new XML element with an element name and an optional value 46 | * 47 | * @param string $name XML tag name 48 | * @param string $value Optional value of the XML tag 49 | * @return AbstractGenerator Fluent interface 50 | */ 51 | public function openElement($name, $value = null) 52 | { 53 | $this->openXmlElement($name); 54 | if ($value !== null) { 55 | $this->writeTextData($value); 56 | } 57 | 58 | return $this; 59 | } 60 | 61 | /** 62 | * End of an XML element 63 | * 64 | * Method marks the end of an XML element 65 | * 66 | * @param string $name XML tag name 67 | * @return AbstractGenerator Fluent interface 68 | */ 69 | public function closeElement($name) 70 | { 71 | $this->closeXmlElement($name); 72 | 73 | return $this; 74 | } 75 | 76 | /** 77 | * Return encoding 78 | * 79 | * @return string 80 | */ 81 | public function getEncoding() 82 | { 83 | return $this->encoding; 84 | } 85 | 86 | /** 87 | * Set XML encoding 88 | * 89 | * @param string $encoding 90 | * @return AbstractGenerator 91 | */ 92 | public function setEncoding($encoding) 93 | { 94 | $this->encoding = $encoding; 95 | return $this; 96 | } 97 | 98 | /** 99 | * Returns the XML as a string and flushes all internal buffers 100 | * 101 | * @return string 102 | */ 103 | public function flush() 104 | { 105 | $xml = $this->saveXml(); 106 | $this->init(); 107 | return $xml; 108 | } 109 | 110 | /** 111 | * Returns XML without document declaration 112 | * 113 | * @return string 114 | */ 115 | public function __toString() 116 | { 117 | return $this->stripDeclaration($this->saveXml()); 118 | } 119 | 120 | /** 121 | * Removes XML declaration from a string 122 | * 123 | * @param string $xml 124 | * @return string 125 | */ 126 | public function stripDeclaration($xml) 127 | { 128 | return preg_replace('/<\?xml version="1.0"( encoding="[^\"]*")?\?>\n/u', '', $xml); 129 | } 130 | 131 | /** 132 | * Start XML element 133 | * 134 | * @param string $name XML element name 135 | */ 136 | abstract protected function openXmlElement($name); 137 | 138 | /** 139 | * Write XML text data into the currently opened XML element 140 | * 141 | * @param string $text 142 | */ 143 | abstract protected function writeTextData($text); 144 | 145 | /** 146 | * End XML element 147 | * 148 | * @param string $name 149 | */ 150 | abstract protected function closeXmlElement($name); 151 | } 152 | -------------------------------------------------------------------------------- /src/Generator/DomDocument.php: -------------------------------------------------------------------------------- 1 | dom->createElement($name); 38 | 39 | $this->currentElement = $this->currentElement->appendChild($newElement); 40 | } 41 | 42 | /** 43 | * Write XML text data into the currently opened XML element 44 | * 45 | * @param string $text 46 | */ 47 | protected function writeTextData($text) 48 | { 49 | $this->currentElement->appendChild($this->dom->createTextNode($text)); 50 | } 51 | 52 | /** 53 | * Close a previously opened XML element 54 | * 55 | * Resets $currentElement to the next parent node in the hierarchy 56 | * 57 | * @param string $name 58 | * @return void 59 | */ 60 | protected function closeXmlElement($name) 61 | { 62 | if (isset($this->currentElement->parentNode)) { 63 | $this->currentElement = $this->currentElement->parentNode; 64 | } 65 | } 66 | 67 | /** 68 | * Save XML as a string 69 | * 70 | * @return string 71 | */ 72 | public function saveXml() 73 | { 74 | return $this->dom->saveXml(); 75 | } 76 | 77 | /** 78 | * Initializes internal objects 79 | * 80 | * @return void 81 | */ 82 | protected function init() 83 | { 84 | $this->dom = new \DOMDocument('1.0', $this->encoding); 85 | $this->currentElement = $this->dom; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Generator/GeneratorInterface.php: -------------------------------------------------------------------------------- 1 | xmlWriter = new \XMLWriter(); 32 | $this->xmlWriter->openMemory(); 33 | $this->xmlWriter->startDocument('1.0', $this->encoding); 34 | } 35 | 36 | /** 37 | * Open a new XML element 38 | * 39 | * @param string $name XML element name 40 | * @return void 41 | */ 42 | protected function openXmlElement($name) 43 | { 44 | $this->xmlWriter->startElement($name); 45 | } 46 | 47 | /** 48 | * Write XML text data into the currently opened XML element 49 | * 50 | * @param string $text XML text data 51 | * @return void 52 | */ 53 | protected function writeTextData($text) 54 | { 55 | $this->xmlWriter->text($text); 56 | } 57 | 58 | /** 59 | * Close a previously opened XML element 60 | * 61 | * @param string $name 62 | * @return XmlWriter 63 | */ 64 | protected function closeXmlElement($name) 65 | { 66 | $this->xmlWriter->endElement(); 67 | 68 | return $this; 69 | } 70 | 71 | /** 72 | * Emit XML document 73 | * 74 | * @return string 75 | */ 76 | public function saveXml() 77 | { 78 | return $this->xmlWriter->flush(false); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Request.php: -------------------------------------------------------------------------------- 1 | setMethod($method); 81 | } 82 | 83 | if ($params !== null) { 84 | $this->setParams($params); 85 | } 86 | } 87 | 88 | /** 89 | * Set encoding to use in request 90 | * 91 | * @param string $encoding 92 | * @return \Zend\XmlRpc\Request 93 | */ 94 | public function setEncoding($encoding) 95 | { 96 | $this->encoding = $encoding; 97 | AbstractValue::setEncoding($encoding); 98 | return $this; 99 | } 100 | 101 | /** 102 | * Retrieve current request encoding 103 | * 104 | * @return string 105 | */ 106 | public function getEncoding() 107 | { 108 | return $this->encoding; 109 | } 110 | 111 | /** 112 | * Set method to call 113 | * 114 | * @param string $method 115 | * @return bool Returns true on success, false if method name is invalid 116 | */ 117 | public function setMethod($method) 118 | { 119 | if (! is_string($method) || ! preg_match('/^[a-z0-9_.:\\\\\/]+$/i', $method)) { 120 | $this->fault = new Fault(634, 'Invalid method name ("' . $method . '")'); 121 | $this->fault->setEncoding($this->getEncoding()); 122 | return false; 123 | } 124 | 125 | $this->method = $method; 126 | return true; 127 | } 128 | 129 | /** 130 | * Retrieve call method 131 | * 132 | * @return string 133 | */ 134 | public function getMethod() 135 | { 136 | return $this->method; 137 | } 138 | 139 | /** 140 | * Add a parameter to the parameter stack 141 | * 142 | * Adds a parameter to the parameter stack, associating it with the type 143 | * $type if provided 144 | * 145 | * @param mixed $value 146 | * @param string $type Optional; type hinting 147 | * @return void 148 | */ 149 | public function addParam($value, $type = null) 150 | { 151 | $this->params[] = $value; 152 | if (null === $type) { 153 | // Detect type if not provided explicitly 154 | if ($value instanceof AbstractValue) { 155 | $type = $value->getType(); 156 | } else { 157 | $xmlRpcValue = AbstractValue::getXmlRpcValue($value); 158 | $type = $xmlRpcValue->getType(); 159 | } 160 | } 161 | $this->types[] = $type; 162 | $this->xmlRpcParams[] = ['value' => $value, 'type' => $type]; 163 | } 164 | 165 | /** 166 | * Set the parameters array 167 | * 168 | * If called with a single, array value, that array is used to set the 169 | * parameters stack. If called with multiple values or a single non-array 170 | * value, the arguments are used to set the parameters stack. 171 | * 172 | * Best is to call with array of the format, in order to allow type hinting 173 | * when creating the XMLRPC values for each parameter: 174 | * 175 | * $array = array( 176 | * array( 177 | * 'value' => $value, 178 | * 'type' => $type 179 | * )[, ... ] 180 | * ); 181 | * 182 | * 183 | * @access public 184 | * @return void 185 | */ 186 | public function setParams() 187 | { 188 | $argc = func_num_args(); 189 | $argv = func_get_args(); 190 | if (0 == $argc) { 191 | return; 192 | } 193 | 194 | if ((1 == $argc) && is_array($argv[0])) { 195 | $params = []; 196 | $types = []; 197 | $wellFormed = true; 198 | foreach ($argv[0] as $arg) { 199 | if (! is_array($arg) || ! isset($arg['value'])) { 200 | $wellFormed = false; 201 | break; 202 | } 203 | $params[] = $arg['value']; 204 | 205 | if (! isset($arg['type'])) { 206 | $xmlRpcValue = AbstractValue::getXmlRpcValue($arg['value']); 207 | $arg['type'] = $xmlRpcValue->getType(); 208 | } 209 | $types[] = $arg['type']; 210 | } 211 | if ($wellFormed) { 212 | $this->xmlRpcParams = $argv[0]; 213 | $this->params = $params; 214 | $this->types = $types; 215 | } else { 216 | $this->params = $argv[0]; 217 | $this->types = []; 218 | $xmlRpcParams = []; 219 | foreach ($argv[0] as $arg) { 220 | if ($arg instanceof AbstractValue) { 221 | $type = $arg->getType(); 222 | } else { 223 | $xmlRpcValue = AbstractValue::getXmlRpcValue($arg); 224 | $type = $xmlRpcValue->getType(); 225 | } 226 | $xmlRpcParams[] = ['value' => $arg, 'type' => $type]; 227 | $this->types[] = $type; 228 | } 229 | $this->xmlRpcParams = $xmlRpcParams; 230 | } 231 | return; 232 | } 233 | 234 | $this->params = $argv; 235 | $this->types = []; 236 | $xmlRpcParams = []; 237 | foreach ($argv as $arg) { 238 | if ($arg instanceof AbstractValue) { 239 | $type = $arg->getType(); 240 | } else { 241 | $xmlRpcValue = AbstractValue::getXmlRpcValue($arg); 242 | $type = $xmlRpcValue->getType(); 243 | } 244 | $xmlRpcParams[] = ['value' => $arg, 'type' => $type]; 245 | $this->types[] = $type; 246 | } 247 | $this->xmlRpcParams = $xmlRpcParams; 248 | } 249 | 250 | /** 251 | * Retrieve the array of parameters 252 | * 253 | * @return array 254 | */ 255 | public function getParams() 256 | { 257 | return $this->params; 258 | } 259 | 260 | /** 261 | * Return parameter types 262 | * 263 | * @return array 264 | */ 265 | public function getTypes() 266 | { 267 | return $this->types; 268 | } 269 | 270 | /** 271 | * Load XML and parse into request components 272 | * 273 | * @param string $request 274 | * @throws Exception\ValueException if invalid XML 275 | * @return bool True on success, false if an error occurred. 276 | */ 277 | public function loadXml($request) 278 | { 279 | if (! is_string($request)) { 280 | $this->fault = new Fault(635); 281 | $this->fault->setEncoding($this->getEncoding()); 282 | return false; 283 | } 284 | 285 | // @see ZF-12293 - disable external entities for security purposes 286 | $loadEntities = libxml_disable_entity_loader(true); 287 | $xmlErrorsFlag = libxml_use_internal_errors(true); 288 | try { 289 | $dom = new DOMDocument; 290 | $dom->loadXML($request); 291 | foreach ($dom->childNodes as $child) { 292 | if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) { 293 | throw new Exception\ValueException( 294 | 'Invalid XML: Detected use of illegal DOCTYPE' 295 | ); 296 | } 297 | } 298 | ErrorHandler::start(); 299 | $xml = simplexml_import_dom($dom); 300 | $error = ErrorHandler::stop(); 301 | libxml_disable_entity_loader($loadEntities); 302 | libxml_use_internal_errors($xmlErrorsFlag); 303 | } catch (\Exception $e) { 304 | // Not valid XML 305 | $this->fault = new Fault(631); 306 | $this->fault->setEncoding($this->getEncoding()); 307 | libxml_disable_entity_loader($loadEntities); 308 | libxml_use_internal_errors($xmlErrorsFlag); 309 | return false; 310 | } 311 | if (! $xml instanceof SimpleXMLElement || $error) { 312 | // Not valid XML 313 | $this->fault = new Fault(631); 314 | $this->fault->setEncoding($this->getEncoding()); 315 | libxml_use_internal_errors($xmlErrorsFlag); 316 | return false; 317 | } 318 | 319 | // Check for method name 320 | if (empty($xml->methodName)) { 321 | // Missing method name 322 | $this->fault = new Fault(632); 323 | $this->fault->setEncoding($this->getEncoding()); 324 | return false; 325 | } 326 | 327 | $this->method = (string) $xml->methodName; 328 | 329 | // Check for parameters 330 | if (! empty($xml->params)) { 331 | $types = []; 332 | $argv = []; 333 | foreach ($xml->params->children() as $param) { 334 | if (! isset($param->value)) { 335 | $this->fault = new Fault(633); 336 | $this->fault->setEncoding($this->getEncoding()); 337 | return false; 338 | } 339 | 340 | try { 341 | $param = AbstractValue::getXmlRpcValue($param->value, AbstractValue::XML_STRING); 342 | $types[] = $param->getType(); 343 | $argv[] = $param->getValue(); 344 | } catch (\Exception $e) { 345 | $this->fault = new Fault(636); 346 | $this->fault->setEncoding($this->getEncoding()); 347 | return false; 348 | } 349 | } 350 | 351 | $this->types = $types; 352 | $this->params = $argv; 353 | } 354 | 355 | $this->xml = $request; 356 | 357 | return true; 358 | } 359 | 360 | /** 361 | * Does the current request contain errors and should it return a fault 362 | * response? 363 | * 364 | * @return bool 365 | */ 366 | public function isFault() 367 | { 368 | return $this->fault instanceof Fault; 369 | } 370 | 371 | /** 372 | * Retrieve the fault response, if any 373 | * 374 | * @return null|\Zend\XmlRpc\Fault 375 | */ 376 | public function getFault() 377 | { 378 | return $this->fault; 379 | } 380 | 381 | /** 382 | * Retrieve method parameters as XMLRPC values 383 | * 384 | * @return array 385 | */ 386 | protected function getXmlRpcParams() 387 | { 388 | $params = []; 389 | if (is_array($this->xmlRpcParams)) { 390 | foreach ($this->xmlRpcParams as $param) { 391 | $value = $param['value']; 392 | $type = $param['type'] ?: AbstractValue::AUTO_DETECT_TYPE; 393 | 394 | if (! $value instanceof AbstractValue) { 395 | $value = AbstractValue::getXmlRpcValue($value, $type); 396 | } 397 | $params[] = $value; 398 | } 399 | } 400 | 401 | return $params; 402 | } 403 | 404 | /** 405 | * Create XML request 406 | * 407 | * @return string 408 | */ 409 | public function saveXml() 410 | { 411 | $args = $this->getXmlRpcParams(); 412 | $method = $this->getMethod(); 413 | 414 | $generator = AbstractValue::getGenerator(); 415 | $generator->openElement('methodCall') 416 | ->openElement('methodName', $method) 417 | ->closeElement('methodName'); 418 | 419 | if (is_array($args) && count($args)) { 420 | $generator->openElement('params'); 421 | 422 | foreach ($args as $arg) { 423 | $generator->openElement('param'); 424 | $arg->generateXml(); 425 | $generator->closeElement('param'); 426 | } 427 | $generator->closeElement('params'); 428 | } 429 | $generator->closeElement('methodCall'); 430 | 431 | return $generator->flush(); 432 | } 433 | 434 | /** 435 | * Return XML request 436 | * 437 | * @return string 438 | */ 439 | public function __toString() 440 | { 441 | return $this->saveXML(); 442 | } 443 | } 444 | -------------------------------------------------------------------------------- /src/Request/Http.php: -------------------------------------------------------------------------------- 1 | fault = new Fault(630); 52 | return; 53 | } 54 | 55 | $this->xml = $xml; 56 | 57 | $this->loadXml($xml); 58 | } 59 | 60 | /** 61 | * Retrieve the raw XML request 62 | * 63 | * @return string 64 | */ 65 | public function getRawRequest() 66 | { 67 | return $this->xml; 68 | } 69 | 70 | /** 71 | * Get headers 72 | * 73 | * Gets all headers as key => value pairs and returns them. 74 | * 75 | * @return array 76 | */ 77 | public function getHeaders() 78 | { 79 | if (null === $this->headers) { 80 | $this->headers = []; 81 | foreach ($_SERVER as $key => $value) { 82 | if ('HTTP_' == substr($key, 0, 5)) { 83 | $header = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5))))); 84 | $this->headers[$header] = $value; 85 | } 86 | } 87 | } 88 | 89 | return $this->headers; 90 | } 91 | 92 | /** 93 | * Retrieve the full HTTP request, including headers and XML 94 | * 95 | * @return string 96 | */ 97 | public function getFullRequest() 98 | { 99 | $request = ''; 100 | foreach ($this->getHeaders() as $key => $value) { 101 | $request .= $key . ': ' . $value . "\n"; 102 | } 103 | 104 | $request .= $this->xml; 105 | 106 | return $request; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Request/Stdin.php: -------------------------------------------------------------------------------- 1 | fault = new Fault(630); 43 | return; 44 | } 45 | 46 | $xml = ''; 47 | while (! feof($fh)) { 48 | $xml .= fgets($fh); 49 | } 50 | fclose($fh); 51 | 52 | $this->xml = $xml; 53 | 54 | $this->loadXml($xml); 55 | } 56 | 57 | /** 58 | * Retrieve the raw XML request 59 | * 60 | * @return string 61 | */ 62 | public function getRawRequest() 63 | { 64 | return $this->xml; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Response.php: -------------------------------------------------------------------------------- 1 | setReturnValue($return, $type); 57 | } 58 | 59 | /** 60 | * Set encoding to use in response 61 | * 62 | * @param string $encoding 63 | * @return \Zend\XmlRpc\Response 64 | */ 65 | public function setEncoding($encoding) 66 | { 67 | $this->encoding = $encoding; 68 | AbstractValue::setEncoding($encoding); 69 | return $this; 70 | } 71 | 72 | /** 73 | * Retrieve current response encoding 74 | * 75 | * @return string 76 | */ 77 | public function getEncoding() 78 | { 79 | return $this->encoding; 80 | } 81 | 82 | /** 83 | * Set the return value 84 | * 85 | * Sets the return value, with optional type hinting if provided. 86 | * 87 | * @param mixed $value 88 | * @param string $type 89 | * @return void 90 | */ 91 | public function setReturnValue($value, $type = null) 92 | { 93 | $this->return = $value; 94 | $this->type = (string) $type; 95 | } 96 | 97 | /** 98 | * Retrieve the return value 99 | * 100 | * @return mixed 101 | */ 102 | public function getReturnValue() 103 | { 104 | return $this->return; 105 | } 106 | 107 | /** 108 | * Retrieve the XMLRPC value for the return value 109 | * 110 | * @return \Zend\XmlRpc\AbstractValue 111 | */ 112 | protected function getXmlRpcReturn() 113 | { 114 | return AbstractValue::getXmlRpcValue($this->return); 115 | } 116 | 117 | /** 118 | * Is the response a fault response? 119 | * 120 | * @return bool 121 | */ 122 | public function isFault() 123 | { 124 | return $this->fault instanceof Fault; 125 | } 126 | 127 | /** 128 | * Returns the fault, if any. 129 | * 130 | * @return null|\Zend\XmlRpc\Fault 131 | */ 132 | public function getFault() 133 | { 134 | return $this->fault; 135 | } 136 | 137 | /** 138 | * Load a response from an XML response 139 | * 140 | * Attempts to load a response from an XMLRPC response, autodetecting if it 141 | * is a fault response. 142 | * 143 | * @param string $response 144 | * @throws Exception\ValueException if invalid XML 145 | * @return bool True if a valid XMLRPC response, false if a fault 146 | * response or invalid input 147 | */ 148 | public function loadXml($response) 149 | { 150 | if (! is_string($response)) { 151 | $this->fault = new Fault(650); 152 | $this->fault->setEncoding($this->getEncoding()); 153 | return false; 154 | } 155 | 156 | try { 157 | $xml = XmlSecurity::scan($response); 158 | } catch (\ZendXml\Exception\RuntimeException $e) { 159 | $this->fault = new Fault(651); 160 | $this->fault->setEncoding($this->getEncoding()); 161 | return false; 162 | } 163 | 164 | if (! empty($xml->fault)) { 165 | // fault response 166 | $this->fault = new Fault(); 167 | $this->fault->setEncoding($this->getEncoding()); 168 | $this->fault->loadXml($response); 169 | return false; 170 | } 171 | 172 | if (empty($xml->params)) { 173 | // Invalid response 174 | $this->fault = new Fault(652); 175 | $this->fault->setEncoding($this->getEncoding()); 176 | return false; 177 | } 178 | 179 | try { 180 | if (! isset($xml->params) || ! isset($xml->params->param) || ! isset($xml->params->param->value)) { 181 | throw new Exception\ValueException('Missing XML-RPC value in XML'); 182 | } 183 | $valueXml = $xml->params->param->value->asXML(); 184 | $value = AbstractValue::getXmlRpcValue($valueXml, AbstractValue::XML_STRING); 185 | } catch (Exception\ValueException $e) { 186 | $this->fault = new Fault(653); 187 | $this->fault->setEncoding($this->getEncoding()); 188 | return false; 189 | } 190 | 191 | $this->setReturnValue($value->getValue()); 192 | return true; 193 | } 194 | 195 | /** 196 | * Return response as XML 197 | * 198 | * @return string 199 | */ 200 | public function saveXml() 201 | { 202 | $value = $this->getXmlRpcReturn(); 203 | $generator = AbstractValue::getGenerator(); 204 | $generator->openElement('methodResponse') 205 | ->openElement('params') 206 | ->openElement('param'); 207 | $value->generateXml(); 208 | $generator->closeElement('param') 209 | ->closeElement('params') 210 | ->closeElement('methodResponse'); 211 | 212 | return $generator->flush(); 213 | } 214 | 215 | /** 216 | * Return XML response 217 | * 218 | * @return string 219 | */ 220 | public function __toString() 221 | { 222 | return $this->saveXML(); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/Response/Http.php: -------------------------------------------------------------------------------- 1 | getEncoding())); 28 | } 29 | 30 | return parent::__toString(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Server.php: -------------------------------------------------------------------------------- 1 | 21 | * use Zend\XmlRpc; 22 | * 23 | * // Instantiate server 24 | * $server = new XmlRpc\Server(); 25 | * 26 | * // Allow some exceptions to report as fault responses: 27 | * XmlRpc\Server\Fault::attachFaultException('My\\Exception'); 28 | * XmlRpc\Server\Fault::attachObserver('My\\Fault\\Observer'); 29 | * 30 | * // Get or build dispatch table: 31 | * if (!XmlRpc\Server\Cache::get($filename, $server)) { 32 | * 33 | * // Attach Some_Service_Class in 'some' namespace 34 | * $server->setClass('Some\\Service\\Class', 'some'); 35 | * 36 | * // Attach Another_Service_Class in 'another' namespace 37 | * $server->setClass('Another\\Service\\Class', 'another'); 38 | * 39 | * // Create dispatch table cache file 40 | * XmlRpc\Server\Cache::save($filename, $server); 41 | * } 42 | * 43 | * $response = $server->handle(); 44 | * echo $response; 45 | * 46 | */ 47 | class Server extends AbstractServer 48 | { 49 | /** 50 | * Character encoding 51 | * @var string 52 | */ 53 | protected $encoding = 'UTF-8'; 54 | 55 | /** 56 | * Request processed 57 | * @var null|Request 58 | */ 59 | protected $request = null; 60 | 61 | /** 62 | * Class to use for responses; defaults to {@link Response\Http} 63 | * @var string 64 | */ 65 | protected $responseClass = 'Zend\XmlRpc\Response\Http'; 66 | 67 | /** 68 | * Dispatch table of name => method pairs 69 | * @var Definition 70 | */ 71 | protected $table; 72 | 73 | /** 74 | * PHP types => XML-RPC types 75 | * @var array 76 | */ 77 | protected $typeMap = [ 78 | 'i4' => 'i4', 79 | 'int' => 'int', 80 | 'integer' => 'int', 81 | 'i8' => 'i8', 82 | 'ex:i8' => 'i8', 83 | 'double' => 'double', 84 | 'float' => 'double', 85 | 'real' => 'double', 86 | 'boolean' => 'boolean', 87 | 'bool' => 'boolean', 88 | 'true' => 'boolean', 89 | 'false' => 'boolean', 90 | 'string' => 'string', 91 | 'str' => 'string', 92 | 'base64' => 'base64', 93 | 'dateTime.iso8601' => 'dateTime.iso8601', 94 | 'date' => 'dateTime.iso8601', 95 | 'time' => 'dateTime.iso8601', 96 | 'DateTime' => 'dateTime.iso8601', 97 | 'array' => 'array', 98 | 'struct' => 'struct', 99 | 'null' => 'nil', 100 | 'nil' => 'nil', 101 | 'ex:nil' => 'nil', 102 | 'void' => 'void', 103 | 'mixed' => 'struct', 104 | ]; 105 | 106 | /** 107 | * Send arguments to all methods or just constructor? 108 | * 109 | * @var bool 110 | */ 111 | protected $sendArgumentsToAllMethods = true; 112 | 113 | /** 114 | * Flag: whether or not {@link handle()} should return a response instead 115 | * of automatically emitting it. 116 | * @var bool 117 | */ 118 | protected $returnResponse = false; 119 | 120 | /** 121 | * Last response results. 122 | * @var Response 123 | */ 124 | protected $response; 125 | 126 | /** 127 | * Constructor 128 | * 129 | * Creates system.* methods. 130 | * 131 | */ 132 | public function __construct() 133 | { 134 | $this->table = new Definition(); 135 | $this->registerSystemMethods(); 136 | } 137 | 138 | /** 139 | * Proxy calls to system object 140 | * 141 | * @param string $method 142 | * @param array $params 143 | * @return mixed 144 | * @throws Server\Exception\BadMethodCallException 145 | */ 146 | public function __call($method, $params) 147 | { 148 | $system = $this->getSystem(); 149 | if (! method_exists($system, $method)) { 150 | throw new Server\Exception\BadMethodCallException('Unknown instance method called on server: ' . $method); 151 | } 152 | return call_user_func_array([$system, $method], $params); 153 | } 154 | 155 | /** 156 | * Attach a callback as an XMLRPC method 157 | * 158 | * Attaches a callback as an XMLRPC method, prefixing the XMLRPC method name 159 | * with $namespace, if provided. Reflection is done on the callback's 160 | * docblock to create the methodHelp for the XMLRPC method. 161 | * 162 | * Additional arguments to pass to the function at dispatch may be passed; 163 | * any arguments following the namespace will be aggregated and passed at 164 | * dispatch time. 165 | * 166 | * @param string|array|callable $function Valid callback 167 | * @param string $namespace Optional namespace prefix 168 | * @throws Server\Exception\InvalidArgumentException 169 | * @return void 170 | */ 171 | public function addFunction($function, $namespace = '') 172 | { 173 | if (! is_string($function) && ! is_array($function)) { 174 | throw new Server\Exception\InvalidArgumentException('Unable to attach function; invalid', 611); 175 | } 176 | 177 | $argv = null; 178 | if (2 < func_num_args()) { 179 | $argv = func_get_args(); 180 | $argv = array_slice($argv, 2); 181 | } 182 | 183 | $function = (array) $function; 184 | foreach ($function as $func) { 185 | if (! is_string($func) || ! function_exists($func)) { 186 | throw new Server\Exception\InvalidArgumentException('Unable to attach function; invalid', 611); 187 | } 188 | $reflection = Reflection::reflectFunction($func, $argv, $namespace); 189 | $this->_buildSignature($reflection); 190 | } 191 | } 192 | 193 | /** 194 | * Attach class methods as XMLRPC method handlers 195 | * 196 | * $class may be either a class name or an object. Reflection is done on the 197 | * class or object to determine the available public methods, and each is 198 | * attached to the server as an available method; if a $namespace has been 199 | * provided, that namespace is used to prefix the XMLRPC method names. 200 | * 201 | * Any additional arguments beyond $namespace will be passed to a method at 202 | * invocation. 203 | * 204 | * @param string|object $class 205 | * @param string $namespace Optional 206 | * @param mixed $argv Optional arguments to pass to methods 207 | * @return void 208 | * @throws Server\Exception\InvalidArgumentException on invalid input 209 | */ 210 | public function setClass($class, $namespace = '', $argv = null) 211 | { 212 | if (is_string($class) && ! class_exists($class)) { 213 | throw new Server\Exception\InvalidArgumentException('Invalid method class', 610); 214 | } 215 | 216 | if (2 < func_num_args()) { 217 | $argv = func_get_args(); 218 | $argv = array_slice($argv, 2); 219 | } 220 | 221 | $dispatchable = Reflection::reflectClass($class, $argv, $namespace); 222 | foreach ($dispatchable->getMethods() as $reflection) { 223 | $this->_buildSignature($reflection, $class); 224 | } 225 | } 226 | 227 | /** 228 | * Raise an xmlrpc server fault 229 | * 230 | * @param string|\Exception $fault 231 | * @param int $code 232 | * @return Server\Fault 233 | */ 234 | public function fault($fault = null, $code = 404) 235 | { 236 | if (! $fault instanceof \Exception) { 237 | $fault = (string) $fault; 238 | if (empty($fault)) { 239 | $fault = 'Unknown Error'; 240 | } 241 | $fault = new Server\Exception\RuntimeException($fault, $code); 242 | } 243 | 244 | return Server\Fault::getInstance($fault); 245 | } 246 | 247 | /** 248 | * Set return response flag 249 | * 250 | * If true, {@link handle()} will return the response instead of 251 | * automatically sending it back to the requesting client. 252 | * 253 | * The response is always available via {@link getResponse()}. 254 | * 255 | * @param bool $flag 256 | * @return Server 257 | */ 258 | public function setReturnResponse($flag = true) 259 | { 260 | $this->returnResponse = (bool) $flag; 261 | return $this; 262 | } 263 | 264 | /** 265 | * Retrieve return response flag 266 | * 267 | * @return bool 268 | */ 269 | public function getReturnResponse() 270 | { 271 | return $this->returnResponse; 272 | } 273 | 274 | /** 275 | * Handle an xmlrpc call 276 | * 277 | * @param Request $request Optional 278 | * @return Response|Fault 279 | */ 280 | public function handle($request = false) 281 | { 282 | // Get request 283 | if ((! $request || ! $request instanceof Request) 284 | && (null === ($request = $this->getRequest())) 285 | ) { 286 | $request = new Request\Http(); 287 | $request->setEncoding($this->getEncoding()); 288 | } 289 | 290 | $this->setRequest($request); 291 | 292 | if ($request->isFault()) { 293 | $response = $request->getFault(); 294 | } else { 295 | try { 296 | $response = $this->handleRequest($request); 297 | } catch (\Exception $e) { 298 | $response = $this->fault($e); 299 | } 300 | } 301 | 302 | // Set output encoding 303 | $response->setEncoding($this->getEncoding()); 304 | $this->response = $response; 305 | 306 | if (! $this->returnResponse) { 307 | echo $response; 308 | return; 309 | } 310 | 311 | return $response; 312 | } 313 | 314 | /** 315 | * Load methods as returned from {@link getFunctions} 316 | * 317 | * Typically, you will not use this method; it will be called using the 318 | * results pulled from {@link Zend\XmlRpc\Server\Cache::get()}. 319 | * 320 | * @param array|Definition $definition 321 | * @return void 322 | * @throws Server\Exception\InvalidArgumentException on invalid input 323 | */ 324 | public function loadFunctions($definition) 325 | { 326 | if (! is_array($definition) && (! $definition instanceof Definition)) { 327 | if (is_object($definition)) { 328 | $type = get_class($definition); 329 | } else { 330 | $type = gettype($definition); 331 | } 332 | throw new Server\Exception\InvalidArgumentException( 333 | 'Unable to load server definition; must be an array or Zend\Server\Definition, received ' . $type, 334 | 612 335 | ); 336 | } 337 | 338 | $this->table->clearMethods(); 339 | $this->registerSystemMethods(); 340 | 341 | if ($definition instanceof Definition) { 342 | $definition = $definition->getMethods(); 343 | } 344 | 345 | foreach ($definition as $key => $method) { 346 | if ('system.' == substr($key, 0, 7)) { 347 | continue; 348 | } 349 | $this->table->addMethod($method, $key); 350 | } 351 | } 352 | 353 | /** 354 | * Set encoding 355 | * 356 | * @param string $encoding 357 | * @return Server 358 | */ 359 | public function setEncoding($encoding) 360 | { 361 | $this->encoding = $encoding; 362 | AbstractValue::setEncoding($encoding); 363 | return $this; 364 | } 365 | 366 | /** 367 | * Retrieve current encoding 368 | * 369 | * @return string 370 | */ 371 | public function getEncoding() 372 | { 373 | return $this->encoding; 374 | } 375 | 376 | /** 377 | * Do nothing; persistence is handled via {@link Zend\XmlRpc\Server\Cache} 378 | * 379 | * @param mixed $mode 380 | * @return void 381 | */ 382 | public function setPersistence($mode) 383 | { 384 | } 385 | 386 | /** 387 | * Set the request object 388 | * 389 | * @param string|Request $request 390 | * @return Server 391 | * @throws Server\Exception\InvalidArgumentException on invalid request class or object 392 | */ 393 | public function setRequest($request) 394 | { 395 | if (is_string($request) && class_exists($request)) { 396 | $request = new $request(); 397 | if (! $request instanceof Request) { 398 | throw new Server\Exception\InvalidArgumentException('Invalid request class'); 399 | } 400 | $request->setEncoding($this->getEncoding()); 401 | } elseif (! $request instanceof Request) { 402 | throw new Server\Exception\InvalidArgumentException('Invalid request object'); 403 | } 404 | 405 | $this->request = $request; 406 | return $this; 407 | } 408 | 409 | /** 410 | * Return currently registered request object 411 | * 412 | * @return null|Request 413 | */ 414 | public function getRequest() 415 | { 416 | return $this->request; 417 | } 418 | 419 | /** 420 | * Last response. 421 | * 422 | * @return Response 423 | */ 424 | public function getResponse() 425 | { 426 | return $this->response; 427 | } 428 | 429 | /** 430 | * Set the class to use for the response 431 | * 432 | * @param string $class 433 | * @throws Server\Exception\InvalidArgumentException if invalid response class 434 | * @return bool True if class was set, false if not 435 | */ 436 | public function setResponseClass($class) 437 | { 438 | if (! class_exists($class) || ! is_subclass_of($class, 'Zend\XmlRpc\Response')) { 439 | throw new Server\Exception\InvalidArgumentException('Invalid response class'); 440 | } 441 | $this->responseClass = $class; 442 | return true; 443 | } 444 | 445 | /** 446 | * Retrieve current response class 447 | * 448 | * @return string 449 | */ 450 | public function getResponseClass() 451 | { 452 | return $this->responseClass; 453 | } 454 | 455 | /** 456 | * Retrieve dispatch table 457 | * 458 | * @return array 459 | */ 460 | public function getDispatchTable() 461 | { 462 | return $this->table; 463 | } 464 | 465 | /** 466 | * Returns a list of registered methods 467 | * 468 | * Returns an array of dispatchables (Zend\Server\Reflection\ReflectionFunction, 469 | * ReflectionMethod, and ReflectionClass items). 470 | * 471 | * @return array 472 | */ 473 | public function getFunctions() 474 | { 475 | return $this->table->toArray(); 476 | } 477 | 478 | /** 479 | * Retrieve system object 480 | * 481 | * @return Server\System 482 | */ 483 | public function getSystem() 484 | { 485 | return $this->system; 486 | } 487 | 488 | /** 489 | * Send arguments to all methods? 490 | * 491 | * If setClass() is used to add classes to the server, this flag defined 492 | * how to handle arguments. If set to true, all methods including constructor 493 | * will receive the arguments. If set to false, only constructor will receive the 494 | * arguments 495 | * 496 | * @param bool|null $flag 497 | * @return self 498 | */ 499 | public function sendArgumentsToAllMethods($flag = null) 500 | { 501 | if ($flag === null) { 502 | return $this->sendArgumentsToAllMethods; 503 | } 504 | 505 | $this->sendArgumentsToAllMethods = (bool) $flag; 506 | return $this; 507 | } 508 | 509 | // @codingStandardsIgnoreStart 510 | /** 511 | * Map PHP type to XML-RPC type 512 | * 513 | * @param string $type 514 | * @return string 515 | */ 516 | protected function _fixType($type) 517 | { 518 | if (isset($this->typeMap[$type])) { 519 | return $this->typeMap[$type]; 520 | } 521 | return 'void'; 522 | } 523 | // @codingStandardsIgnoreEnd 524 | 525 | /** 526 | * Handle an xmlrpc call (actual work) 527 | * 528 | * @param Request $request 529 | * @return Response 530 | * @throws Server\Exception\RuntimeException 531 | * Zend\XmlRpc\Server\Exceptions are thrown for internal errors; otherwise, 532 | * any other exception may be thrown by the callback 533 | */ 534 | protected function handleRequest(Request $request) 535 | { 536 | $method = $request->getMethod(); 537 | 538 | // Check for valid method 539 | if (! $this->table->hasMethod($method)) { 540 | throw new Server\Exception\RuntimeException('Method "' . $method . '" does not exist', 620); 541 | } 542 | 543 | $info = $this->table->getMethod($method); 544 | $params = $request->getParams(); 545 | $argv = $info->getInvokeArguments(); 546 | if (0 < count($argv) and $this->sendArgumentsToAllMethods()) { 547 | $params = array_merge($params, $argv); 548 | } 549 | 550 | // Check calling parameters against signatures 551 | $matched = false; 552 | $sigCalled = $request->getTypes(); 553 | 554 | $sigLength = count($sigCalled); 555 | $paramsLen = count($params); 556 | if ($sigLength < $paramsLen) { 557 | for ($i = $sigLength; $i < $paramsLen; ++$i) { 558 | $xmlRpcValue = AbstractValue::getXmlRpcValue($params[$i]); 559 | $sigCalled[] = $xmlRpcValue->getType(); 560 | } 561 | } 562 | 563 | $signatures = $info->getPrototypes(); 564 | foreach ($signatures as $signature) { 565 | $sigParams = $signature->getParameters(); 566 | if ($sigCalled === $sigParams) { 567 | $matched = true; 568 | break; 569 | } 570 | } 571 | if (! $matched) { 572 | throw new Server\Exception\RuntimeException('Calling parameters do not match signature', 623); 573 | } 574 | 575 | $return = $this->_dispatch($info, $params); 576 | $responseClass = $this->getResponseClass(); 577 | return new $responseClass($return); 578 | } 579 | 580 | /** 581 | * Register system methods with the server 582 | * 583 | * @return void 584 | */ 585 | protected function registerSystemMethods() 586 | { 587 | $system = new Server\System($this); 588 | $this->system = $system; 589 | $this->setClass($system, 'system'); 590 | } 591 | 592 | /** 593 | * Checks if the object has this class as one of its parents 594 | * 595 | * @see https://bugs.php.net/bug.php?id=53727 596 | * @see https://github.com/zendframework/zf2/pull/1807 597 | * 598 | * @deprecated since zf 2.3 requires PHP >= 5.3.23 599 | * 600 | * @param string $className 601 | * @param string $type 602 | * @return bool 603 | */ 604 | protected static function isSubclassOf($className, $type) 605 | { 606 | return is_subclass_of($className, $type); 607 | } 608 | } 609 | -------------------------------------------------------------------------------- /src/Server/Cache.php: -------------------------------------------------------------------------------- 1 | true]; 38 | 39 | /** 40 | * @var array Array of fault observers 41 | */ 42 | protected static $observers = []; 43 | 44 | /** 45 | * Constructor 46 | * 47 | * @param \Exception $e 48 | * @return Fault 49 | */ 50 | public function __construct(\Exception $e) 51 | { 52 | $this->exception = $e; 53 | $code = 404; 54 | $message = 'Unknown error'; 55 | 56 | foreach (array_keys(static::$faultExceptionClasses) as $class) { 57 | if ($e instanceof $class) { 58 | $code = $e->getCode(); 59 | $message = $e->getMessage(); 60 | break; 61 | } 62 | } 63 | 64 | parent::__construct($code, $message); 65 | 66 | // Notify exception observers, if present 67 | if (! empty(static::$observers)) { 68 | foreach (array_keys(static::$observers) as $observer) { 69 | $observer::observe($this); 70 | } 71 | } 72 | } 73 | 74 | /** 75 | * Return Zend\XmlRpc\Server\Fault instance 76 | * 77 | * @param \Exception $e 78 | * @return Fault 79 | */ 80 | public static function getInstance(\Exception $e) 81 | { 82 | return new static($e); 83 | } 84 | 85 | /** 86 | * Attach valid exceptions that can be used to define xmlrpc faults 87 | * 88 | * @param string|array $classes Class name or array of class names 89 | * @return void 90 | */ 91 | public static function attachFaultException($classes) 92 | { 93 | if (! is_array($classes)) { 94 | $classes = (array) $classes; 95 | } 96 | 97 | foreach ($classes as $class) { 98 | if (is_string($class) && class_exists($class)) { 99 | static::$faultExceptionClasses[$class] = true; 100 | } 101 | } 102 | } 103 | 104 | /** 105 | * Detach fault exception classes 106 | * 107 | * @param string|array $classes Class name or array of class names 108 | * @return void 109 | */ 110 | public static function detachFaultException($classes) 111 | { 112 | if (! is_array($classes)) { 113 | $classes = (array) $classes; 114 | } 115 | 116 | foreach ($classes as $class) { 117 | if (is_string($class) && isset(static::$faultExceptionClasses[$class])) { 118 | unset(static::$faultExceptionClasses[$class]); 119 | } 120 | } 121 | } 122 | 123 | /** 124 | * Attach an observer class 125 | * 126 | * Allows observation of xmlrpc server faults, thus allowing logging or mail 127 | * notification of fault responses on the xmlrpc server. 128 | * 129 | * Expects a valid class name; that class must have a public static method 130 | * 'observe' that accepts an exception as its sole argument. 131 | * 132 | * @param string $class 133 | * @return bool 134 | */ 135 | public static function attachObserver($class) 136 | { 137 | if (! is_string($class) || ! class_exists($class) || ! is_callable([$class, 'observe'])) { 138 | return false; 139 | } 140 | 141 | if (! isset(static::$observers[$class])) { 142 | static::$observers[$class] = true; 143 | } 144 | 145 | return true; 146 | } 147 | 148 | /** 149 | * Detach an observer 150 | * 151 | * @param string $class 152 | * @return bool 153 | */ 154 | public static function detachObserver($class) 155 | { 156 | if (! isset(static::$observers[$class])) { 157 | return false; 158 | } 159 | 160 | unset(static::$observers[$class]); 161 | return true; 162 | } 163 | 164 | /** 165 | * Retrieve the exception 166 | * 167 | * @access public 168 | * @return \Exception 169 | */ 170 | public function getException() 171 | { 172 | return $this->exception; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/Server/System.php: -------------------------------------------------------------------------------- 1 | server = $server; 30 | } 31 | 32 | /** 33 | * List all available XMLRPC methods 34 | * 35 | * Returns an array of methods. 36 | * 37 | * @return array 38 | */ 39 | public function listMethods() 40 | { 41 | $table = $this->server->getDispatchTable()->getMethods(); 42 | return array_keys($table); 43 | } 44 | 45 | /** 46 | * Display help message for an XMLRPC method 47 | * 48 | * @param string $method 49 | * @throws Exception\InvalidArgumentException 50 | * @return string 51 | */ 52 | public function methodHelp($method) 53 | { 54 | $table = $this->server->getDispatchTable(); 55 | if (! $table->hasMethod($method)) { 56 | throw new Exception\InvalidArgumentException('Method "' . $method . '" does not exist', 640); 57 | } 58 | 59 | return $table->getMethod($method)->getMethodHelp(); 60 | } 61 | 62 | /** 63 | * Return a method signature 64 | * 65 | * @param string $method 66 | * @throws Exception\InvalidArgumentException 67 | * @return array 68 | */ 69 | public function methodSignature($method) 70 | { 71 | $table = $this->server->getDispatchTable(); 72 | if (! $table->hasMethod($method)) { 73 | throw new Exception\InvalidArgumentException('Method "' . $method . '" does not exist', 640); 74 | } 75 | $method = $table->getMethod($method)->toArray(); 76 | return $method['prototypes']; 77 | } 78 | 79 | /** 80 | * Multicall - boxcar feature of XML-RPC for calling multiple methods 81 | * in a single request. 82 | * 83 | * Expects an array of structs representing method calls, each element 84 | * having the keys: 85 | * - methodName 86 | * - params 87 | * 88 | * Returns an array of responses, one for each method called, with the value 89 | * returned by the method. If an error occurs for a given method, returns a 90 | * struct with a fault response. 91 | * 92 | * @see http://www.xmlrpc.com/discuss/msgReader$1208 93 | * @param array $methods 94 | * @return array 95 | */ 96 | public function multicall($methods) 97 | { 98 | $responses = []; 99 | foreach ($methods as $method) { 100 | $fault = false; 101 | if (! is_array($method)) { 102 | $fault = $this->server->fault('system.multicall expects each method to be a struct', 601); 103 | } elseif (! isset($method['methodName'])) { 104 | $fault = $this->server->fault('Missing methodName: ' . var_export($methods, 1), 602); 105 | } elseif (! isset($method['params'])) { 106 | $fault = $this->server->fault('Missing params', 603); 107 | } elseif (! is_array($method['params'])) { 108 | $fault = $this->server->fault('Params must be an array', 604); 109 | } else { 110 | if ('system.multicall' == $method['methodName']) { 111 | // don't allow recursive calls to multicall 112 | $fault = $this->server->fault('Recursive system.multicall forbidden', 605); 113 | } 114 | } 115 | 116 | if (! $fault) { 117 | try { 118 | $request = new \Zend\XmlRpc\Request(); 119 | $request->setMethod($method['methodName']); 120 | $request->setParams($method['params']); 121 | $response = $this->server->handle($request); 122 | if ($response instanceof \Zend\XmlRpc\Fault 123 | || $response->isFault() 124 | ) { 125 | $fault = $response; 126 | } else { 127 | $responses[] = $response->getReturnValue(); 128 | } 129 | } catch (\Exception $e) { 130 | $fault = $this->server->fault($e); 131 | } 132 | } 133 | 134 | if ($fault) { 135 | $responses[] = [ 136 | 'faultCode' => $fault->getCode(), 137 | 'faultString' => $fault->getMessage() 138 | ]; 139 | } 140 | } 141 | 142 | return $responses; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/Value/AbstractCollection.php: -------------------------------------------------------------------------------- 1 | $value) { 25 | // If the elements of the given array are not Zend\XmlRpc\Value objects, 26 | // we need to convert them as such (using auto-detection from PHP value) 27 | if (! $value instanceof parent) { 28 | $value = static::getXmlRpcValue($value, self::AUTO_DETECT_TYPE); 29 | } 30 | $this->value[$key] = $value; 31 | } 32 | } 33 | 34 | /** 35 | * Return the value of this object, convert the XML-RPC native collection values into a PHP array 36 | * 37 | * @return array 38 | */ 39 | public function getValue() 40 | { 41 | $values = (array) $this->value; 42 | foreach ($values as $key => $value) { 43 | $values[$key] = $value->getValue(); 44 | } 45 | return $values; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Value/AbstractScalar.php: -------------------------------------------------------------------------------- 1 | getGenerator(); 24 | 25 | $generator 26 | ->openElement('value') 27 | ->openElement($this->type, $this->value) 28 | ->closeElement($this->type) 29 | ->closeElement('value'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Value/ArrayValue.php: -------------------------------------------------------------------------------- 1 | type = self::XMLRPC_TYPE_ARRAY; 22 | parent::__construct($value); 23 | } 24 | 25 | /** 26 | * Generate the XML code that represent an array native MXL-RPC value 27 | * 28 | * @return void 29 | */ 30 | protected function generate() 31 | { 32 | $generator = $this->getGenerator(); 33 | $generator 34 | ->openElement('value') 35 | ->openElement('array') 36 | ->openElement('data'); 37 | 38 | if (is_array($this->value)) { 39 | foreach ($this->value as $val) { 40 | $val->generateXml(); 41 | } 42 | } 43 | 44 | $generator 45 | ->closeElement('data') 46 | ->closeElement('array') 47 | ->closeElement('value'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Value/Base64.php: -------------------------------------------------------------------------------- 1 | type = self::XMLRPC_TYPE_BASE64; 24 | 25 | $value = (string) $value; // Make sure this value is string 26 | if (! $alreadyEncoded) { 27 | $value = base64_encode($value); // We encode it in base64 28 | } 29 | $this->value = $value; 30 | } 31 | 32 | /** 33 | * Return the value of this object, convert the XML-RPC native base64 value into a PHP string 34 | * We return this value decoded (a normal string) 35 | * 36 | * @return string 37 | */ 38 | public function getValue() 39 | { 40 | return base64_decode($this->value); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Value/BigInteger.php: -------------------------------------------------------------------------------- 1 | value = BigIntegerMath::factory()->init($value, 10); 22 | $this->type = self::XMLRPC_TYPE_I8; 23 | } 24 | 25 | /** 26 | * Return bigint value object 27 | * 28 | * @return string 29 | */ 30 | public function getValue() 31 | { 32 | return $this->value; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Value/Boolean.php: -------------------------------------------------------------------------------- 1 | type = self::XMLRPC_TYPE_BOOLEAN; 23 | // Make sure the value is boolean and then convert it into an integer 24 | // The double conversion is because a bug in the ZendOptimizer in PHP version 5.0.4 25 | $this->value = (int)(bool) $value; 26 | } 27 | 28 | /** 29 | * Return the value of this object, convert the XML-RPC native boolean value into a PHP boolean 30 | * 31 | * @return bool 32 | */ 33 | public function getValue() 34 | { 35 | return (bool) $this->value; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Value/DateTime.php: -------------------------------------------------------------------------------- 1 | type = self::XMLRPC_TYPE_DATETIME; 42 | 43 | if ($value instanceof \DateTime) { 44 | $this->value = $value->format($this->phpFormatString); 45 | } elseif (is_numeric($value)) { // The value is numeric, we make sure it is an integer 46 | $this->value = date($this->phpFormatString, (int) $value); 47 | } else { 48 | try { 49 | $dateTime = new \DateTime($value); 50 | } catch (\Exception $e) { 51 | throw new Exception\ValueException($e->getMessage(), $e->getCode(), $e); 52 | } 53 | 54 | $this->value = $dateTime->format($this->phpFormatString); // Convert the DateTime to iso8601 format 55 | } 56 | } 57 | 58 | /** 59 | * Return the value of this object as iso8601 dateTime value 60 | * 61 | * @return int As a Unix timestamp 62 | */ 63 | public function getValue() 64 | { 65 | return $this->value; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Value/Double.php: -------------------------------------------------------------------------------- 1 | type = self::XMLRPC_TYPE_DOUBLE; 22 | $precision = (int) ini_get('precision'); 23 | $formatString = '%1.' . $precision . 'F'; 24 | $this->value = rtrim(sprintf($formatString, (float) $value), '0'); 25 | } 26 | 27 | /** 28 | * Return the value of this object, convert the XML-RPC native double value into a PHP float 29 | * 30 | * @return float 31 | */ 32 | public function getValue() 33 | { 34 | return (float) $this->value; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Value/Integer.php: -------------------------------------------------------------------------------- 1 | PHP_INT_MAX) { 25 | throw new Exception\ValueException('Overlong integer given'); 26 | } 27 | 28 | $this->type = self::XMLRPC_TYPE_INTEGER; 29 | $this->value = (int) $value; // Make sure this value is integer 30 | } 31 | 32 | /** 33 | * Return the value of this object, convert the XML-RPC native integer value into a PHP integer 34 | * 35 | * @return int 36 | */ 37 | public function getValue() 38 | { 39 | return $this->value; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Value/Nil.php: -------------------------------------------------------------------------------- 1 | type = self::XMLRPC_TYPE_NIL; 21 | $this->value = null; 22 | } 23 | 24 | /** 25 | * Return the value of this object, convert the XML-RPC native nill value into a PHP NULL 26 | * 27 | * @return null 28 | */ 29 | public function getValue() 30 | { 31 | return; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Value/Struct.php: -------------------------------------------------------------------------------- 1 | type = self::XMLRPC_TYPE_STRUCT; 22 | parent::__construct($value); 23 | } 24 | 25 | /** 26 | * Generate the XML code that represent struct native MXL-RPC value 27 | * 28 | * @return void 29 | */ 30 | protected function generate() 31 | { 32 | $generator = $this->getGenerator(); 33 | $generator 34 | ->openElement('value') 35 | ->openElement('struct'); 36 | 37 | if (is_array($this->value)) { 38 | foreach ($this->value as $name => $val) { 39 | $generator 40 | ->openElement('member') 41 | ->openElement('name', $name) 42 | ->closeElement('name'); 43 | $val->generateXml(); 44 | $generator->closeElement('member'); 45 | } 46 | } 47 | 48 | $generator 49 | ->closeElement('struct') 50 | ->closeElement('value'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Value/Text.php: -------------------------------------------------------------------------------- 1 | type = self::XMLRPC_TYPE_STRING; 22 | 23 | // Make sure this value is string and all XML characters are encoded 24 | $this->value = (string) $value; 25 | } 26 | 27 | /** 28 | * Return the value of this object, convert the XML-RPC native string value into a PHP string 29 | * 30 | * @return string 31 | */ 32 | public function getValue() 33 | { 34 | return (string) $this->value; 35 | } 36 | } 37 | --------------------------------------------------------------------------------