Differences Between: [Versions 401 and 403]
1 <?php 2 3 namespace PhpXmlRpc; 4 5 use PhpXmlRpc\Exception\StateErrorException; 6 use PhpXmlRpc\Exception\TypeErrorException; 7 use PhpXmlRpc\Exception\ValueErrorException; 8 use PhpXmlRpc\Traits\CharsetEncoderAware; 9 use PhpXmlRpc\Traits\DeprecationLogger; 10 11 /** 12 * This class enables the creation of values for XML-RPC, by encapsulating plain php values. 13 * 14 * @property Value[]|mixed $me deprecated - public access left in purely for BC. Access via scalarVal()/__construct() 15 * @property int $params $mytype - public access left in purely for BC. Access via kindOf()/__construct() 16 * @property string|null $_php_class deprecated - public access left in purely for BC. 17 */ 18 class Value implements \Countable, \IteratorAggregate, \ArrayAccess 19 { 20 use CharsetEncoderAware; 21 use DeprecationLogger; 22 23 public static $xmlrpcI4 = "i4"; 24 public static $xmlrpcI8 = "i8"; 25 public static $xmlrpcInt = "int"; 26 public static $xmlrpcBoolean = "boolean"; 27 public static $xmlrpcDouble = "double"; 28 public static $xmlrpcString = "string"; 29 public static $xmlrpcDateTime = "dateTime.iso8601"; 30 public static $xmlrpcBase64 = "base64"; 31 public static $xmlrpcArray = "array"; 32 public static $xmlrpcStruct = "struct"; 33 public static $xmlrpcValue = "undefined"; 34 public static $xmlrpcNull = "null"; 35 36 public static $xmlrpcTypes = array( 37 "i4" => 1, 38 "i8" => 1, 39 "int" => 1, 40 "boolean" => 1, 41 "double" => 1, 42 "string" => 1, 43 "dateTime.iso8601" => 1, 44 "base64" => 1, 45 "array" => 2, 46 "struct" => 3, 47 "null" => 1, 48 ); 49 50 /** @var Value[]|mixed */ 51 protected $me = array(); 52 /** 53 * @var int 0 for undef, 1 for scalar, 2 for array, 3 for struct 54 */ 55 protected $mytype = 0; 56 /** @var string|null */ 57 protected $_php_class = null; 58 59 /** 60 * Build an xml-rpc value. 61 * 62 * When no value or type is passed in, the value is left uninitialized, and the value can be added later. 63 * 64 * @param Value[]|mixed $val if passing in an array, all array elements should be PhpXmlRpc\Value themselves 65 * @param string $type any valid xml-rpc type name (lowercase): i4, int, boolean, string, double, dateTime.iso8601, 66 * base64, array, struct, null. 67 * If null, 'string' is assumed. 68 * You should refer to http://xmlrpc.com/spec.md for more information on what each of these mean. 69 */ 70 public function __construct($val = -1, $type = '') 71 { 72 // optimization creep - do not call addXX, do it all inline. 73 // downside: booleans will not be coerced anymore 74 if ($val !== -1 || $type != '') { 75 switch ($type) { 76 case '': 77 $this->mytype = 1; 78 $this->me['string'] = $val; 79 break; 80 case 'i4': 81 case 'i8': 82 case 'int': 83 case 'double': 84 case 'string': 85 case 'boolean': 86 case 'dateTime.iso8601': 87 case 'base64': 88 case 'null': 89 $this->mytype = 1; 90 $this->me[$type] = $val; 91 break; 92 case 'array': 93 $this->mytype = 2; 94 $this->me['array'] = $val; 95 break; 96 case 'struct': 97 $this->mytype = 3; 98 $this->me['struct'] = $val; 99 break; 100 default: 101 $this->getLogger()->error("XML-RPC: " . __METHOD__ . ": not a known type ($type)"); 102 } 103 } 104 } 105 106 /** 107 * Add a single php value to an xml-rpc value. 108 * 109 * If the xml-rpc value is an array, the php value is added as its last element. 110 * If the xml-rpc value is empty (uninitialized), this method makes it a scalar value, and sets that value. 111 * Fails if the xml-rpc value is not an array (i.e. a struct or a scalar) and already initialized. 112 * 113 * @param mixed $val 114 * @param string $type allowed values: i4, i8, int, boolean, string, double, dateTime.iso8601, base64, null. 115 * @return int 1 or 0 on failure 116 * 117 * @todo arguably, as we have addArray to add elements to an Array value, and addStruct to add elements to a Struct 118 * value, we should not allow this method to add values to an Array. The 'scalar' in the method name refers to 119 * the expected state of the target object, not to the type of $val. Also, this works differently from 120 * addScalar/addStruct in that, when adding an element to an array, it wraps it into a new Value 121 * @todo rename? 122 */ 123 public function addScalar($val, $type = 'string') 124 { 125 $typeOf = null; 126 if (isset(static::$xmlrpcTypes[$type])) { 127 $typeOf = static::$xmlrpcTypes[$type]; 128 } 129 130 if ($typeOf !== 1) { 131 $this->getLogger()->error("XML-RPC: " . __METHOD__ . ": not a scalar type ($type)"); 132 return 0; 133 } 134 135 // coerce booleans into correct values 136 /// @todo we should either do it for datetimes, integers, i8 and doubles, too, or just plain remove this check, 137 /// implemented on booleans only... 138 if ($type == static::$xmlrpcBoolean) { 139 if (strcasecmp($val, 'true') == 0 || $val == 1 || ($val == true && strcasecmp($val, 'false'))) { 140 $val = true; 141 } else { 142 $val = false; 143 } 144 } 145 146 switch ($this->mytype) { 147 case 1: 148 $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': scalar xmlrpc value can have only one value'); 149 return 0; 150 case 3: 151 $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': cannot add anonymous scalar to struct xmlrpc value'); 152 return 0; 153 case 2: 154 // we're adding a scalar value to an array here 155 /// @todo should we try avoiding re-wrapping Value objects? 156 $class = get_class($this); 157 $this->me['array'][] = new $class($val, $type); 158 159 return 1; 160 default: 161 // a scalar, so set the value and remember we're scalar 162 $this->me[$type] = $val; 163 $this->mytype = $typeOf; 164 165 return 1; 166 } 167 } 168 169 /** 170 * Add an array of xml-rpc value objects to an xml-rpc value. 171 * 172 * If the xml-rpc value is an array, the elements are appended to the existing ones. 173 * If the xml-rpc value is empty (uninitialized), this method makes it an array value, and sets that value. 174 * Fails otherwise. 175 * 176 * @param Value[] $values 177 * @return int 1 or 0 on failure 178 * 179 * @todo add some checking for $values to be an array of xml-rpc values? 180 * @todo rename to addToArray? 181 */ 182 public function addArray($values) 183 { 184 if ($this->mytype == 0) { 185 $this->mytype = static::$xmlrpcTypes['array']; 186 $this->me['array'] = $values; 187 188 return 1; 189 } elseif ($this->mytype == 2) { 190 // we're adding to an array here 191 $this->me['array'] = array_merge($this->me['array'], $values); 192 193 return 1; 194 } else { 195 $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': already initialized as a [' . $this->kindOf() . ']'); 196 return 0; 197 } 198 } 199 200 /** 201 * Merges an array of named xml-rpc value objects into an xml-rpc value. 202 * 203 * If the xml-rpc value is a struct, the elements are merged with the existing ones (overwriting existing ones). 204 * If the xml-rpc value is empty (uninitialized), this method makes it a struct value, and sets that value. 205 * Fails otherwise. 206 * 207 * @param Value[] $values 208 * @return int 1 or 0 on failure 209 * 210 * @todo add some checking for $values to be an array of xml-rpc values? 211 * @todo rename to addToStruct? 212 */ 213 public function addStruct($values) 214 { 215 if ($this->mytype == 0) { 216 $this->mytype = static::$xmlrpcTypes['struct']; 217 $this->me['struct'] = $values; 218 219 return 1; 220 } elseif ($this->mytype == 3) { 221 // we're adding to a struct here 222 $this->me['struct'] = array_merge($this->me['struct'], $values); 223 224 return 1; 225 } else { 226 $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': already initialized as a [' . $this->kindOf() . ']'); 227 return 0; 228 } 229 } 230 231 /** 232 * Returns a string describing the base type of the value. 233 * 234 * @return string either "struct", "array", "scalar" or "undef" 235 */ 236 public function kindOf() 237 { 238 switch ($this->mytype) { 239 case 3: 240 return 'struct'; 241 case 2: 242 return 'array'; 243 case 1: 244 return 'scalar'; 245 default: 246 return 'undef'; 247 } 248 } 249 250 251 /** 252 * Returns the value of a scalar xml-rpc value (base 64 decoding is automatically handled here) 253 * 254 * @return mixed 255 */ 256 public function scalarVal() 257 { 258 $b = reset($this->me); 259 260 return $b; 261 } 262 263 /** 264 * Returns the type of the xml-rpc value. 265 * 266 * @return string For integers, 'int' is always returned in place of 'i4'. 'i8' is considered a separate type and 267 * returned as such 268 */ 269 public function scalarTyp() 270 { 271 reset($this->me); 272 $a = key($this->me); 273 if ($a == static::$xmlrpcI4) { 274 $a = static::$xmlrpcInt; 275 } 276 277 return $a; 278 } 279 280 /** 281 * Returns the xml representation of the value. XML prologue not included. 282 * 283 * @param string $charsetEncoding the charset to be used for serialization. If null, US-ASCII is assumed 284 * @return string 285 */ 286 public function serialize($charsetEncoding = '') 287 { 288 $val = reset($this->me); 289 $typ = key($this->me); 290 291 return '<value>' . $this->serializeData($typ, $val, $charsetEncoding) . "</value>\n"; 292 } 293 294 /** 295 * @param string $typ 296 * @param Value[]|mixed $val 297 * @param string $charsetEncoding 298 * @return string 299 * 300 * @deprecated this should be folded back into serialize() 301 */ 302 protected function serializeData($typ, $val, $charsetEncoding = '') 303 { 304 $this->logDeprecationUnlessCalledBy('serialize'); 305 306 if (!isset(static::$xmlrpcTypes[$typ])) { 307 return ''; 308 } 309 310 switch (static::$xmlrpcTypes[$typ]) { 311 case 1: 312 switch ($typ) { 313 case static::$xmlrpcBase64: 314 $rs = "<{$typ}>" . base64_encode($val) . "</{$typ}>"; 315 break; 316 case static::$xmlrpcBoolean: 317 $rs = "<{$typ}>" . ($val ? '1' : '0') . "</{$typ}>"; 318 break; 319 case static::$xmlrpcString: 320 // Do NOT use htmlentities, since it will produce named html entities, which are invalid xml 321 $rs = "<{$typ}>" . $this->getCharsetEncoder()->encodeEntities($val, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</{$typ}>"; 322 break; 323 case static::$xmlrpcInt: 324 case static::$xmlrpcI4: 325 case static::$xmlrpcI8: 326 $rs = "<{$typ}>" . (int)$val . "</{$typ}>"; 327 break; 328 case static::$xmlrpcDouble: 329 // avoid using standard conversion of float to string because it is locale-dependent, 330 // and also because the xml-rpc spec forbids exponential notation. 331 // sprintf('%F') could be most likely ok, but it fails e.g. on 2e-14. 332 // The code below tries its best at keeping max precision while avoiding exp notation, 333 // but there is of course no limit in the number of decimal places to be used... 334 $rs = "<{$typ}>" . preg_replace('/\\.?0+$/', '', number_format((double)$val, PhpXmlRpc::$xmlpc_double_precision, '.', '')) . "</{$typ}>"; 335 break; 336 case static::$xmlrpcDateTime: 337 if (is_string($val)) { 338 $rs = "<{$typ}>{$val}</{$typ}>"; 339 // DateTimeInterface is not present in php 5.4... 340 } elseif (is_a($val, 'DateTimeInterface') || is_a($val, 'DateTime')) { 341 $rs = "<{$typ}>" . $val->format('Ymd\TH:i:s') . "</{$typ}>"; 342 } elseif (is_int($val)) { 343 $rs = "<{$typ}>" . date('Ymd\TH:i:s', $val) . "</{$typ}>"; 344 } else { 345 // not really a good idea here: but what should we output anyway? left for backward compat... 346 $rs = "<{$typ}>{$val}</{$typ}>"; 347 } 348 break; 349 case static::$xmlrpcNull: 350 if (PhpXmlRpc::$xmlrpc_null_apache_encoding) { 351 $rs = "<ex:nil/>"; 352 } else { 353 $rs = "<nil/>"; 354 } 355 break; 356 default: 357 // no standard type value should arrive here, but provide a possibility 358 // for xml-rpc values of unknown type... 359 $rs = "<{$typ}>{$val}</{$typ}>"; 360 } 361 break; 362 case 3: 363 // struct 364 if ($this->_php_class) { 365 $rs = '<struct php_class="' . $this->_php_class . "\">\n"; 366 } else { 367 $rs = "<struct>\n"; 368 } 369 $charsetEncoder = $this->getCharsetEncoder(); 370 /** @var Value $val2 */ 371 foreach ($val as $key2 => $val2) { 372 $rs .= '<member><name>' . $charsetEncoder->encodeEntities($key2, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</name>\n"; 373 $rs .= $val2->serialize($charsetEncoding); 374 $rs .= "</member>\n"; 375 } 376 $rs .= '</struct>'; 377 break; 378 case 2: 379 // array 380 $rs = "<array>\n<data>\n"; 381 /** @var Value $element */ 382 foreach ($val as $element) { 383 $rs .= $element->serialize($charsetEncoding); 384 } 385 $rs .= "</data>\n</array>"; 386 break; 387 default: 388 /// @todo log a warning? 389 $rs = ''; 390 break; 391 } 392 393 return $rs; 394 } 395 396 /** 397 * Returns the number of members in an xml-rpc value: 398 * - 0 for uninitialized values 399 * - 1 for scalar values 400 * - the number of elements for struct and array values 401 * 402 * @return integer 403 */ 404 #[\ReturnTypeWillChange] 405 public function count() 406 { 407 switch ($this->mytype) { 408 case 3: 409 return count($this->me['struct']); 410 case 2: 411 return count($this->me['array']); 412 case 1: 413 return 1; 414 default: 415 return 0; 416 } 417 } 418 419 /** 420 * Implements the IteratorAggregate interface 421 * @internal required to be public to implement an Interface 422 * 423 * @return \ArrayIterator 424 */ 425 #[\ReturnTypeWillChange] 426 public function getIterator() 427 { 428 switch ($this->mytype) { 429 case 3: 430 return new \ArrayIterator($this->me['struct']); 431 case 2: 432 return new \ArrayIterator($this->me['array']); 433 case 1: 434 return new \ArrayIterator($this->me); 435 default: 436 return new \ArrayIterator(); 437 } 438 } 439 440 /** 441 * @internal required to be public to implement an Interface 442 * 443 * @param mixed $offset 444 * @param mixed $value 445 * @return void 446 * 447 * @throws ValueErrorException|TypeErrorException 448 */ 449 #[\ReturnTypeWillChange] 450 public function offsetSet($offset, $value) 451 { 452 switch ($this->mytype) { 453 case 3: 454 if (!($value instanceof Value)) { 455 throw new TypeErrorException('It is only possible to add Value objects to an XML-RPC Struct'); 456 } 457 if (is_null($offset)) { 458 // disallow struct members with empty names 459 throw new ValueErrorException('It is not possible to add anonymous members to an XML-RPC Struct'); 460 } else { 461 $this->me['struct'][$offset] = $value; 462 } 463 return; 464 case 2: 465 if (!($value instanceof Value)) { 466 throw new TypeErrorException('It is only possible to add Value objects to an XML-RPC Array'); 467 } 468 if (is_null($offset)) { 469 $this->me['array'][] = $value; 470 } else { 471 // nb: we are not checking that $offset is above the existing array range... 472 $this->me['array'][$offset] = $value; 473 } 474 return; 475 case 1: 476 /// @todo: should we handle usage of i4 to retrieve int (in both set/unset/isset)? After all we consider 477 /// 'int' to be the preferred form, as evidenced in scalarTyp() 478 reset($this->me); 479 $type = key($this->me); 480 if ($type != $offset && ($type != 'i4' || $offset != 'int')) { 481 throw new ValueErrorException('...'); 482 } 483 $this->me[$type] = $value; 484 return; 485 default: 486 // it would be nice to allow empty values to be turned into non-empty ones this way, but we miss info to do so 487 throw new ValueErrorException("XML-RPC Value is of type 'undef' and its value can not be set using array index"); 488 } 489 } 490 491 /** 492 * @internal required to be public to implement an Interface 493 * 494 * @param mixed $offset 495 * @return bool 496 */ 497 #[\ReturnTypeWillChange] 498 public function offsetExists($offset) 499 { 500 switch ($this->mytype) { 501 case 3: 502 return isset($this->me['struct'][$offset]); 503 case 2: 504 return isset($this->me['array'][$offset]); 505 case 1: 506 // handle i4 vs int 507 if ($offset == 'i4') { 508 // to be consistent with set and unset, we disallow usage of i4 to check for int 509 reset($this->me); 510 return $offset == key($this->me); 511 } else { 512 return $offset == $this->scalarTyp(); 513 } 514 default: 515 return false; 516 } 517 } 518 519 /** 520 * @internal required to be public to implement an Interface 521 * 522 * @param mixed $offset 523 * @return void 524 * 525 * @throws ValueErrorException|StateErrorException 526 */ 527 #[\ReturnTypeWillChange] 528 public function offsetUnset($offset) 529 { 530 switch ($this->mytype) { 531 case 3: 532 unset($this->me['struct'][$offset]); 533 return; 534 case 2: 535 unset($this->me['array'][$offset]); 536 return; 537 case 1: 538 // can not remove value from a scalar 539 /// @todo feature creep - allow this to move back the value to 'undef' state? 540 throw new StateErrorException("XML-RPC Value is of type 'scalar' and its value can not be unset using array index"); 541 default: 542 throw new StateErrorException("XML-RPC Value is of type 'undef' and its value can not be unset using array index"); 543 } 544 } 545 546 /** 547 * @internal required to be public to implement an Interface 548 * 549 * @param mixed $offset 550 * @return mixed|Value|null 551 * @throws StateErrorException 552 */ 553 #[\ReturnTypeWillChange] 554 public function offsetGet($offset) 555 { 556 switch ($this->mytype) { 557 case 3: 558 return isset($this->me['struct'][$offset]) ? $this->me['struct'][$offset] : null; 559 case 2: 560 return isset($this->me['array'][$offset]) ? $this->me['array'][$offset] : null; 561 case 1: 562 /// @todo what to return on bad type: null or exception? 563 $value = reset($this->me); 564 $type = key($this->me); 565 return $type == $offset ? $value : (($type == 'i4' && $offset == 'int') ? $value : null); 566 default: 567 // return null or exception? 568 throw new StateErrorException("XML-RPC Value is of type 'undef' and can not be accessed using array index"); 569 } 570 } 571 572 // *** BC layer *** 573 574 /** 575 * Checks whether a struct member with a given name is present. 576 * 577 * Works only on xml-rpc values of type struct. 578 * 579 * @param string $key the name of the struct member to be looked up 580 * @return boolean 581 * 582 * @deprecated use array access, e.g. isset($val[$key]) 583 */ 584 public function structMemExists($key) 585 { 586 $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated'); 587 588 return array_key_exists($key, $this->me['struct']); 589 } 590 591 /** 592 * Returns the value of a given struct member (an xml-rpc value object in itself). 593 * Will raise a php warning if struct member of given name does not exist. 594 * 595 * @param string $key the name of the struct member to be looked up 596 * @return Value 597 * 598 * @deprecated use array access, e.g. $val[$key] 599 */ 600 public function structMem($key) 601 { 602 $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated'); 603 604 return $this->me['struct'][$key]; 605 } 606 607 /** 608 * Reset internal pointer for xml-rpc values of type struct. 609 * @return void 610 * 611 * @deprecated iterate directly over the object using foreach instead 612 */ 613 public function structReset() 614 { 615 $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated'); 616 617 reset($this->me['struct']); 618 } 619 620 /** 621 * Return next member element for xml-rpc values of type struct. 622 * 623 * @return array having the same format as PHP's `each` method 624 * 625 * @deprecated iterate directly over the object using foreach instead 626 */ 627 public function structEach() 628 { 629 $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated'); 630 631 $key = key($this->me['struct']); 632 $value = current($this->me['struct']); 633 next($this->me['struct']); 634 return array(1 => $value, 'value' => $value, 0 => $key, 'key' => $key); 635 } 636 637 /** 638 * Returns the n-th member of an xml-rpc value of array type. 639 * 640 * @param integer $key the index of the value to be retrieved (zero based) 641 * 642 * @return Value 643 * 644 * @deprecated use array access, e.g. $val[$key] 645 */ 646 public function arrayMem($key) 647 { 648 $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated'); 649 650 return $this->me['array'][$key]; 651 } 652 653 /** 654 * Returns the number of members in an xml-rpc value of array type. 655 * 656 * @return integer 657 * 658 * @deprecated use count() instead 659 */ 660 public function arraySize() 661 { 662 $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated'); 663 664 return count($this->me['array']); 665 } 666 667 /** 668 * Returns the number of members in an xml-rpc value of struct type. 669 * 670 * @return integer 671 * 672 * @deprecated use count() instead 673 */ 674 public function structSize() 675 { 676 $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated'); 677 678 return count($this->me['struct']); 679 } 680 681 // we have to make this return by ref in order to allow calls such as `$resp->_cookies['name'] = ['value' => 'something'];` 682 public function &__get($name) 683 { 684 switch ($name) { 685 case 'me': 686 case 'mytype': 687 case '_php_class': 688 $this->logDeprecation('Getting property Value::' . $name . ' is deprecated'); 689 return $this->$name; 690 default: 691 /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout... 692 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1); 693 trigger_error('Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING); 694 $result = null; 695 return $result; 696 } 697 } 698 699 public function __set($name, $value) 700 { 701 switch ($name) { 702 case 'me': 703 case 'mytype': 704 case '_php_class': 705 $this->logDeprecation('Setting property Value::' . $name . ' is deprecated'); 706 $this->$name = $value; 707 break; 708 default: 709 /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout... 710 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1); 711 trigger_error('Undefined property via __set(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING); 712 } 713 } 714 715 public function __isset($name) 716 { 717 switch ($name) { 718 case 'me': 719 case 'mytype': 720 case '_php_class': 721 $this->logDeprecation('Checking property Value::' . $name . ' is deprecated'); 722 return isset($this->$name); 723 default: 724 return false; 725 } 726 } 727 728 public function __unset($name) 729 { 730 switch ($name) { 731 case 'me': 732 case 'mytype': 733 case '_php_class': 734 $this->logDeprecation('Unsetting property Value::' . $name . ' is deprecated'); 735 unset($this->$name); 736 break; 737 default: 738 /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout... 739 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1); 740 trigger_error('Undefined property via __unset(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING); 741 } 742 } 743 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body