Commit d4c6ef70 authored by jerome mouneyrac's avatar jerome mouneyrac
Browse files

webservice MDL-20803 add web service documentation generator

parent 11c33a94
......@@ -43,7 +43,7 @@ class moodle_group_external extends external_api {
'description' => new external_value(PARAM_RAW, 'group description text'),
'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'),
)
)
), 'List of group object. A group has a courseid, a name, a description and an enrolment key.'
)
)
);
......@@ -103,7 +103,7 @@ class moodle_group_external extends external_api {
'description' => new external_value(PARAM_RAW, 'group description text'),
'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'),
)
)
), 'List of group object. A group has an id, a courseid, a name, a description and an enrolment key.'
);
}
......@@ -114,7 +114,8 @@ class moodle_group_external extends external_api {
public static function get_groups_parameters() {
return new external_function_parameters(
array(
'groupids' => new external_multiple_structure(new external_value(PARAM_INT, 'Group ID')),
'groupids' => new external_multiple_structure(new external_value(PARAM_INT, 'Group ID')
,'List of group id. A group id is an integer.'),
)
);
}
......
......@@ -6,12 +6,17 @@ $string['addfunctionhelp'] = 'Select the function to add to the service.';
$string['addrequiredcapability'] = 'Assign/Unassign the required capability';
$string['addservice'] = 'Add a new service: $a->name (id: $a->id)';
$string['actwebserviceshhdr'] = 'Active web service protocols';
$string['apiexplorer'] = 'API explorer';
$string['apiexplorernotavalaible'] = 'API explorer not available yet.';
$string['arguments'] = 'Arguments';
$string['configwebserviceplugins'] = 'For security reasons enable only protocols that are used.';
$string['deleteservice'] = 'Delete the service: $a->name (id: $a->id)';
$string['deleteserviceconfirm'] = 'Do you really want to delete external service \"$a\"?';
$string['disabledwarning'] = 'All webs service protocols are disabled, the \Enable web services\" setting can be found in the \"Advanced features\" section.';
$string['editservice'] = 'Edit the service: $a->name (id: $a->id)';
$string['enabled'] = 'Enabled';
$string['error'] = 'Error: $a';
$string['errorcodes'] = 'Error Codes';
$string['execute'] = 'Execute';
$string['executewarnign'] = 'WARNING: if you press execute your database will be modified and changes can not be reverted automatically!';
$string['externalservices'] = 'External services';
......@@ -22,15 +27,21 @@ $string['function'] = 'Function';
$string['functions'] = 'Functions';
$string['iprestriction'] = 'IP restriction';
$string['manageprotocols'] = 'Manage protocols';
$string['noerrorcode'] = 'No error code';
$string['norequiredcapability'] = 'No required capability';
$string['optional'] = 'Optional';
$string['potusers'] = 'Not authorised users';
$string['potusersmatching'] = 'Not authorised users matching';
$string['protocol'] = 'Protocol';
$string['removefunction'] = 'Remove';
$string['removefunctionconfirm'] = 'Do you really want to remove function \"$a->function\" from service \"$a->service\"?';
$string['requireauthentication'] = 'This method requires authentication with xxx permission.';
$string['required'] = 'Required';
$string['requiredcapability'] = 'Required capability';
$string['selectedcapabilitydoesntexit'] = 'The currently set required capability ($a) doesn\'t exist anymore. Please change it and save the changes.';
$string['response'] = 'Response';
$string['restcode'] = 'REST code';
$string['restrictedusers'] = 'Authorised users only';
$string['selectedcapabilitydoesntexit'] = 'The currently set required capability ($a) doesn\'t exist anymore. Please change it and save the changes.';
$string['selectedcapability'] = 'Selected';
$string['servicename'] = 'Service name';
$string['servicesbuiltin'] = 'Built-in services';
......@@ -41,3 +52,9 @@ $string['serviceuserssettings'] = 'Change settings for the authorised users';
$string['testclient'] = 'Web service test client';
$string['validuntil'] = 'Valid until';
$string['webservices'] = 'Web services';
$string['wsdocumentation'] = 'Web service documentation';
$string['wsdocumentationintro'] = 'Following a listing of web service functions available for the username <b>$a</b>.<br/>In order to create a client we advice you to read the <a href=\"http://docs.moodle.org/en/Development:Creating_a_web_service_and_a_web_service_function#Create_your_own_client\">Moodle documentation</a>';
$string['wsdocumentationlogin'] = 'Enter your web service username and password.';
$string['wspassword'] = 'Web service password';
$string['wsusername'] = 'Web service username';
$string['xmlrpcstructure'] = 'XML-RPC structure';
......@@ -19,115 +19,260 @@
// //
///////////////////////////////////////////////////////////////////////////
// TODO: this needs to be rewritten to use the new description format
// the problem here is that the list of functions is different for each use or even token
// I guess this should be moved to server itself and it should require user auth,
// SOAP does already support WSDL when parameters &wsdl=1 used
die('TODO');
/**
* This file generate a web service documentation in HTML
* This documentation describe how to call a Moodle Web Service
*/
// disable moodle specific debug messages and any errors in output
define('NO_DEBUG_DISPLAY', true);
define('NO_MOODLE_COOKIES', true);
require_once('../config.php');
require_once('./wsdocrenderer.php');
require_once('lib.php');
$protocol = optional_param('protocol',"soap",PARAM_ALPHA);
$username = optional_param('username',"",PARAM_ALPHA);
$password = optional_param('password',"",PARAM_ALPHA);
/// TODO Retrieve user (authentication)
$user = "";
/// PAGE settings
$PAGE->set_course($COURSE);
$PAGE->set_url('webservice/wsdoc.php');
$PAGE->set_title(get_string('wspagetitle', 'webservice'));
$PAGE->set_heading(get_string('wspagetitle', 'webservice'));
$PAGE->set_generaltype("form");
/**
* This class generate the web service documentation specific to one
* web service user
* @package webservice
* @copyright 2009 Moodle Pty Ltd (http://moodle.com)
* @author Jerome Mouneyrac
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class webservice_documentation_generator {
/** @property array all external function description
* they */
protected $functions;
/** @property string $username name of local user */
protected $username = null;
// Display the documentation
echo $OUTPUT->header();
generate_documentation($protocol); //documentation relatif to the protocol
generate_functionlist($protocol, $user); //documentation relatif to the available function
echo $OUTPUT->footer();
/** @property string $password password of the local user */
protected $password = null;
/**
* Contructor
*/
public function __construct() {
$this->functionsdescriptions = array();
$this->functions = array();
}
function generate_functionlist($protocol, $user) {
/**
* Run the documentation generation
* @param bool $simple use simple authentication
* @return void
*/
public function run() {
/// retrieve all function that the user can access
/// =>
/// retrieve all function that are available into enable services that
/// have (no restriction user or the user is into the restricted user list)
/// and (no required capability or the user has the required capability)
// init all properties from the request data
$this->get_authentication_parameters();
// do SQL request here
// this sets up $USER TODO: and $SESSION for the environment.php
try {
$this->authenticate_user();
} catch(moodle_exception $e) {
$errormessage = $e->debuginfo;
$displayloginpage = true;
}
/// load once all externallib.php of the retrieved functions
if (!empty($displayloginpage)){
$this->display_login_page_html($errormessage);
} else {
// make a descriptions list of all function that user is allowed to excecute
$this->generate_documentation();
/// foreach retrieved functions display the description
//finally display the documentation
$this->display_documentation_html();
}
// in order to display the description we need to use an algo similar to the validation
// every time we get a scalar value, we need to convert it into a human readable value as
// PARAM_INT => 'integer' or PARAM_TEXT => 'string' or PARAM_BOOL => 'boolean' ...
die;
}
}
///////////////////////////
/////// CLASS METHODS /////
///////////////////////////
/**
* Generate documentation specific to a protocol
* @param string $protocol
*/
function generate_documentation($protocol) {
switch ($protocol) {
case "soap":
$documentation = get_string('soapdocumentation','webservice');
break;
case "xmlrpc":
$documentation = get_string('xmlrpcdocumentation','webservice');
break;
default:
break;
/**
* This method parses the $_REQUEST superglobal and looks for
* the following information:
* user authentication - username+password
* @return void
*/
protected function get_authentication_parameters() {
if (isset($_REQUEST['wsusername'])) {
$this->username = $_REQUEST['wsusername'];
}
if (isset($_REQUEST['wspassword'])) {
$this->password = $_REQUEST['wspassword'];
}
}
echo $documentation;
echo "<strong style=\"color:orange\">".get_string('wsuserreminder','webservice')."</strong>";
}
/**
* Generate the documentation specific to the auhenticated webservice user
* @return void
*/
protected function generate_documentation() {
global $USER, $DB;
/// first of all get a complete list of services user is allowed to access
$params = array();
$wscond1 = '';
$wscond2 = '';
// make sure the function is listed in at least one service user is allowed to use
// allow access only if:
// 1/ entry in the external_services_users table if required
// 2/ validuntil not reached
// 3/ has capability if specified in service desc
// 4/ iprestriction
$sql = "SELECT s.*, NULL AS iprestriction
FROM {external_services} s
JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 0)
WHERE s.enabled = 1 $wscond1
UNION
SELECT s.*, su.iprestriction
FROM {external_services} s
JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 1)
JOIN {external_services_users} su ON (su.externalserviceid = s.id AND su.userid = :userid)
WHERE s.enabled = 1 AND su.validuntil IS NULL OR su.validuntil < :now $wscond2";
$params = array_merge($params, array('userid'=>$USER->id, 'now'=>time()));
$serviceids = array();
$rs = $DB->get_recordset_sql($sql, $params);
// make sure user may access at least one service
$remoteaddr = getremoteaddr();
$allowed = false;
foreach ($rs as $service) {
if (isset($serviceids[$service->id])) {
continue;
}
if ($service->requiredcapability and !has_capability($service->requiredcapability, $this->restricted_context)) {
continue; // cap required, sorry
}
if ($service->iprestriction and !address_in_subnet($remoteaddr, $service->iprestriction)) {
continue; // wrong request source ip, sorry
}
$serviceids[$service->id] = $service->id;
}
$rs->close();
// now get the list of all functions
if ($serviceids) {
list($serviceids, $params) = $DB->get_in_or_equal($serviceids);
$sql = "SELECT f.*
FROM {external_functions} f
WHERE f.name IN (SELECT sf.functionname
FROM {external_services_functions} sf
WHERE sf.externalserviceid $serviceids)";
$functions = $DB->get_records_sql($sql, $params);
} else {
$functions = array();
}
foreach ($functions as $function) {
$this->functions[$function->name] = external_function_info($function);
}
}
/**
* Authenticate user using username+password
* This function sets up $USER global.
* called into the Moodle header
* @return void
*/
protected function authenticate_user() {
global $CFG, $DB, $USER;
if (!NO_MOODLE_COOKIES) {
throw new coding_exception('Cookies must be disabled!');
}
if (!is_enabled_auth('webservice')) {
throw new webservice_access_exception('WS auth not enabled');
}
if (!$auth = get_auth_plugin('webservice')) {
throw new webservice_access_exception('WS auth missing');
}
if (!$this->username) {
throw new webservice_access_exception('Missing username');
}
if (!$this->password) {
throw new webservice_access_exception('Missing password');
}
if (!$auth->user_login_webservice($this->username, $this->password)) {
throw new webservice_access_exception('Wrong username or password');
}
$USER = $DB->get_record('user', array('username'=>$this->username, 'mnethostid'=>$CFG->mnet_localhost_id, 'deleted'=>0), '*', MUST_EXIST);
}
////////////////////////////////////////////////
///// DISPLAY METHODS /////
////////////////////////////////////////////////
/**
* Generate and display the documentation
*/
protected function display_documentation_html() {
global $PAGE, $OUTPUT, $SITE;
$PAGE->set_url('/webservice/wsdoc');
$PAGE->set_docs_path('');
$PAGE->set_title($SITE->fullname." ".get_string('wsdocumentation', 'webservice'));
$PAGE->set_heading($SITE->fullname." ".get_string('wsdocumentation', 'webservice'));
$PAGE->set_generaltype('popup');
echo $OUTPUT->header();
$renderer = $PAGE->theme->get_renderer('core_wsdoc',$OUTPUT);
echo $renderer->documentation_html($this->functions, $this->username);
echo $OUTPUT->footer();
}
/**
* Display login page to the web service documentation
* @global <type> $PAGE
* @global <type> $OUTPUT
* @global <type> $SITE
* @global <type> $CFG
* @param string $errormessage error message displayed if wrong login
*/
protected function display_login_page_html($errormessage) {
global $PAGE, $OUTPUT, $SITE, $CFG;
$PAGE->set_url('/webservice/wsdoc');
$PAGE->set_docs_path('');
$PAGE->set_title($SITE->fullname." ".get_string('wsdocumentation', 'webservice'));
$PAGE->set_heading($SITE->fullname." ".get_string('wsdocumentation', 'webservice'));
$PAGE->set_generaltype('popup');
echo $OUTPUT->header();
$renderer = $PAGE->theme->get_renderer('core_wsdoc',$OUTPUT);
echo $renderer->login_page_html($errormessage);
echo $OUTPUT->footer();
/**
* Convert a Moodle type (PARAM_ALPHA, PARAM_NUMBER,...) as a SOAP type (string, interger,...)
* @param integer $moodleparam
* @return string SOAP type
*/
function converterMoodleParamIntoWsParam($moodleparam) {
switch ($moodleparam) {
case PARAM_NUMBER:
return "integer";
break;
case PARAM_INT:
return "integer";
break;
case PARAM_BOOL:
return "boolean";
break;
case PARAM_ALPHANUM:
return "string";
break;
case PARAM_ALPHA:
return "string";
break;
case PARAM_RAW:
return "string";
break;
case PARAM_ALPHANUMEXT:
return "string";
break;
case PARAM_NOTAGS:
return "string";
break;
case PARAM_TEXT:
return "string";
break;
}
}
///////////////////////////
/////// RUN THE SCRIPT ////
///////////////////////////
//run the documentation generator
$generator = new webservice_documentation_generator();
$generator->run();
die;
<?php
///////////////////////////////////////////////////////////////////////////
// //
// This file is part of Moodle - http://moodle.org/ //
// Moodle - Modular Object-Oriented Dynamic Learning Environment //
// //
// Moodle 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 3 of the License, or //
// (at your option) any later version. //
// //
// Moodle 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. //
// //
// You should have received a copy of the GNU General Public License //
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. //
// //
///////////////////////////////////////////////////////////////////////////
/**
* Web service documentation renderer.
* @package webservice
* @copyright 2009 Moodle Pty Ltd (http://moodle.com)
* @author Jerome Mouneyrac
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class moodle_core_wsdoc_renderer extends moodle_renderer_base {
/**
* Create documentation for a description object
* @param object $params a part of parameter/return description
* @return string the html to display
*/
public function detailed_html_description($params) {
$paramdesc = "";
if (!empty($params->desc)) {
$paramdesc = "<span style=\"color:#2A33A6\"><i>//".$params->desc."</i></span><br/>";
}
if ($params instanceof external_multiple_structure) {
return $paramdesc."list of ( <br/>". $this->detailed_html_description($params->content).")";
} else if ($params instanceof external_single_structure) {
//var_dump($params->keys);
$singlestructuredesc = $paramdesc."object {<br/>";
foreach ($params->keys as $attributname => $attribut) {
$singlestructuredesc .= "<b>".$attributname."</b> ".$this->detailed_html_description($params->keys[$attributname]);
}
$singlestructuredesc .= "} <br/>";
return $singlestructuredesc;
} else {
switch($params->type) {
case PARAM_BOOL: // 0 or 1 only for now
case PARAM_INT:
$type = 'int';
break;
case PARAM_FLOAT;
$type = 'double';
break;
default:
$type = 'string';
}
return $type." ".$paramdesc;
}
}
/**
* Create description in indented xml format
* It is indented in order to be displayed into <pre> tag
* @param object $returndescription
* @param string $indentation composed by space only
* @return string the html to diplay
*/
public function description_in_indented_xml_format($returndescription, $indentation = "") {
$indentation = $indentation . " ";
$brakeline = <<<EOF
EOF;
if ($returndescription instanceof external_multiple_structure) {
$return = $indentation."<MULTIPLE>".$brakeline;
$return .= $this->description_in_indented_xml_format($returndescription->content, $indentation);
$return .= $indentation."</MULTIPLE>".$brakeline;
return $return;
} else if ($returndescription instanceof external_single_structure) {
$singlestructuredesc = $indentation."<SINGLE>".$brakeline;
$keyindentation = $indentation." ";
foreach ($returndescription->keys as $attributname => $attribut) {
$singlestructuredesc .= $keyindentation."<KEY name=\"".$attributname."\">".$brakeline.
$this->description_in_indented_xml_format($returndescription->keys[$attributname], $keyindentation).
$keyindentation."</KEY>".$brakeline;
}
$singlestructuredesc .= $indentation."</SINGLE>".$brakeline;
return $singlestructuredesc;
} else {
switch($returndescription->type) {
case PARAM_BOOL: // 0 or 1 only for now
case PARAM_INT:
$type = 'int';
break;
case PARAM_FLOAT;
$type = 'double';
break;
default:
$type = 'string';
}
return $indentation."<VALUE>".$type."</VALUE>".$brakeline;
}
}
/**
* Return the REST response (xml code display in <pre> tag)
* @param string $functionname
* @param object $returndescription
* @return string the html to diplay
*/
public function rest_response_html($functionname, $returndescription) {
$restresponsehtml = "";
$restresponsehtml .= "<pre>";
$restresponsehtml .= "<div style=\"border:solid 1px #DEDEDE;background:#FEEBE5;color:#222222;padding:4px;\">";
$restresponsehtml .= '<b>REST code</b><br/>';
$brakeline = <<<EOF
EOF;
$content = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>".$brakeline."<RESPONSE>".$brakeline;
$content .= $this->description_in_indented_xml_format($returndescription);
$content .="</RESPONSE>".$brakeline;
$restresponsehtml .= htmlentities($content);
$restresponsehtml .= "</div>";
$restresponsehtml .= "</pre>";
return $restresponsehtml;
}
/**
* This display all the documentation
* @param array $functions contains all decription objects
* @param string $username
* @return string the html to diplay
*/
public function documentation_html($functions, $username) {
$documentationhtml = "";
$documentationhtml .= "<table style=\"margin-left:auto; margin-right:auto;\"><tr><td>";
$documentationhtml .= get_string('wsdocumentationintro', 'webservice', $username);
$documentationhtml .= "<br/><br/><br/>";
foreach ($functions as $functionname => $description) {
$documentationhtml .= print_collapsible_region_start('', 'aera_'.$functionname,"<strong>".$functionname."</strong>",false,true,true);
$documentationhtml .= "<br/>";
$documentationhtml .= "<div style=\"border:solid 1px #DEDEDE;background:#E2E0E0;color:#222222;padding:4px;\">";
$documentationhtml .= $description->description;
$documentationhtml .= "</div>";
$documentationhtml .= "<br/><br/>";
$documentationhtml .= "<span style=\"color:#EA33A6\">Authentication</span><br/>";
$documentationhtml .= "<span style=\"font-size:80%\">";
$documentationhtml .= get_string('requireauthentication', 'webservice'/*,$description->type*/);
$documentationhtml .= "</span>";
$documentationhtml .= "<br/><br/>";
$documentationhtml .= "<span style=\"color:#EA33A6\">".get_string('arguments', 'webservice')."</span><br/>";
foreach ($description->parameters_desc->keys as $paramname => $paramdesc) {
$documentationhtml .= "<span style=\"font-size:80%\">";
$required = $paramdesc->required?get_string('required', 'webservice'):get_string('optional', 'webservice');
$documentationhtml .= "<b>".$paramname . "</b> (" .$required. ")<br/>";
$documentationhtml .= "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;".$paramdesc->desc." <br/><br/>";
$documentationhtml .= "<div style=\"border:solid 1px #DEDEDE;background:#FFF1BC;color:#222222;padding:4px;\">";
$documentationhtml .= "<pre>";
$documentationhtml .= print_collapsible_region_start('', 'aera_'.$functionname."_".$paramname,'<b>'.get_string('xmlrpcstructure', 'webservice').'</b>',false,true,true);
//echo '<b>'.get_string('xmlrpcstructure', 'webservice').'</b><br/>';
$documentationhtml .= $this->detailed_html_description($paramdesc);
$documentationhtml .= print_collapsible_region_end(true);
$documentationhtml .= "</pre>";
$documentationhtml .= "</div><br/>";
$documentationhtml .= "<pre>";
$documentationhtml .= "<div style=\"border:solid 1px #DEDEDE;background:#FEEBE5;color:#222222;padding:4px;\">";
$documentationhtml .= '<b>'.get_string('restcode', 'webservice').'</b><br/>';
$documentationhtml .= htmlentities($this->description_in_indented_xml_format($paramdesc));
$documentationhtml .= "</div>";
$documentationhtml .= "</pre>";
$documentationhtml .= "</span>";
}
$documentationhtml .= "<br/><br/>";