Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 401 and 402]

   1  <?php
   2  
   3  namespace PhpXmlRpc;
   4  
   5  use PhpXmlRpc\Exception\StateErrorException;
   6  use PhpXmlRpc\Traits\CharsetEncoderAware;
   7  use PhpXmlRpc\Traits\DeprecationLogger;
   8  use PhpXmlRpc\Traits\PayloadBearer;
   9  
  10  /**
  11   * This class provides the representation of the response of an XML-RPC server.
  12   * Server-side, a server method handler will construct a Response and pass it as its return value.
  13   * An identical Response object will be returned by the result of an invocation of the send() method of the Client class.
  14   *
  15   * @property Value|string|mixed $val deprecated - public access left in purely for BC. Access via value()/__construct()
  16   * @property string $valtyp deprecated - public access left in purely for BC. Access via valueType()/__construct()
  17   * @property int $errno deprecated - public access left in purely for BC. Access via faultCode()/__construct()
  18   * @property string $errstr deprecated - public access left in purely for BC. Access faultString()/__construct()
  19   * @property string $payload deprecated - public access left in purely for BC. Access via getPayload()/setPayload()
  20   * @property string $content_type deprecated - public access left in purely for BC. Access via getContentType()/setPayload()
  21   * @property array $hdrs deprecated. Access via httpResponse()['headers'], set via $httpResponse['headers']
  22   * @property array _cookies deprecated. Access via httpResponse()['cookies'], set via $httpResponse['cookies']
  23   * @property string $raw_data deprecated. Access via httpResponse()['raw_data'], set via $httpResponse['raw_data']
  24   */
  25  class Response
  26  {
  27      use CharsetEncoderAware;
  28      use DeprecationLogger;
  29      use PayloadBearer;
  30  
  31      /** @var Value|string|mixed */
  32      protected $val = 0;
  33      /** @var string */
  34      protected $valtyp;
  35      /** @var int */
  36      protected $errno = 0;
  37      /** @var string */
  38      protected $errstr = '';
  39  
  40      protected $httpResponse = array('headers' => array(), 'cookies' => array(), 'raw_data' => '', 'status_code' => null);
  41  
  42      /**
  43       * @param Value|string|mixed $val either a Value object, a php value or the xml serialization of an xml-rpc value (a string).
  44       *                                Note that using anything other than a Value object wll have an impact on serialization.
  45       * @param integer $fCode set it to anything but 0 to create an error response. In that case, $val is discarded
  46       * @param string $fString the error string, in case of an error response
  47       * @param string $valType The type of $val passed in. Either 'xmlrpcvals', 'phpvals' or 'xml'. Leave empty to let
  48       *                        the code guess the correct type by looking at $val - in which case strings are assumed
  49       *                        to be serialized xml
  50       * @param array|null $httpResponse this should be set when the response is being built out of data received from
  51       *                                 http (i.e. not when programmatically building a Response server-side). Array
  52       *                                 keys should include, if known: headers, cookies, raw_data, status_code
  53       *
  54       * @todo add check that $val / $fCode / $fString is of correct type? We could at least log a warning for fishy cases...
  55       *       NB: as of now we do not do it, since it might be either an xml-rpc value or a plain php val, or a complete
  56       *       xml chunk, depending on usage of Client::send() inside which the constructor is called.
  57       */
  58      public function __construct($val, $fCode = 0, $fString = '', $valType = '', $httpResponse = null)
  59      {
  60          if ($fCode != 0) {
  61              // error response
  62              $this->errno = $fCode;
  63              $this->errstr = $fString;
  64          } else {
  65              // successful response
  66              $this->val = $val;
  67              if ($valType == '') {
  68                  // user did not declare type of response value: try to guess it
  69                  if (is_object($this->val) && is_a($this->val, 'PhpXmlRpc\Value')) {
  70                      $this->valtyp = 'xmlrpcvals';
  71                  } elseif (is_string($this->val)) {
  72                      $this->valtyp = 'xml';
  73                  } else {
  74                      $this->valtyp = 'phpvals';
  75                  }
  76              } else {
  77                  $this->valtyp = $valType;
  78                  // user declares the type of resp value: we "almost" trust it... but log errors just in case
  79                  if (($this->valtyp == 'xmlrpcvals' && (!is_a($this->val, 'PhpXmlRpc\Value'))) ||
  80                      ($this->valtyp == 'xml' && (!is_string($this->val)))) {
  81                      $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': value passed in does not match type ' . $valType);
  82                  }
  83              }
  84          }
  85  
  86          if (is_array($httpResponse)) {
  87              $this->httpResponse = array_merge(array('headers' => array(), 'cookies' => array(), 'raw_data' => '', 'status_code' => null), $httpResponse);
  88          }
  89      }
  90  
  91      /**
  92       * Returns the error code of the response.
  93       *
  94       * @return integer the error code of this response (0 for not-error responses)
  95       */
  96      public function faultCode()
  97      {
  98          return $this->errno;
  99      }
 100  
 101      /**
 102       * Returns the error code of the response.
 103       *
 104       * @return string the error string of this response ('' for not-error responses)
 105       */
 106      public function faultString()
 107      {
 108          return $this->errstr;
 109      }
 110  
 111      /**
 112       * Returns the value received by the server. If the Response's faultCode is non-zero then the value returned by this
 113       * method should not be used (it may not even be an object).
 114       *
 115       * @return Value|string|mixed the Value object returned by the server. Might be an xml string or plain php value
 116       *                            depending on the convention adopted when creating the Response
 117       */
 118      public function value()
 119      {
 120          return $this->val;
 121      }
 122  
 123      /**
 124       * @return string
 125       */
 126      public function valueType()
 127      {
 128          return $this->valtyp;
 129      }
 130  
 131      /**
 132       * Returns an array with the cookies received from the server.
 133       * Array has the form: $cookiename => array ('value' => $val, $attr1 => $val1, $attr2 => $val2, ...)
 134       * with attributes being e.g. 'expires', 'path', domain'.
 135       * NB: cookies sent as 'expired' by the server (i.e. with an expiry date in the past) are still present in the array.
 136       * It is up to the user-defined code to decide how to use the received cookies, and whether they have to be sent back
 137       * with the next request to the server (using $client->setCookie) or not.
 138       * The values are filled in at constructor time, and might not be set for specific debug values used.
 139       *
 140       * @return array[] array of cookies received from the server
 141       */
 142      public function cookies()
 143      {
 144          return $this->httpResponse['cookies'];
 145      }
 146  
 147      /**
 148       * Returns an array with info about the http response received from the server.
 149       * The values are filled in at constructor time, and might not be set for specific debug values used.
 150       *
 151       * @return array array with keys 'headers', 'cookies', 'raw_data' and 'status_code'.
 152       */
 153      public function httpResponse()
 154      {
 155          return $this->httpResponse;
 156      }
 157  
 158      /**
 159       * Returns xml representation of the response, XML prologue _not_ included. Sets `payload` and `content_type` properties
 160       *
 161       * @param string $charsetEncoding the charset to be used for serialization. If null, US-ASCII is assumed
 162       * @return string the xml representation of the response
 163       * @throws StateErrorException if the response was built out of a value of an unsupported type
 164       */
 165      public function serialize($charsetEncoding = '')
 166      {
 167          if ($charsetEncoding != '') {
 168              $this->content_type = 'text/xml; charset=' . $charsetEncoding;
 169          } else {
 170              $this->content_type = 'text/xml';
 171          }
 172  
 173          if (PhpXmlRpc::$xmlrpc_null_apache_encoding) {
 174              $result = "<methodResponse xmlns:ex=\"" . PhpXmlRpc::$xmlrpc_null_apache_encoding_ns . "\">\n";
 175          } else {
 176              $result = "<methodResponse>\n";
 177          }
 178          if ($this->errno) {
 179              // Let non-ASCII response messages be tolerated by clients by xml-encoding non ascii chars
 180              $result .= "<fault>\n" .
 181                  "<value>\n<struct><member><name>faultCode</name>\n<value><int>" . $this->errno .
 182                  "</int></value>\n</member>\n<member>\n<name>faultString</name>\n<value><string>" .
 183                  $this->getCharsetEncoder()->encodeEntities($this->errstr, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) .
 184                  "</string></value>\n</member>\n</struct>\n</value>\n</fault>";
 185          } else {
 186              if (is_object($this->val) && is_a($this->val, 'PhpXmlRpc\Value')) {
 187                  $result .= "<params>\n<param>\n" . $this->val->serialize($charsetEncoding) . "</param>\n</params>";
 188              } else if (is_string($this->val) && $this->valtyp == 'xml') {
 189                  $result .= "<params>\n<param>\n" .
 190                      $this->val .
 191                      "</param>\n</params>";
 192              } else if ($this->valtyp == 'phpvals') {
 193                      $encoder = new Encoder();
 194                      $val = $encoder->encode($this->val);
 195                      $result .= "<params>\n<param>\n" . $val->serialize($charsetEncoding) . "</param>\n</params>";
 196              } else {
 197                  throw new StateErrorException('cannot serialize xmlrpc response objects whose content is native php values');
 198              }
 199          }
 200          $result .= "\n</methodResponse>";
 201  
 202          $this->payload = $result;
 203  
 204          return $result;
 205      }
 206  
 207      /**
 208       * @param string $charsetEncoding
 209       * @return string
 210       */
 211      public function xml_header($charsetEncoding = '')
 212      {
 213          if ($charsetEncoding != '') {
 214              return "<?xml version=\"1.0\" encoding=\"$charsetEncoding\"?" . ">\n";
 215          } else {
 216              return "<?xml version=\"1.0\"?" . ">\n";
 217          }
 218      }
 219  
 220      // *** BC layer ***
 221  
 222      // we have to make this return by ref in order to allow calls such as `$resp->_cookies['name'] = ['value' => 'something'];`
 223      public function &__get($name)
 224      {
 225          switch ($name) {
 226              case 'val':
 227              case 'valtyp':
 228              case 'errno':
 229              case 'errstr':
 230              case 'payload':
 231              case 'content_type':
 232                  $this->logDeprecation('Getting property Response::' . $name . ' is deprecated');
 233                  return $this->$name;
 234              case 'hdrs':
 235                  $this->logDeprecation('Getting property Response::' . $name . ' is deprecated');
 236                  return $this->httpResponse['headers'];
 237              case '_cookies':
 238                  $this->logDeprecation('Getting property Response::' . $name . ' is deprecated');
 239                  return $this->httpResponse['cookies'];
 240              case 'raw_data':
 241                  $this->logDeprecation('Getting property Response::' . $name . ' is deprecated');
 242                  return $this->httpResponse['raw_data'];
 243              default:
 244                  /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
 245                  $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
 246                  trigger_error('Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
 247                  $result = null;
 248                  return $result;
 249          }
 250      }
 251  
 252      public function __set($name, $value)
 253      {
 254          switch ($name) {
 255              case 'val':
 256              case 'valtyp':
 257              case 'errno':
 258              case 'errstr':
 259              case 'payload':
 260              case 'content_type':
 261                  $this->logDeprecation('Setting property Response::' . $name . ' is deprecated');
 262                  $this->$name = $value;
 263                  break;
 264              case 'hdrs':
 265                  $this->logDeprecation('Setting property Response::' . $name . ' is deprecated');
 266                  $this->httpResponse['headers'] = $value;
 267                  break;
 268              case '_cookies':
 269                  $this->logDeprecation('Setting property Response::' . $name . ' is deprecated');
 270                  $this->httpResponse['cookies'] = $value;
 271                  break;
 272              case 'raw_data':
 273                  $this->logDeprecation('Setting property Response::' . $name . ' is deprecated');
 274                  $this->httpResponse['raw_data'] = $value;
 275                  break;
 276              default:
 277                  /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
 278                  $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
 279                  trigger_error('Undefined property via __set(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
 280          }
 281      }
 282  
 283      public function __isset($name)
 284      {
 285          switch ($name) {
 286              case 'val':
 287              case 'valtyp':
 288              case 'errno':
 289              case 'errstr':
 290              case 'payload':
 291              case 'content_type':
 292                  $this->logDeprecation('Checking property Response::' . $name . ' is deprecated');
 293                  return isset($this->$name);
 294              case 'hdrs':
 295                  $this->logDeprecation('Checking property Response::' . $name . ' is deprecated');
 296                  return isset($this->httpResponse['headers']);
 297              case '_cookies':
 298                  $this->logDeprecation('Checking property Response::' . $name . ' is deprecated');
 299                  return isset($this->httpResponse['cookies']);
 300              case 'raw_data':
 301                  $this->logDeprecation('Checking property Response::' . $name . ' is deprecated');
 302                  return isset($this->httpResponse['raw_data']);
 303              default:
 304                  return false;
 305          }
 306      }
 307  
 308      public function __unset($name)
 309      {
 310          switch ($name) {
 311              case 'val':
 312              case 'valtyp':
 313              case 'errno':
 314              case 'errstr':
 315              case 'payload':
 316              case 'content_type':
 317                  $this->logDeprecation('Setting property Response::' . $name . ' is deprecated');
 318                  unset($this->$name);
 319                  break;
 320              case 'hdrs':
 321                  $this->logDeprecation('Unsetting property Response::' . $name . ' is deprecated');
 322                  unset($this->httpResponse['headers']);
 323                  break;
 324              case '_cookies':
 325                  $this->logDeprecation('Unsetting property Response::' . $name . ' is deprecated');
 326                  unset($this->httpResponse['cookies']);
 327                  break;
 328              case 'raw_data':
 329                  $this->logDeprecation('Unsetting property Response::' . $name . ' is deprecated');
 330                  unset($this->httpResponse['raw_data']);
 331                  break;
 332              default:
 333                  /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
 334                  $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
 335                  trigger_error('Undefined property via __unset(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
 336          }
 337      }
 338  }