MDL-20179 Replaced incompatible code with something cross-browser compatible. Merged...
[moodle.git] / webservice / lib.php
CommitLineData
06e7fadc 1<?php
2/**
3 * Moodle - Modular Object-Oriented Dynamic Learning Environment
4 * http://moodle.com
5 *
6 * LICENSE
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details:
17 *
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @category Moodle
21 * @package webservice
22 * @copyright Copyright (c) 1999 onwards Martin Dougiamas http://dougiamas.com
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL License
24 */
25
24350e06 26require_once(dirname(dirname(__FILE__)) . '/lib/formslib.php');
06e7fadc 27
28/**
29 * web service library
30 */
31final class webservice_lib {
32
33 /**
34 * Return list of all web service protocol into the webservice folder
35 * @global <type> $CFG
36 * @return <type>
37 */
38 public static function get_list_protocols() {
39 global $CFG;
40 $protocols = array();
41 $directorypath = $CFG->dirroot . "/webservice";
42 if( $dh = opendir($directorypath)) {
43 while( false !== ($file = readdir($dh)))
44 {
45 if( $file == '.' || $file == '..' || $file == 'CVS') { // Skip '.' and '..'
46 continue;
47 }
48 $path = $directorypath . '/' . $file;
40f024c9 49 ///browse the subfolder
06e7fadc 50 if( is_dir($path) ) {
fbe52a39 51 if ($file != 'db') { //we don't want to browse the 'db' subfolder of webservice folder
40f024c9 52 require_once($path."/lib.php");
53 $classname = $file."_server";
54 $protocols[] = new $classname;
fbe52a39 55 }
06e7fadc 56 }
40f024c9 57 ///retrieve api.php file
06e7fadc 58 else {
59 continue;
60 }
61 }
62 closedir($dh);
63 }
64 return $protocols;
65 }
66
67 /**
68 * Temporary Authentication method to be modified/removed
69 * @global <type> $DB
70 * @param <type> $token
71 * @return <type>
72 */
73 public static function mock_check_token($token) {
74 //fake test
fa0797ec 75 if ($token == 456) {
06e7fadc 76 ///retrieve the user
77 global $DB;
78 $user = $DB->get_record('user', array('username'=>'wsuser', 'mnethostid'=>1));
79
80 if (empty($user)) {
81 return false;
82 }
83
84 return $user;
85 } else {
86 return false;
87 }
88 }
89
24350e06 90 /**
91 * Retrieve all external.php from Moodle (except the one of the exception list)
92 * @param <type> $
93 * @param <type> $directorypath
94 * @return boolean true if n
95 */
96 public static function setListApiFiles( &$files, $directorypath )
97 {
98 global $CFG;
99
100 if(is_dir($directorypath)){ //check that we are browsing a folder not a file
101
102 if( $dh = opendir($directorypath))
103 {
104 while( false !== ($file = readdir($dh)))
105 {
106
107 if( $file == '.' || $file == '..') { // Skip '.' and '..'
108 continue;
109 }
110 $path = $directorypath . '/' . $file;
40f024c9 111 ///browse the subfolder
24350e06 112 if( is_dir($path) ) {
40f024c9 113 webservice_lib::setListApiFiles($files, $path);
24350e06 114 }
40f024c9 115 ///retrieve api.php file
24350e06 116 else if ($file == "external.php") {
117 $files[] = $path;
118 }
119 }
120 closedir($dh);
121
122 }
123 }
124
125 }
126
40f024c9 127 /**
3fba577b 128 * Generate web service description array from the phpdoc for a given class
129 * @param string $file the class file
130 * @param string $class the class name
131 * @return array description
132 *
133 *
134 -------
135 Example
136 -------
137 * Docnlock: @ subparam string $params:searches->search - the string to search
138 * $params is considered as the first element, searches the second, and search the terminal
fa0797ec 139 * Except the terminal element, all other will be generated as an array
3fba577b 140 * => left element are generated as an associative array.
141 * If the following character is ':' so the right element is a key named 'multiple:element_name'
142 * If the following character is '->' so the right element will be named 'element_name'
143 * Rule: If a key is named 'multiple:xxx' other key must be 'multiple:yyy'
144
145 Docblock of mock_function
146 ---------------------------
147 @ param array|struct $params
148 @ subparam string $params:searches->search - the string to search
149 @ subparam string $params:searches->search2 optional - optional string to search
150 @ subparam string $params:searches->search3 - the string to search
151 @ subparam string $params:airport->planes:plane->company->employees:employee->name - name of a employee of a company of a plane of an airport
152 @ return array users
153 @ subreturn integer $users:user->id
154 @ subreturn integer $users:user->auth
fa0797ec 155
3fba577b 156 Generated description array
157 ---------------------------
158 description["mock_function"]=>
159 array(3) {
160 ["params"]=>
161 array(2) {
162 ["multiple:searches"]=>
163 array(2) {
164 ["search"]=>
165 string(6) "string"
166 ["search3"]=>
167 string(6) "string"
168 }
169 ["multiple:airport"]=>
170 array(1) {
171 ["planes"]=>
172 array(1) {
173 ["multiple:plane"]=>
174 array(1) {
175 ["company"]=>
176 array(1) {
177 ["employees"]=>
178 array(1) {
179 ["multiple:employee"]=>
180 array(1) {
181 ["name"]=>
182 string(6) "string"
183 }
184 }
185 }
186 }
187 }
188 }
189 }
190 ["optional"]=>
191 array(1) {
192 ["multiple:searches"]=>
193 array(1) {
194 ["search2"]=>
195 string(6) "string"
196 }
197 }
198 ["return"]=>
199 array(1) {
200 ["multiple:user"]=>
201 array(13) {
202 ["id"]=>
203 string(7) "integer"
204 ["auth"]=>
205 string(7) "integer"
206 }
207 }
40f024c9 208 */
209 public static function generate_webservice_description($file, $class){
210 require_once($file);
211 require_once "Zend/Loader.php";
212 Zend_Loader::registerAutoload();
213 $reflection = Zend_Server_Reflection::reflectClass($class);
214 $description = array();
215
216 foreach($reflection->getMethods() as $method){
217
7c9152bc 218 $docBlock = $method->getDocComment();
40f024c9 219
7c9152bc 220 //retrieve the return and add it into the description if not array|object
221 preg_match_all('/@return\s+(\w+)\s+((?:\$)?\w+)/', $docBlock, $returnmatches);
40f024c9 222
7c9152bc 223 //retrieve the subparam and subreturn
224 preg_match_all('/\s*\*\s*@(subparam|subreturn)\s+(\w+)\s+(\$\w+(?::\w+|->\w+)+)((?:\s+(?:optional|required|multiple))*)/', $docBlock, $matches);
40f024c9 225
fa0797ec 226 /// process every @subparam and @subreturn line of the docblock
7c9152bc 227 for($i=0;$i<sizeof($matches[1]);$i++){
3fba577b 228
fa0797ec 229 /// identify the description type of the docblock line: is it params, optional or return (first key of a description method array)
7c9152bc 230 switch ($matches[1][$i]) {
231 case "subparam":
232 if (strpos($matches[4][$i], "optional")!==false) {
233 $descriptiontype = "optional";
234 } else {
235 $descriptiontype = "params" ;
236 }
237 break;
238 case "subreturn":
239 $descriptiontype = "return";
240 break;
241 }
40f024c9 242
fa0797ec 243 /// init description[method]
7c9152bc 244 if (empty($description[$method->getName()])) {
245 $description[$method->getName()] = array();
246 }
247
fa0797ec 248 /// directly set description[method][return] if the return value is a primary type
7c9152bc 249 if (strpos($returnmatches[1][0] ,"object")===false && strpos($returnmatches[1][0],"array")===false) {
250 $description[$method->getName()]['return'] = array($returnmatches[2][0] => $returnmatches[1][0]);
251 }
40f024c9 252
7c9152bc 253
3fba577b 254 ///algorythm parts
255 ///1. We compare the string to the description array
256 /// When we find a attribut that is not in the description, we retrieve all the rest of the string
257 ///2. We create the missing part of the description array, starting from the end of the rest of the string
258 ///3. We add the missing part to the description array
7c9152bc 259
3fba577b 260 ///Part 1.
7c9152bc 261
7c9152bc 262
fa0797ec 263 /// extract the first part into $param (has to be $params in the case of @subparam, or anything in the case of $subreturn)
264 /// extract the second part
3fba577b 265 if (strpos($matches[3][$i], "->")===false || (strpos($matches[3][$i], ":")!==false && (strpos($matches[3][$i], ":") < strpos($matches[3][$i], "->")))) {
266 $separator = ":";
267 $separatorsize=1;
7c9152bc 268
3fba577b 269 } else {
270 $separator = "->";
271 $separatorsize=2;
272 }
40f024c9 273
3fba577b 274 $param = substr($matches[3][$i],1,strpos($matches[3][$i], $separator)-1); //first element/part/array
fa0797ec 275 //for example for the line @subparam string $params:students->student->name
276 // @params is the first element/part/array of this docnlock line
277 // students is the second element/part/array
278 // ...
279 // name is the terminal element, this element will be generated as String here
40f024c9 280
40f024c9 281
3fba577b 282 $otherparam = substr($matches[3][$i],strpos($matches[3][$i], $separator)+$separatorsize); //rest of the line
283 $parsingdesc = $description[$method->getName()]; //$pasingdesc is the current position of the algorythm into the description array
fa0797ec 284 //it is used to check if a element already exist into the description array
40f024c9 285
3fba577b 286 if (!empty($parsingdesc) && array_key_exists($descriptiontype, $parsingdesc)){
287 $parsingdesc = $parsingdesc[$descriptiontype];
288 }
289 $descriptionpath=array(); //we save in this variable the description path (e.g all keys to go deep into the description array)
fa0797ec 290 //it will be used to know where to add a new part the description array
40f024c9 291
3fba577b 292 $creationfinished = false; //it's used to stop the algorythm when we find a new element that we can add to the descripitoin
293 unset($type);
40f024c9 294
fa0797ec 295 /// try to extract the other elements and add them to the descripition id there are not already in the description
3fba577b 296 while(!$creationfinished && (strpos($otherparam, ":") || strpos($otherparam, "->"))) {
297 if (strpos($otherparam, "->")===false || (strpos($otherparam, ":")!==false && (strpos($otherparam, ":") < strpos($otherparam, "->")))) {
298 $type = $separator;
40f024c9 299
3fba577b 300 $separator = ":";
301 $separatorsize=1;
302 } else {
303 $type = $separator;
304 $separator = "->";
305 $separatorsize=2;
306 }
40f024c9 307
3fba577b 308 $param = substr($otherparam,0,strpos($otherparam, $separator));
40f024c9 309
3fba577b 310 $otherparam = substr($otherparam,strpos($otherparam, $separator)+$separatorsize);
40f024c9 311
40f024c9 312
3fba577b 313 if ($type==":") {
314 /// this element is not already in the description array yet and it is a non associative array
315 /// we add it (and its sub structure) to the description array
316 if (!array_key_exists('multiple:'.$param, $parsingdesc)){
40f024c9 317
3fba577b 318 $desctoadd = webservice_lib::create_end_of_descriptionline(":".$param.$separator.$otherparam, $matches[2][$i]);
40f024c9 319
3fba577b 320 if(empty($descriptionpath) ) {
321 if (empty($description[$method->getName()]) || !array_key_exists($descriptiontype, $description[$method->getName()])) {
322 $desctoadd = array($descriptiontype => $desctoadd);
40f024c9 323 }
3fba577b 324 $paramtoadd = $descriptiontype;
40f024c9 325 } else {
3fba577b 326 $paramtoadd = 'multiple:'.$param;
40f024c9 327 }
3fba577b 328 webservice_lib::add_end_of_description($paramtoadd, $desctoadd, $description[$method->getName()], $descriptionpath);
329 $creationfinished = true; // we do not want to keep going to parse this line,
fa0797ec 330 // neither add again the terminal element of the line to the descripiton
7c9152bc 331 } else {
3fba577b 332 if(empty($descriptionpath)) {
333 $descriptionpath[] = $descriptiontype;
7c9152bc 334 }
3fba577b 335 $descriptionpath[] = 'multiple:'.$param;
336 $parsingdesc = $parsingdesc['multiple:'.$param];
7c9152bc 337 }
3fba577b 338 } else {
339 /// this element is not in the description array yet and it is a associative array
340 /// we add it (and its sub structure) to the description array
341 if (!array_key_exists($param, $parsingdesc)){
40f024c9 342
3fba577b 343 $desctoadd = webservice_lib::create_end_of_descriptionline("->".$param.$separator.$otherparam, $matches[2][$i]);
7c9152bc 344
345 if(empty($descriptionpath)) {
3fba577b 346
7c9152bc 347 if (empty($description[$method->getName()]) || !array_key_exists($descriptiontype, $description[$method->getName()])) {
348 $desctoadd = array($descriptiontype => $desctoadd);
40f024c9 349 }
7c9152bc 350 $paramtoadd = $descriptiontype;
3fba577b 351
7c9152bc 352 } else {
3fba577b 353 $paramtoadd = $param;
7c9152bc 354 }
7c9152bc 355 webservice_lib::add_end_of_description($paramtoadd, $desctoadd, $description[$method->getName()], $descriptionpath);
356
3fba577b 357 $creationfinished = true; // we do not want to keep going to parse this line,
fa0797ec 358 // neither add again the terminal element of the line to the descripiton
7c9152bc 359 } else {
7c9152bc 360 if(empty($descriptionpath)) {
3fba577b 361 $descriptionpath[] = $descriptiontype;
362 }
363 $descriptionpath[] = $param;
364 $parsingdesc = $parsingdesc[$param];
365 }
366 }
40f024c9 367
3fba577b 368 }
fa0797ec 369 /// Add the "terminal" element of the line to the description array
3fba577b 370 if (!$creationfinished) {
7c9152bc 371
3fba577b 372 if (!empty($type) && $type==":") {
373
374 $desctoadd = webservice_lib::create_end_of_descriptionline($separator.$otherparam, $matches[2][$i]);
375
376 if(empty($descriptionpath)) {
377 if (empty($description[$method->getName()]) || !array_key_exists($descriptiontype, $description[$method->getName()])) {
378 $desctoadd = array($descriptiontype => $desctoadd);
40f024c9 379 }
3fba577b 380 $paramtoadd = $descriptiontype;
381 } else {
382 $paramtoadd = 'multiple:'.$param;
383 }
384
385 webservice_lib::add_end_of_description($paramtoadd, $desctoadd, $description[$method->getName()], $descriptionpath);
386
387 } else {
388 $desctoadd = webservice_lib::create_end_of_descriptionline($separator.$otherparam, $matches[2][$i]);
389 if(empty($descriptionpath)) {
390
391 if (empty($description[$method->getName()]) || !array_key_exists($descriptiontype, $description[$method->getName()])) {
392 $desctoadd = array($descriptiontype => $desctoadd);
393 }
394 $paramtoadd = $descriptiontype;
395
396 } else {
397 $paramtoadd = $param;
40f024c9 398 }
3fba577b 399 webservice_lib::add_end_of_description($paramtoadd, $desctoadd, $description[$method->getName()], $descriptionpath);
40f024c9 400 }
401 }
3fba577b 402
40f024c9 403 }
404 }
f7631e73 405 // echo "<pre>";
406 // var_dump($description);
407 // echo "</pre>";
40f024c9 408 return $description;
3fba577b 409
40f024c9 410 }
411
412 /**
3fba577b 413 * Add a description part to the descripition array
40f024c9 414 * @param <type> $param
415 * @param <type> $desctoadd
416 * @param <type> $descriptionlevel
417 * @param <type> $descriptionpath
418 * @param <type> $level
419 */
420 public static function add_end_of_description($param, $desctoadd, &$descriptionlevel, $descriptionpath, $level= 0){
421 if (sizeof($descriptionpath)==0 || sizeof($descriptionpath)==$level+1) {
422
423 if (is_array($descriptionlevel) && !empty($descriptionlevel)) {
424 foreach($desctoadd as $key=>$value) {
425 if ($key!="params" && $key!="optional" && $key!="return") { //TODO
426 $descriptionlevel[$param][$key] = $value;
427 } else {
428 $descriptionlevel[$param] = $value;
429 }
430 }
431 } else {
432 $descriptionlevel = $desctoadd;
433 }
434 } else {
add1a6da 435 webservice_lib::add_end_of_description($param, $desctoadd, $descriptionlevel[$descriptionpath[$level]], $descriptionpath, $level+1);
40f024c9 436 }
437
438 }
439
440
441 /**
3fba577b 442 * We create a description part for the description array
443 * Structure explained in the "generate_webservice_description" dockblock
40f024c9 444 * @param <type> $stringtoadd
445 * @param <type> $type
446 * @return <type>
447 */
3fba577b 448 public static function create_end_of_descriptionline($stringtoadd, $type) {
40f024c9 449
450 if (strrpos($stringtoadd, "->")===false || (strrpos($stringtoadd, ":")!==false && (strrpos($stringtoadd, ":") > strrpos($stringtoadd, "->")))) {
451 $separator = ":";
452 $separatorsize=1;
453 } else {
454 $separator = "->";
455 $separatorsize=2;
456 }
457
458 $param = substr($stringtoadd,strrpos($stringtoadd, $separator)+$separatorsize);
459 $result = array( $param => $type);
460
461 $otherparam = substr($stringtoadd,0,strlen($stringtoadd)-strlen($param)-$separatorsize);
462
463 while(strrpos($otherparam, ":")!==false || strrpos($otherparam, "->")!==false) {
464 if (strrpos($otherparam, "->")===false || (strrpos($otherparam, ":")!==false && (strrpos($otherparam, ":") > strrpos($otherparam, "->")))) {
465 $separator = ":";
466 $separatorsize=1;
467 } else {
468 $separator = "->";
469 $separatorsize=2;
470 }
471 $param = substr($otherparam,strrpos($otherparam, $separator)+$separatorsize);
472 $otherparam = substr($otherparam,0,strrpos($otherparam, $separator));
473
474 if ($separator==":") {
475 $result = array('multiple:'.$param => $result);
476 } else {
477 $result = array($param => $result);
478 }
479
480 }
481
482 return $result;
483
484 }
485
fa0797ec 486 /**
487 * Check if the Moodle site has the web service protocol enable
488 * @global object $CFG
489 * @param string $protocol
490 */
491 function display_webservices_availability($protocol){
492 global $CFG;
493
494 $available = true;
495
496 echo get_string('webservicesenable','webservice').": ";
497 if (empty($CFG->enablewebservices)) {
498 echo "<strong style=\"color:red\">".get_string('fail','webservice')."</strong>";
499 $available = false;
500 } else {
501 echo "<strong style=\"color:green\">".get_string('ok','webservice')."</strong>";
502 }
503 echo "<br/>";
504
505 foreach(webservice_lib::get_list_protocols() as $wsprotocol) {
f7631e73 506 if (strtolower($wsprotocol->get_protocolid()) == strtolower($protocol)) {
507 echo get_string('protocolenable','webservice',array($wsprotocol->get_protocolid())).": ";
508 if ( get_config($wsprotocol-> get_protocolid(), "enable")) {
fa0797ec 509 echo "<strong style=\"color:green\">".get_string('ok','webservice')."</strong>";
510 } else {
511 echo "<strong style=\"color:red\">".get_string('fail','webservice')."</strong>";
512 $available = false;
513 }
514 echo "<br/>";
515 continue;
516 }
517 }
518
519 //check debugging
520 if ($CFG->debugdisplay) {
521 echo "<strong style=\"color:red\">".get_string('debugdisplayon','webservice')."</strong>";
522 $available = false;
523 }
524
525 return $available;
526 }
527
06e7fadc 528}
529
530/**
531 * Web Service server base class
532 */
533abstract class webservice_server {
534
535 /**
536 * Web Service Protocol name (eg. SOAP, REST, XML-RPC,...)
537 * @var String
538 */
539 private $protocolname;
540
f7631e73 541 /**
542 * Web Service Protocol id (eg. soap, rest, xmlrpc...)
543 * @var String
544 */
545 private $protocolid;
546
06e7fadc 547 public function __construct() {
548 }
549
550 abstract public function run();
551
552 public function get_protocolname() {
553 return $this->protocolname;
554 }
555
f7631e73 556 public function get_protocolid() {
557 return $this->protocolid;
558 }
559
06e7fadc 560 public function set_protocolname($protocolname) {
561 $this->protocolname = $protocolname;
562 }
563
f7631e73 564 public function set_protocolid($protocolid) {
565 $this->protocolid = $protocolid;
566 }
567
06e7fadc 568 public function get_enable() {
f7631e73 569 return get_config($this->get_protocolid(), "enable");
06e7fadc 570 }
571
572 public function set_enable($enable) {
f7631e73 573 set_config("enable", $enable, $this->get_protocolid());
574 }
575
576 /**
577 * Names of the server settings
578 * @return array
579 */
580 public static function get_setting_names() {
581 return array();
582 }
583
584 public function settings_form(&$mform) {
06e7fadc 585 }
586
587}
588
589/**
590 * Temporary authentication class to be removed/modified
591 */
592class ws_authentication {
593 /**
594 *
595 * @param array|struct $params
596 * @return integer
597 */
fbe52a39 598 function get_token($params) {
06e7fadc 599 if ($params['username'] == 'wsuser' && $params['password'] == 'wspassword') {
fa0797ec 600 return '456';
06e7fadc 601 } else {
602 throw new moodle_exception('wrongusernamepassword');
fbe52a39 603 }
06e7fadc 604 }
605}
606
24350e06 607/**
608 * Form for web service user settings (administration)
609 */
610final class wsuser_form extends moodleform {
611 protected $username;
612
613 /**
614 * Definition of the moodleform
615 */
616 public function definition() {
617 global $DB;
618 $this->username = $this->_customdata['username'];
619 $mform =& $this->_form;
40f024c9 620
24350e06 621 $mform->addElement('hidden', 'username', $this->username);
622 $param = new stdClass();
623 $param->username = $this->username;
624 $wsuser = $DB->get_record("user", array("username" => $this->username));
625
626 $mform->addElement('text', 'ipwhitelist', get_string('ipwhitelist', 'admin'), array('value'=>get_user_preferences("ipwhitelist", "", $wsuser->id),'size' => '40'));
627 $mform->addElement('static', null, '', get_string('ipwhitelistdesc','admin', $param));
628
629 $this->add_action_buttons(true, get_string('savechanges','admin'));
630 }
631}
632
f7631e73 633/**
634 * Form for web service server settings (administration)
635 */
636final class wssettings_form extends moodleform {
637 protected $settings;
638
639 /**
640 * Definition of the moodleform
641 */
642 public function definition() {
643 global $DB,$CFG;
644 $settings = $this->_customdata['settings'];
645 $mform =& $this->_form;
646
647 $mform->addElement('hidden', 'settings', $settings);
648 $param = new stdClass();
649
650 require_once($CFG->dirroot . '/webservice/'. $settings . '/lib.php');
651 $servername = $settings.'_server';
652 $server = new $servername();
653 $server->settings_form($mform);
654
655 // set the data if we have some.
656 $data = array();
657 $option_names = $server->get_setting_names();
658 foreach ($option_names as $config) {
659 $data[$config] = get_config($settings, $config);
660 }
661 $this->set_data($data);
662
663
664 $this->add_action_buttons(true, get_string('savechanges','admin'));
665 }
666}
667
06e7fadc 668?>