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