Merge branch 'MDL-55028-master' of git://github.com/junpataleta/moodle
[moodle.git] / webservice / soap / locallib.php
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/>.
18 /**
19  * SOAP web service implementation classes and methods.
20  *
21  * @package    webservice_soap
22  * @copyright  2009 Petr Skodak
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
25 global $CFG;
26 require_once($CFG->dirroot . '/webservice/lib.php');
27 use webservice_soap\wsdl;
29 /**
30  * SOAP service server implementation.
31  *
32  * @package    webservice_soap
33  * @copyright  2009 Petr Skodak
34  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35  * @since Moodle 2.0
36  */
37 class webservice_soap_server extends webservice_base_server {
39     /** @var moodle_url The server URL. */
40     protected $serverurl;
42     /** @var  SoapServer The Soap */
43     protected $soapserver;
45     /** @var  string The response. */
46     protected $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. */
52     protected $wsdlmode;
54     /** @var \webservice_soap\wsdl The object for WSDL generation. */
55     protected $wsdl;
57     /**
58      * Contructor.
59      *
60      * @param string $authmethod authentication method of the web service (WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN, ...)
61      */
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;
68     }
70     /**
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
74      *   - Token (wstoken)
75      */
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;
90                 }
91             }
92             $this->serverurl = new moodle_url('/webservice/soap/simpleserver.php/' . $this->username . '/' . $this->password);
93         } else {
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);
98         }
100         if ($wsdl = optional_param('wsdl', 0, PARAM_INT)) {
101             $this->wsdlmode = true;
102         }
103     }
105     /**
106      * Runs the SOAP web service.
107      *
108      * @throws coding_exception
109      * @throws moodle_exception
110      * @throws webservice_access_exception
111      */
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();
134         }
136         // Log the web service request.
137         $params = array(
138             'other' => array(
139                 'function' => 'unknown'
140             )
141         );
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);
145         $event->trigger();
147         // Handle the SOAP request.
148         $this->handle();
150         // Session cleanup.
151         $this->session_cleanup();
152         die;
153     }
155     /**
156      * Generates the WSDL.
157      */
158     protected function generate_wsdl() {
159         // Initialise 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);
164         }
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);
168         }
169     }
171     /**
172      * Handles the web service function call.
173      */
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();
181         } else {
182             $wsdlurl = clone($this->serverurl);
183             $wsdlurl->param('wsdl', 1);
185             $options = array(
186                 'uri' => $this->serverurl->out(false)
187             );
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);
195             }
197             // Get soap request from raw POST data.
198             $soaprequest = file_get_contents('php://input');
199             // Handle the request.
200             try {
201                 $this->soapserver->handle($soaprequest);
202             } catch (Exception $e) {
203                 $this->fault($e);
204             }
205         }
206     }
208     /**
209      * Send the error information to the WS client formatted as an XML document.
210      *
211      * @param Exception $ex the exception to send back
212      */
213     protected function send_error($ex = null) {
214         if ($ex) {
215             $info = $ex->getMessage();
216             if (debugging() and isset($ex->debuginfo)) {
217                 $info .= ' - '.$ex->debuginfo;
218             }
219         } else {
220             $info = 'Unknown error';
221         }
223         // Initialise new DOM document object.
224         $dom = new DOMDocument('1.0', 'UTF-8');
226         // Fault node.
227         $fault = $dom->createElement('SOAP-ENV:Fault');
228         // Faultcode node.
229         $fault->appendChild($dom->createElement('faultcode', 'MOODLE:error'));
230         // Faultstring node.
231         $fault->appendChild($dom->createElement('faultstring', $info));
233         // Body node.
234         $body = $dom->createElement('SOAP-ENV:Body');
235         $body->appendChild($fault);
237         // Envelope node.
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();
245     }
247     /**
248      * Send the result of function call to the WS client.
249      */
250     protected function send_response() {
251         $this->send_headers();
252         echo $this->response;
253     }
255     /**
256      * Internal implementation - sending of page headers.
257      */
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"');
266     }
268     /**
269      * Generate a server fault.
270      *
271      * Note that the parameter order is the reverse of SoapFault's constructor parameters.
272      *
273      * Moodle note: basically we return the faultactor (errorcode) and faultdetails (debuginfo).
274      *
275      * If an exception is passed as the first argument, its message and code
276      * will be used to create the fault object.
277      *
278      * @link   http://www.w3.org/TR/soap12-part1/#faultcodes
279      * @param  string|Exception $fault
280      * @param  string $code SOAP Fault Codes
281      */
282     public function fault($fault = null, $code = 'Receiver') {
283         $allowedfaultmodes = array(
284             'VersionMismatch', 'MustUnderstand', 'DataEncodingUnknown',
285             'Sender', 'Receiver', 'Server'
286         );
287         if (!in_array($code, $allowedfaultmodes)) {
288             $code = 'Receiver';
289         }
291         // Intercept any exceptions and add the errorcode and debuginfo (optional).
292         $actor = null;
293         $details = null;
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;
299             $errorcode = $actor;
300             if (debugging()) {
301                 $message = $fault->getMessage();
302                 $details = isset($fault->debuginfo) ? $fault->debuginfo : null;
303             }
304         } else if (is_string($fault)) {
305             $message = $fault;
306         }
308         $this->soapserver->fault($code, $message . ' | ERRORCODE: ' . $errorcode, $actor, $details);
309     }
312 /**
313  * SOAP test client class
314  *
315  * @package    webservice_soap
316  * @copyright  2009 Petr Skodak
317  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
318  * @since Moodle 2.0
319  */
320 class webservice_soap_test_client implements webservice_test_client_interface {
322     /**
323      * Execute test client WS request
324      *
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
328      * @return mixed
329      */
330     public function simpletest($serverurl, $function, $params) {
331         global $CFG;
333         require_once($CFG->dirroot . '/webservice/soap/lib.php');
334         $client = new webservice_soap_client($serverurl);
335         return $client->call($function, $params);
336     }