mirror of
https://github.com/moodle/moodle.git
synced 2025-02-23 19:44:19 +01:00
Change most of User web service functions to support multiple operation in one call. Adapt SOAP and XMLRPC test client. (REST User test clients wont work anymore till REST server supports array parameter)
609 lines
24 KiB
PHP
609 lines
24 KiB
PHP
<?php
|
|
/**
|
|
* Moodle - Modular Object-Oriented Dynamic Learning Environment
|
|
* http://moodle.com
|
|
*
|
|
* LICENSE
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details:
|
|
*
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
*
|
|
* @category Moodle
|
|
* @package webservice
|
|
* @copyright Copyright (c) 1999 onwards Martin Dougiamas http://dougiamas.com
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL License
|
|
*/
|
|
|
|
require_once(dirname(dirname(__FILE__)) . '/lib/formslib.php');
|
|
|
|
/**
|
|
* web service library
|
|
*/
|
|
final class webservice_lib {
|
|
|
|
/**
|
|
* Return list of all web service protocol into the webservice folder
|
|
* @global <type> $CFG
|
|
* @return <type>
|
|
*/
|
|
public static function get_list_protocols() {
|
|
global $CFG;
|
|
$protocols = array();
|
|
$directorypath = $CFG->dirroot . "/webservice";
|
|
if( $dh = opendir($directorypath)) {
|
|
while( false !== ($file = readdir($dh)))
|
|
{
|
|
if( $file == '.' || $file == '..' || $file == 'CVS') { // Skip '.' and '..'
|
|
continue;
|
|
}
|
|
$path = $directorypath . '/' . $file;
|
|
///browse the subfolder
|
|
if( is_dir($path) ) {
|
|
require_once($path."/lib.php");
|
|
$classname = $file."_server";
|
|
$protocols[] = new $classname;
|
|
}
|
|
///retrieve api.php file
|
|
else {
|
|
continue;
|
|
}
|
|
}
|
|
closedir($dh);
|
|
}
|
|
return $protocols;
|
|
}
|
|
|
|
/**
|
|
* Temporary Authentication method to be modified/removed
|
|
* @global <type> $DB
|
|
* @param <type> $token
|
|
* @return <type>
|
|
*/
|
|
public static function mock_check_token($token) {
|
|
//fake test
|
|
if ($token == 456) {
|
|
///retrieve the user
|
|
global $DB;
|
|
$user = $DB->get_record('user', array('username'=>'wsuser', 'mnethostid'=>1));
|
|
|
|
if (empty($user)) {
|
|
return false;
|
|
}
|
|
|
|
return $user;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve all external.php from Moodle (except the one of the exception list)
|
|
* @param <type> $
|
|
* @param <type> $directorypath
|
|
* @return boolean true if n
|
|
*/
|
|
public static function setListApiFiles( &$files, $directorypath )
|
|
{
|
|
global $CFG;
|
|
|
|
if(is_dir($directorypath)){ //check that we are browsing a folder not a file
|
|
|
|
if( $dh = opendir($directorypath))
|
|
{
|
|
while( false !== ($file = readdir($dh)))
|
|
{
|
|
|
|
if( $file == '.' || $file == '..') { // Skip '.' and '..'
|
|
continue;
|
|
}
|
|
$path = $directorypath . '/' . $file;
|
|
///browse the subfolder
|
|
if( is_dir($path) ) {
|
|
webservice_lib::setListApiFiles($files, $path);
|
|
}
|
|
///retrieve api.php file
|
|
else if ($file == "external.php") {
|
|
$files[] = $path;
|
|
}
|
|
}
|
|
closedir($dh);
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Generate web service description array from the phpdoc for a given class
|
|
* @param string $file the class file
|
|
* @param string $class the class name
|
|
* @return array description
|
|
*
|
|
*
|
|
-------
|
|
Example
|
|
-------
|
|
* Docnlock: @ subparam string $params:searches->search - the string to search
|
|
* $params is considered as the first element, searches the second, and search the terminal
|
|
* Except the terminal element, all other will be generated as an array
|
|
* => left element are generated as an associative array.
|
|
* If the following character is ':' so the right element is a key named 'multiple:element_name'
|
|
* If the following character is '->' so the right element will be named 'element_name'
|
|
* Rule: If a key is named 'multiple:xxx' other key must be 'multiple:yyy'
|
|
|
|
Docblock of mock_function
|
|
---------------------------
|
|
@ param array|struct $params
|
|
@ subparam string $params:searches->search - the string to search
|
|
@ subparam string $params:searches->search2 optional - optional string to search
|
|
@ subparam string $params:searches->search3 - the string to search
|
|
@ subparam string $params:airport->planes:plane->company->employees:employee->name - name of a employee of a company of a plane of an airport
|
|
@ return array users
|
|
@ subreturn integer $users:user->id
|
|
@ subreturn integer $users:user->auth
|
|
|
|
Generated description array
|
|
---------------------------
|
|
description["mock_function"]=>
|
|
array(3) {
|
|
["params"]=>
|
|
array(2) {
|
|
["multiple:searches"]=>
|
|
array(2) {
|
|
["search"]=>
|
|
string(6) "string"
|
|
["search3"]=>
|
|
string(6) "string"
|
|
}
|
|
["multiple:airport"]=>
|
|
array(1) {
|
|
["planes"]=>
|
|
array(1) {
|
|
["multiple:plane"]=>
|
|
array(1) {
|
|
["company"]=>
|
|
array(1) {
|
|
["employees"]=>
|
|
array(1) {
|
|
["multiple:employee"]=>
|
|
array(1) {
|
|
["name"]=>
|
|
string(6) "string"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
["optional"]=>
|
|
array(1) {
|
|
["multiple:searches"]=>
|
|
array(1) {
|
|
["search2"]=>
|
|
string(6) "string"
|
|
}
|
|
}
|
|
["return"]=>
|
|
array(1) {
|
|
["multiple:user"]=>
|
|
array(13) {
|
|
["id"]=>
|
|
string(7) "integer"
|
|
["auth"]=>
|
|
string(7) "integer"
|
|
}
|
|
}
|
|
*/
|
|
public static function generate_webservice_description($file, $class){
|
|
require_once($file);
|
|
require_once "Zend/Loader.php";
|
|
Zend_Loader::registerAutoload();
|
|
$reflection = Zend_Server_Reflection::reflectClass($class);
|
|
$description = array();
|
|
|
|
foreach($reflection->getMethods() as $method){
|
|
|
|
$docBlock = $method->getDocComment();
|
|
|
|
//retrieve the return and add it into the description if not array|object
|
|
preg_match_all('/@return\s+(\w+)\s+((?:\$)?\w+)/', $docBlock, $returnmatches);
|
|
|
|
//retrieve the subparam and subreturn
|
|
preg_match_all('/\s*\*\s*@(subparam|subreturn)\s+(\w+)\s+(\$\w+(?::\w+|->\w+)+)((?:\s+(?:optional|required|multiple))*)/', $docBlock, $matches);
|
|
|
|
/// process every @subparam and @subreturn line of the docblock
|
|
for($i=0;$i<sizeof($matches[1]);$i++){
|
|
|
|
/// identify the description type of the docblock line: is it params, optional or return (first key of a description method array)
|
|
switch ($matches[1][$i]) {
|
|
case "subparam":
|
|
if (strpos($matches[4][$i], "optional")!==false) {
|
|
$descriptiontype = "optional";
|
|
} else {
|
|
$descriptiontype = "params" ;
|
|
}
|
|
break;
|
|
case "subreturn":
|
|
$descriptiontype = "return";
|
|
break;
|
|
}
|
|
|
|
/// init description[method]
|
|
if (empty($description[$method->getName()])) {
|
|
$description[$method->getName()] = array();
|
|
}
|
|
|
|
/// directly set description[method][return] if the return value is a primary type
|
|
if (strpos($returnmatches[1][0] ,"object")===false && strpos($returnmatches[1][0],"array")===false) {
|
|
$description[$method->getName()]['return'] = array($returnmatches[2][0] => $returnmatches[1][0]);
|
|
}
|
|
|
|
|
|
///algorythm parts
|
|
///1. We compare the string to the description array
|
|
/// When we find a attribut that is not in the description, we retrieve all the rest of the string
|
|
///2. We create the missing part of the description array, starting from the end of the rest of the string
|
|
///3. We add the missing part to the description array
|
|
|
|
///Part 1.
|
|
|
|
|
|
/// extract the first part into $param (has to be $params in the case of @subparam, or anything in the case of $subreturn)
|
|
/// extract the second part
|
|
if (strpos($matches[3][$i], "->")===false || (strpos($matches[3][$i], ":")!==false && (strpos($matches[3][$i], ":") < strpos($matches[3][$i], "->")))) {
|
|
$separator = ":";
|
|
$separatorsize=1;
|
|
|
|
} else {
|
|
$separator = "->";
|
|
$separatorsize=2;
|
|
}
|
|
|
|
$param = substr($matches[3][$i],1,strpos($matches[3][$i], $separator)-1); //first element/part/array
|
|
//for example for the line @subparam string $params:students->student->name
|
|
// @params is the first element/part/array of this docnlock line
|
|
// students is the second element/part/array
|
|
// ...
|
|
// name is the terminal element, this element will be generated as String here
|
|
|
|
|
|
$otherparam = substr($matches[3][$i],strpos($matches[3][$i], $separator)+$separatorsize); //rest of the line
|
|
$parsingdesc = $description[$method->getName()]; //$pasingdesc is the current position of the algorythm into the description array
|
|
//it is used to check if a element already exist into the description array
|
|
|
|
if (!empty($parsingdesc) && array_key_exists($descriptiontype, $parsingdesc)){
|
|
$parsingdesc = $parsingdesc[$descriptiontype];
|
|
}
|
|
$descriptionpath=array(); //we save in this variable the description path (e.g all keys to go deep into the description array)
|
|
//it will be used to know where to add a new part the description array
|
|
|
|
$creationfinished = false; //it's used to stop the algorythm when we find a new element that we can add to the descripitoin
|
|
unset($type);
|
|
|
|
/// try to extract the other elements and add them to the descripition id there are not already in the description
|
|
while(!$creationfinished && (strpos($otherparam, ":") || strpos($otherparam, "->"))) {
|
|
if (strpos($otherparam, "->")===false || (strpos($otherparam, ":")!==false && (strpos($otherparam, ":") < strpos($otherparam, "->")))) {
|
|
$type = $separator;
|
|
|
|
$separator = ":";
|
|
$separatorsize=1;
|
|
} else {
|
|
$type = $separator;
|
|
$separator = "->";
|
|
$separatorsize=2;
|
|
}
|
|
|
|
$param = substr($otherparam,0,strpos($otherparam, $separator));
|
|
|
|
$otherparam = substr($otherparam,strpos($otherparam, $separator)+$separatorsize);
|
|
|
|
|
|
if ($type==":") {
|
|
/// this element is not already in the description array yet and it is a non associative array
|
|
/// we add it (and its sub structure) to the description array
|
|
if (!array_key_exists('multiple:'.$param, $parsingdesc)){
|
|
|
|
$desctoadd = webservice_lib::create_end_of_descriptionline(":".$param.$separator.$otherparam, $matches[2][$i]);
|
|
|
|
if(empty($descriptionpath) ) {
|
|
if (empty($description[$method->getName()]) || !array_key_exists($descriptiontype, $description[$method->getName()])) {
|
|
$desctoadd = array($descriptiontype => $desctoadd);
|
|
}
|
|
$paramtoadd = $descriptiontype;
|
|
} else {
|
|
$paramtoadd = 'multiple:'.$param;
|
|
}
|
|
webservice_lib::add_end_of_description($paramtoadd, $desctoadd, $description[$method->getName()], $descriptionpath);
|
|
$creationfinished = true; // we do not want to keep going to parse this line,
|
|
// neither add again the terminal element of the line to the descripiton
|
|
} else {
|
|
if(empty($descriptionpath)) {
|
|
$descriptionpath[] = $descriptiontype;
|
|
}
|
|
$descriptionpath[] = 'multiple:'.$param;
|
|
$parsingdesc = $parsingdesc['multiple:'.$param];
|
|
}
|
|
} else {
|
|
/// this element is not in the description array yet and it is a associative array
|
|
/// we add it (and its sub structure) to the description array
|
|
if (!array_key_exists($param, $parsingdesc)){
|
|
|
|
$desctoadd = webservice_lib::create_end_of_descriptionline("->".$param.$separator.$otherparam, $matches[2][$i]);
|
|
|
|
if(empty($descriptionpath)) {
|
|
|
|
if (empty($description[$method->getName()]) || !array_key_exists($descriptiontype, $description[$method->getName()])) {
|
|
$desctoadd = array($descriptiontype => $desctoadd);
|
|
}
|
|
$paramtoadd = $descriptiontype;
|
|
|
|
} else {
|
|
$paramtoadd = $param;
|
|
}
|
|
webservice_lib::add_end_of_description($paramtoadd, $desctoadd, $description[$method->getName()], $descriptionpath);
|
|
|
|
$creationfinished = true; // we do not want to keep going to parse this line,
|
|
// neither add again the terminal element of the line to the descripiton
|
|
} else {
|
|
if(empty($descriptionpath)) {
|
|
$descriptionpath[] = $descriptiontype;
|
|
}
|
|
$descriptionpath[] = $param;
|
|
$parsingdesc = $parsingdesc[$param];
|
|
}
|
|
}
|
|
|
|
}
|
|
/// Add the "terminal" element of the line to the description array
|
|
if (!$creationfinished) {
|
|
|
|
if (!empty($type) && $type==":") {
|
|
|
|
$desctoadd = webservice_lib::create_end_of_descriptionline($separator.$otherparam, $matches[2][$i]);
|
|
|
|
if(empty($descriptionpath)) {
|
|
if (empty($description[$method->getName()]) || !array_key_exists($descriptiontype, $description[$method->getName()])) {
|
|
$desctoadd = array($descriptiontype => $desctoadd);
|
|
}
|
|
$paramtoadd = $descriptiontype;
|
|
} else {
|
|
$paramtoadd = 'multiple:'.$param;
|
|
}
|
|
|
|
webservice_lib::add_end_of_description($paramtoadd, $desctoadd, $description[$method->getName()], $descriptionpath);
|
|
|
|
} else {
|
|
$desctoadd = webservice_lib::create_end_of_descriptionline($separator.$otherparam, $matches[2][$i]);
|
|
if(empty($descriptionpath)) {
|
|
|
|
if (empty($description[$method->getName()]) || !array_key_exists($descriptiontype, $description[$method->getName()])) {
|
|
$desctoadd = array($descriptiontype => $desctoadd);
|
|
}
|
|
$paramtoadd = $descriptiontype;
|
|
|
|
} else {
|
|
$paramtoadd = $param;
|
|
}
|
|
webservice_lib::add_end_of_description($paramtoadd, $desctoadd, $description[$method->getName()], $descriptionpath);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
// echo "<pre>";
|
|
// var_dump($description);
|
|
// echo "</pre>";
|
|
return $description;
|
|
|
|
}
|
|
|
|
/**
|
|
* Add a description part to the descripition array
|
|
* @param <type> $param
|
|
* @param <type> $desctoadd
|
|
* @param <type> $descriptionlevel
|
|
* @param <type> $descriptionpath
|
|
* @param <type> $level
|
|
*/
|
|
public static function add_end_of_description($param, $desctoadd, &$descriptionlevel, $descriptionpath, $level= 0){
|
|
if (sizeof($descriptionpath)==0 || sizeof($descriptionpath)==$level+1) {
|
|
|
|
if (is_array($descriptionlevel) && !empty($descriptionlevel)) {
|
|
foreach($desctoadd as $key=>$value) {
|
|
if ($key!="params" && $key!="optional" && $key!="return") { //TODO
|
|
$descriptionlevel[$param][$key] = $value;
|
|
} else {
|
|
$descriptionlevel[$param] = $value;
|
|
}
|
|
}
|
|
} else {
|
|
$descriptionlevel = $desctoadd;
|
|
}
|
|
} else {
|
|
webservice_lib::add_end_of_description($param, $desctoadd, &$descriptionlevel[$descriptionpath[$level]], $descriptionpath, $level+1);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* We create a description part for the description array
|
|
* Structure explained in the "generate_webservice_description" dockblock
|
|
* @param <type> $stringtoadd
|
|
* @param <type> $type
|
|
* @return <type>
|
|
*/
|
|
public static function create_end_of_descriptionline($stringtoadd, $type) {
|
|
|
|
if (strrpos($stringtoadd, "->")===false || (strrpos($stringtoadd, ":")!==false && (strrpos($stringtoadd, ":") > strrpos($stringtoadd, "->")))) {
|
|
$separator = ":";
|
|
$separatorsize=1;
|
|
} else {
|
|
$separator = "->";
|
|
$separatorsize=2;
|
|
}
|
|
|
|
$param = substr($stringtoadd,strrpos($stringtoadd, $separator)+$separatorsize);
|
|
$result = array( $param => $type);
|
|
|
|
$otherparam = substr($stringtoadd,0,strlen($stringtoadd)-strlen($param)-$separatorsize);
|
|
|
|
while(strrpos($otherparam, ":")!==false || strrpos($otherparam, "->")!==false) {
|
|
if (strrpos($otherparam, "->")===false || (strrpos($otherparam, ":")!==false && (strrpos($otherparam, ":") > strrpos($otherparam, "->")))) {
|
|
$separator = ":";
|
|
$separatorsize=1;
|
|
} else {
|
|
$separator = "->";
|
|
$separatorsize=2;
|
|
}
|
|
$param = substr($otherparam,strrpos($otherparam, $separator)+$separatorsize);
|
|
$otherparam = substr($otherparam,0,strrpos($otherparam, $separator));
|
|
|
|
if ($separator==":") {
|
|
$result = array('multiple:'.$param => $result);
|
|
} else {
|
|
$result = array($param => $result);
|
|
}
|
|
|
|
}
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
/**
|
|
* Check if the Moodle site has the web service protocol enable
|
|
* @global object $CFG
|
|
* @param string $protocol
|
|
*/
|
|
function display_webservices_availability($protocol){
|
|
global $CFG;
|
|
|
|
$available = true;
|
|
|
|
echo get_string('webservicesenable','webservice').": ";
|
|
if (empty($CFG->enablewebservices)) {
|
|
echo "<strong style=\"color:red\">".get_string('fail','webservice')."</strong>";
|
|
$available = false;
|
|
} else {
|
|
echo "<strong style=\"color:green\">".get_string('ok','webservice')."</strong>";
|
|
}
|
|
echo "<br/>";
|
|
|
|
foreach(webservice_lib::get_list_protocols() as $wsprotocol) {
|
|
if (strtolower($wsprotocol->get_protocolname()) == strtolower($protocol)) {
|
|
echo get_string('protocolenable','webservice',array($wsprotocol->get_protocolname())).": ";
|
|
if ( get_config($wsprotocol-> get_protocolname(), "enable")) {
|
|
echo "<strong style=\"color:green\">".get_string('ok','webservice')."</strong>";
|
|
} else {
|
|
echo "<strong style=\"color:red\">".get_string('fail','webservice')."</strong>";
|
|
$available = false;
|
|
}
|
|
echo "<br/>";
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//check debugging
|
|
if ($CFG->debugdisplay) {
|
|
echo "<strong style=\"color:red\">".get_string('debugdisplayon','webservice')."</strong>";
|
|
$available = false;
|
|
}
|
|
|
|
return $available;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Web Service server base class
|
|
*/
|
|
abstract class webservice_server {
|
|
|
|
/**
|
|
* Web Service Protocol name (eg. SOAP, REST, XML-RPC,...)
|
|
* @var String
|
|
*/
|
|
private $protocolname;
|
|
|
|
public function __construct() {
|
|
}
|
|
|
|
abstract public function run();
|
|
|
|
public function get_protocolname() {
|
|
return $this->protocolname;
|
|
}
|
|
|
|
public function set_protocolname($protocolname) {
|
|
$this->protocolname = $protocolname;
|
|
}
|
|
|
|
public function get_enable() {
|
|
return get_config($this->get_protocolname(), "enable");
|
|
}
|
|
|
|
public function set_enable($enable) {
|
|
set_config("enable", $enable, $this->get_protocolname());
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Temporary authentication class to be removed/modified
|
|
*/
|
|
class ws_authentication {
|
|
/**
|
|
*
|
|
* @param array|struct $params
|
|
* @return integer
|
|
*/
|
|
function tmp_get_token($params) {
|
|
if ($params['username'] == 'wsuser' && $params['password'] == 'wspassword') {
|
|
return '456';
|
|
} else {
|
|
throw new moodle_exception('wrongusernamepassword');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Form for web service user settings (administration)
|
|
*/
|
|
final class wsuser_form extends moodleform {
|
|
protected $username;
|
|
|
|
/**
|
|
* Definition of the moodleform
|
|
*/
|
|
public function definition() {
|
|
global $DB;
|
|
$this->username = $this->_customdata['username'];
|
|
$mform =& $this->_form;
|
|
|
|
$strrequired = get_string('required');
|
|
|
|
$mform->addElement('hidden', 'username', $this->username);
|
|
$param = new stdClass();
|
|
$param->username = $this->username;
|
|
$wsuser = $DB->get_record("user", array("username" => $this->username));
|
|
|
|
$mform->addElement('text', 'ipwhitelist', get_string('ipwhitelist', 'admin'), array('value'=>get_user_preferences("ipwhitelist", "", $wsuser->id),'size' => '40'));
|
|
$mform->addElement('static', null, '', get_string('ipwhitelistdesc','admin', $param));
|
|
|
|
$this->add_action_buttons(true, get_string('savechanges','admin'));
|
|
}
|
|
}
|
|
|
|
?>
|