Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.
   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  
  18  /**
  19   * XML-RPC web service implementation classes and methods.
  20   *
  21   * @package    webservice_xmlrpc
  22   * @copyright  2009 Petr Skodak
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  require_once("$CFG->dirroot/webservice/lib.php");
  27  
  28  /**
  29   * XML-RPC service server implementation.
  30   *
  31   * @package    webservice_xmlrpc
  32   * @copyright  2009 Petr Skodak
  33   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  34   * @since Moodle 2.0
  35   */
  36  class webservice_xmlrpc_server extends webservice_base_server {
  37  
  38      /** @var string $response The XML-RPC response string. */
  39      private $response;
  40  
  41      /**
  42       * Contructor
  43       *
  44       * @param string $authmethod authentication method of the web service (WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN, ...)
  45       */
  46      public function __construct($authmethod) {
  47          parent::__construct($authmethod);
  48          $this->wsname = 'xmlrpc';
  49      }
  50  
  51      /**
  52       * This method parses the request input, it needs to get:
  53       *  1/ user authentication - username+password or token
  54       *  2/ function name
  55       *  3/ function parameters
  56       */
  57      protected function parse_request() {
  58          // Retrieve and clean the POST/GET parameters from the parameters specific to the server.
  59          parent::set_web_service_call_settings();
  60  
  61          if ($this->authmethod == WEBSERVICE_AUTHMETHOD_USERNAME) {
  62              $this->username = isset($_GET['wsusername']) ? $_GET['wsusername'] : null;
  63              $this->password = isset($_GET['wspassword']) ? $_GET['wspassword'] : null;
  64          } else {
  65              $this->token = isset($_GET['wstoken']) ? $_GET['wstoken'] : null;
  66          }
  67  
  68          // Get the XML-RPC request data.
  69          $rawpostdata = $this->fetch_input_content();
  70          $methodname = null;
  71  
  72          // Decode the request to get the decoded parameters and the name of the method to be called.
  73          $decodedparams = xmlrpc_decode_request($rawpostdata, $methodname, 'UTF-8');
  74          $methodinfo = external_api::external_function_info($methodname);
  75          $methodparams = array_keys($methodinfo->parameters_desc->keys);
  76          $methodvariables = [];
  77  
  78          // Add the decoded parameters to the methodvariables array.
  79          if (is_array($decodedparams)) {
  80              foreach ($decodedparams as $index => $param) {
  81                  // See MDL-53962 - XML-RPC requests will usually be sent as an array (as in, one with indicies).
  82                  // We need to use a bit of "magic" to add the correct index back. Zend used to do this for us.
  83                  $methodvariables[$methodparams[$index]] = $param;
  84              }
  85          }
  86  
  87          $this->functionname = $methodname;
  88          $this->parameters = $methodvariables;
  89      }
  90  
  91      /**
  92       * Fetch content from the client.
  93       *
  94       * @return string
  95       */
  96      protected function fetch_input_content() {
  97          return file_get_contents('php://input');
  98      }
  99  
 100      /**
 101       * Prepares the response.
 102       */
 103      protected function prepare_response() {
 104          try {
 105              if (!empty($this->function->returns_desc)) {
 106                  $validatedvalues = external_api::clean_returnvalue($this->function->returns_desc, $this->returns);
 107                  $encodingoptions = array(
 108                      "encoding" => "UTF-8",
 109                      "verbosity" => "no_white_space",
 110                      // See MDL-54868.
 111                      "escaping" => ["markup"]
 112                  );
 113                  // We can now convert the response to the requested XML-RPC format.
 114                  $this->response = xmlrpc_encode_request(null, $validatedvalues, $encodingoptions);
 115              }
 116          } catch (invalid_response_exception $ex) {
 117              $this->response = $this->generate_error($ex);
 118          }
 119      }
 120  
 121      /**
 122       * Send the result of function call to the WS client.
 123       */
 124      protected function send_response() {
 125          $this->prepare_response();
 126          $this->send_headers();
 127          echo $this->response;
 128      }
 129  
 130      /**
 131       * Send the error information to the WS client.
 132       *
 133       * @param Exception $ex
 134       */
 135      protected function send_error($ex = null) {
 136          $this->response = $this->generate_error($ex);
 137          $this->send_headers();
 138          echo $this->response;
 139      }
 140  
 141      /**
 142       * Sends the headers for the XML-RPC response.
 143       */
 144      protected function send_headers() {
 145          // Standard headers.
 146          header('HTTP/1.1 200 OK');
 147          header('Connection: close');
 148          header('Content-Length: ' . strlen($this->response));
 149          header('Content-Type: text/xml; charset=utf-8');
 150          header('Date: ' . gmdate('D, d M Y H:i:s', 0) . ' GMT');
 151          header('Server: Moodle XML-RPC Server/1.0');
 152          // Other headers.
 153          header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
 154          header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
 155          header('Pragma: no-cache');
 156          header('Accept-Ranges: none');
 157          // Allow cross-origin requests only for Web Services.
 158          // This allow to receive requests done by Web Workers or webapps in different domains.
 159          header('Access-Control-Allow-Origin: *');
 160      }
 161  
 162      /**
 163       * Generate the XML-RPC fault response.
 164       *
 165       * @param Exception|Throwable $ex The exception.
 166       * @param int $faultcode The faultCode to be included in the fault response
 167       * @return string The XML-RPC fault response xml containing the faultCode and faultString.
 168       */
 169      protected function generate_error($ex, $faultcode = 404) {
 170          $error = $ex->getMessage();
 171  
 172          if (!empty($ex->errorcode)) {
 173              // The faultCode must be an int, so we obtain a hash of the errorcode then get an integer value of the hash.
 174              $faultcode = base_convert(md5($ex->errorcode), 16, 10);
 175  
 176              // We strip the $code to 8 digits (and hope for no error code collisions).
 177              // Collisions should be pretty rare, and if needed the client can retrieve
 178              // the accurate errorcode from the last | in the exception message.
 179              $faultcode = substr($faultcode, 0, 8);
 180  
 181              // Add the debuginfo to the exception message if debuginfo must be returned.
 182              if (debugging() and isset($ex->debuginfo)) {
 183                  $error .= ' | DEBUG INFO: ' . $ex->debuginfo . ' | ERRORCODE: ' . $ex->errorcode;
 184              } else {
 185                  $error .= ' | ERRORCODE: ' . $ex->errorcode;
 186              }
 187          }
 188  
 189          $fault = array(
 190              'faultCode' => (int) $faultcode,
 191              'faultString' => $error
 192          );
 193  
 194          $encodingoptions = array(
 195              "encoding" => "UTF-8",
 196              "verbosity" => "no_white_space",
 197              // See MDL-54868.
 198              "escaping" => ["markup"]
 199          );
 200  
 201          return xmlrpc_encode_request(null, $fault, $encodingoptions);
 202      }
 203  }
 204  
 205  /**
 206   * XML-RPC test client class
 207   *
 208   * @package    webservice_xmlrpc
 209   * @copyright  2009 Petr Skodak
 210   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 211   * @since Moodle 2.0
 212   */
 213  class webservice_xmlrpc_test_client implements webservice_test_client_interface {
 214      /**
 215       * Execute test client WS request
 216       * @param string $serverurl server url (including token parameter or username/password parameters)
 217       * @param string $function function name
 218       * @param array $params parameters of the called function
 219       * @return mixed
 220       */
 221      public function simpletest($serverurl, $function, $params) {
 222          global $CFG;
 223  
 224          $url = new moodle_url($serverurl);
 225          $token = $url->get_param('wstoken');
 226          require_once($CFG->dirroot . '/webservice/xmlrpc/lib.php');
 227          $client = new webservice_xmlrpc_client($serverurl, $token);
 228          return $client->call($function, $params);
 229      }
 230  }