2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
19 * SOAP web service implementation classes and methods.
21 * @package webservice_soap
22 * @copyright 2009 Petr Skodak
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 require_once($CFG->dirroot
. '/webservice/lib.php');
27 use webservice_soap\wsdl
;
30 * SOAP service server implementation.
32 * @package webservice_soap
33 * @copyright 2009 Petr Skodak
34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37 class webservice_soap_server
extends webservice_base_server
{
39 /** @var moodle_url The server URL. */
42 /** @var SoapServer The Soap */
43 protected $soapserver;
45 /** @var string The response. */
48 /** @var string The class name of the virtual class generated for this web service. */
49 protected $serviceclass;
51 /** @var bool WSDL mode flag. */
54 /** @var \webservice_soap\wsdl The object for WSDL generation. */
60 * @param string $authmethod authentication method of the web service (WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN, ...)
62 public function __construct($authmethod) {
63 parent
::__construct($authmethod);
64 // Must not cache wsdl - the list of functions is created on the fly.
65 ini_set('soap.wsdl_cache_enabled', '0');
66 $this->wsname
= 'soap';
67 $this->wsdlmode
= false;
71 * This method parses the $_POST and $_GET superglobals and looks for the following information:
72 * - User authentication parameters:
73 * - Username + password (wsusername and wspassword), or
76 protected function parse_request() {
77 // Retrieve and clean the POST/GET parameters from the parameters specific to the server.
78 parent
::set_web_service_call_settings();
80 if ($this->authmethod
== WEBSERVICE_AUTHMETHOD_USERNAME
) {
81 $this->username
= optional_param('wsusername', null, PARAM_RAW
);
82 $this->password
= optional_param('wspassword', null, PARAM_RAW
);
84 if (!$this->username
or !$this->password
) {
85 // Workaround for the trouble with & in soap urls.
86 $authdata = get_file_argument();
87 $authdata = explode('/', trim($authdata, '/'));
88 if (count($authdata) == 2) {
89 list($this->username
, $this->password
) = $authdata;
92 $this->serverurl
= new moodle_url('/webservice/soap/simpleserver.php/' . $this->username
. '/' . $this->password
);
94 $this->token
= optional_param('wstoken', null, PARAM_RAW
);
96 $this->serverurl
= new moodle_url('/webservice/soap/server.php');
97 $this->serverurl
->param('wstoken', $this->token
);
100 if ($wsdl = optional_param('wsdl', 0, PARAM_INT
)) {
101 $this->wsdlmode
= true;
106 * Runs the SOAP web service.
108 * @throws coding_exception
109 * @throws moodle_exception
110 * @throws webservice_access_exception
112 public function run() {
113 // We will probably need a lot of memory in some functions.
114 raise_memory_limit(MEMORY_EXTRA
);
116 // Set some longer timeout since operations may need longer time to finish.
117 external_api
::set_timeout();
119 // Set up exception handler.
120 set_exception_handler(array($this, 'exception_handler'));
122 // Init all properties from the request data.
123 $this->parse_request();
125 // Authenticate user, this has to be done after the request parsing. This also sets up $USER and $SESSION.
126 $this->authenticate_user();
128 // Make a list of all functions user is allowed to execute.
129 $this->init_service_class();
131 if ($this->wsdlmode
) {
132 // Generate the WSDL.
133 $this->generate_wsdl();
136 // Log the web service request.
139 'function' => 'unknown'
142 $event = \core\event\webservice_function_called
::create($params);
143 $logdataparams = array(SITEID
, 'webservice_soap', '', '', $this->serviceclass
. ' ' . getremoteaddr(), 0, $this->userid
);
144 $event->set_legacy_logdata($logdataparams);
147 // Handle the SOAP request.
151 $this->session_cleanup();
156 * Generates the WSDL.
158 protected function generate_wsdl() {
160 $this->wsdl
= new wsdl($this->serviceclass
, $this->serverurl
);
161 // Register service struct classes as complex types.
162 foreach ($this->servicestructs
as $structinfo) {
163 $this->wsdl
->add_complex_type($structinfo->classname
, $structinfo->properties
);
165 // Register the method for the WSDL generation.
166 foreach ($this->servicemethods
as $methodinfo) {
167 $this->wsdl
->register($methodinfo->name
, $methodinfo->inputparams
, $methodinfo->outputparams
, $methodinfo->description
);
172 * Handles the web service function call.
174 protected function handle() {
175 if ($this->wsdlmode
) {
176 // Prepare the response.
177 $this->response
= $this->wsdl
->to_xml();
179 // Send the results back in correct format.
180 $this->send_response();
182 $wsdlurl = clone($this->serverurl
);
183 $wsdlurl->param('wsdl', 1);
186 'uri' => $this->serverurl
->out(false)
188 // Initialise the SOAP server.
189 $this->soapserver
= new SoapServer($wsdlurl->out(false), $options);
190 if (!empty($this->serviceclass
)) {
191 $this->soapserver
->setClass($this->serviceclass
);
192 // Get all the methods for the generated service class then register to the SOAP server.
193 $functions = get_class_methods($this->serviceclass
);
194 $this->soapserver
->addFunction($functions);
197 // Get soap request from raw POST data.
198 $soaprequest = file_get_contents('php://input');
199 // Handle the request.
201 $this->soapserver
->handle($soaprequest);
202 } catch (Exception
$e) {
209 * Send the error information to the WS client formatted as an XML document.
211 * @param Exception $ex the exception to send back
213 protected function send_error($ex = null) {
215 $info = $ex->getMessage();
216 if (debugging() and isset($ex->debuginfo
)) {
217 $info .= ' - '.$ex->debuginfo
;
220 $info = 'Unknown error';
223 // Initialise new DOM document object.
224 $dom = new DOMDocument('1.0', 'UTF-8');
227 $fault = $dom->createElement('SOAP-ENV:Fault');
229 $fault->appendChild($dom->createElement('faultcode', 'MOODLE:error'));
231 $fault->appendChild($dom->createElement('faultstring', $info));
234 $body = $dom->createElement('SOAP-ENV:Body');
235 $body->appendChild($fault);
238 $envelope = $dom->createElement('SOAP-ENV:Envelope');
239 $envelope->setAttribute('xmlns:SOAP-ENV', 'http://schemas.xmlsoap.org/soap/envelope/');
240 $envelope->appendChild($body);
241 $dom->appendChild($envelope);
243 $this->response
= $dom->saveXML();
244 $this->send_response();
248 * Send the result of function call to the WS client.
250 protected function send_response() {
251 $this->send_headers();
252 echo $this->response
;
256 * Internal implementation - sending of page headers.
258 protected function send_headers() {
259 header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
260 header('Expires: ' . gmdate('D, d M Y H:i:s', 0) . ' GMT');
261 header('Pragma: no-cache');
262 header('Accept-Ranges: none');
263 header('Content-Length: ' . strlen($this->response
));
264 header('Content-Type: application/xml; charset=utf-8');
265 header('Content-Disposition: inline; filename="response.xml"');
269 * Generate a server fault.
271 * Note that the parameter order is the reverse of SoapFault's constructor parameters.
273 * Moodle note: basically we return the faultactor (errorcode) and faultdetails (debuginfo).
275 * If an exception is passed as the first argument, its message and code
276 * will be used to create the fault object.
278 * @link http://www.w3.org/TR/soap12-part1/#faultcodes
279 * @param string|Exception $fault
280 * @param string $code SOAP Fault Codes
282 public function fault($fault = null, $code = 'Receiver') {
283 $allowedfaultmodes = array(
284 'VersionMismatch', 'MustUnderstand', 'DataEncodingUnknown',
285 'Sender', 'Receiver', 'Server'
287 if (!in_array($code, $allowedfaultmodes)) {
291 // Intercept any exceptions and add the errorcode and debuginfo (optional).
294 $errorcode = 'unknownerror';
295 $message = get_string($errorcode);
296 if ($fault instanceof Exception
) {
297 // Add the debuginfo to the exception message if debuginfo must be returned.
298 $actor = isset($fault->errorcode
) ?
$fault->errorcode
: null;
301 $message = $fault->getMessage();
302 $details = isset($fault->debuginfo
) ?
$fault->debuginfo
: null;
304 } else if (is_string($fault)) {
308 $this->soapserver
->fault($code, $message . ' | ERRORCODE: ' . $errorcode, $actor, $details);
313 * SOAP test client class
315 * @package webservice_soap
316 * @copyright 2009 Petr Skodak
317 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
320 class webservice_soap_test_client
implements webservice_test_client_interface
{
323 * Execute test client WS request
325 * @param string $serverurl server url (including token parameter or username/password parameters)
326 * @param string $function function name
327 * @param array $params parameters of the called function
330 public function simpletest($serverurl, $function, $params) {
333 require_once($CFG->dirroot
. '/webservice/soap/lib.php');
334 $client = new webservice_soap_client($serverurl);
335 return $client->call($function, $params);