first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
namespace core_webservice\privacy;
/**
* Privacy provider class for the core_webservice component.
*
* @package core_webservice
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the lang string identifier for the reason that no data is returned by the Privacy API.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+100
View File
@@ -0,0 +1,100 @@
<?php
// This file is part of Moodle - https://moodle.org/
//
// 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/>.
/**
* Provides the {@see core_webservice\token_filter} class.
*
* @package core_webservice
* @copyright 2020 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_webservice;
use moodleform;
/**
* Form allowing to filter displayed tokens.
*
* @copyright 2020 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class token_filter extends moodleform {
/**
* Defines the form fields.
*/
public function definition() {
global $DB;
$mform = $this->_form;
$presetdata = $this->_customdata;
$mform->addElement('header', 'tokenfilter', get_string('tokenfilter', 'webservice'));
if (empty($presetdata->token) && empty($presetdata->users) && empty($presetdata->services)) {
$mform->setExpanded('tokenfilter', false);
} else {
$mform->setExpanded('tokenfilter', true);
}
// Token name.
$mform->addElement('text', 'name', get_string('tokenname', 'core_webservice'), ['size' => 32]);
$mform->setType('name', PARAM_TEXT);
// User selector.
$attributes = [
'multiple' => true,
'ajax' => 'core_user/form_user_selector',
'valuehtmlcallback' => function($userid) {
global $DB, $OUTPUT;
$context = \context_system::instance();
$fields = \core_user\fields::for_name()->with_identity($context, false);
$record = \core_user::get_user($userid, 'id' . $fields->get_sql()->selects, MUST_EXIST);
$user = (object)[
'id' => $record->id,
'fullname' => fullname($record, has_capability('moodle/site:viewfullnames', $context)),
'extrafields' => [],
];
foreach ($fields->get_required_fields([\core_user\fields::PURPOSE_IDENTITY]) as $extrafield) {
$user->extrafields[] = (object)[
'name' => $extrafield,
'value' => s($record->$extrafield)
];
}
return $OUTPUT->render_from_template('core_user/form_user_selector_suggestion', $user);
},
];
$mform->addElement('autocomplete', 'users', get_string('user'), [], $attributes);
// Service selector.
$options = $DB->get_records_menu('external_services', null, '', 'id, name');
$attributes = [
'multiple' => true,
];
$mform->addElement('autocomplete', 'services', get_string('service', 'webservice'), $options, $attributes);
// Action buttons.
$mform->addGroup([
$mform->createElement('submit', 'submitbutton', get_string('tokenfiltersubmit', 'core_webservice')),
$mform->createElement('submit', 'resetbutton', get_string('tokenfilterreset', 'core_webservice'), [], false),
], 'actionbuttons', '', ' ', false);
}
}
+131
View File
@@ -0,0 +1,131 @@
<?php
// This file is part of Moodle - https://moodle.org/
//
// 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/>.
/**
* Provides the {@see \core_webservice\token_form} class.
*
* @package core_webservice
* @category admin
* @copyright 2020 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_webservice;
use core_user;
use DateInterval;
use DateTime;
/**
* Form to create and edit a web service token.
*
* Tokens allow users call external functions provided by selected web services. They can optionally have IP restriction
* and date validity defined.
*
* @copyright 2010 Jerome Mouneyrac <jerome@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class token_form extends \moodleform {
/**
* Defines the form fields.
*/
public function definition() {
global $DB;
$mform = $this->_form;
$data = $this->_customdata;
$mform->addElement('header', 'token', get_string('token', 'webservice'));
$mform->addElement('text', 'name', get_string('tokenname', 'webservice'));
$mform->setType('name', PARAM_TEXT);
$mform->addElement('static', 'tokennamehint', '', get_string('tokennamehint', 'webservice'));
// User selector.
$attributes = [
'multiple' => false,
'ajax' => 'core_user/form_user_selector',
'valuehtmlcallback' => function($userid) {
global $OUTPUT;
$context = \context_system::instance();
$fields = \core_user\fields::for_name()->with_identity($context, false);
$record = core_user::get_user($userid, 'id ' . $fields->get_sql()->selects, MUST_EXIST);
$user = (object)[
'id' => $record->id,
'fullname' => fullname($record, has_capability('moodle/site:viewfullnames', $context)),
'extrafields' => [],
];
foreach ($fields->get_required_fields([\core_user\fields::PURPOSE_IDENTITY]) as $extrafield) {
$user->extrafields[] = (object)[
'name' => $extrafield,
'value' => s($record->$extrafield)
];
}
return $OUTPUT->render_from_template('core_user/form_user_selector_suggestion', $user);
},
];
$mform->addElement('autocomplete', 'user', get_string('user'), [], $attributes);
$mform->addRule('user', get_string('required'), 'required', null, 'client');
// Service selector.
$options = $DB->get_records_menu('external_services', null, '', 'id, name');
$mform->addElement('select', 'service', get_string('service', 'webservice'), $options);
$mform->addRule('service', get_string('required'), 'required', null, 'client');
$mform->setType('service', PARAM_INT);
$mform->addElement('text', 'iprestriction', get_string('iprestriction', 'webservice'));
$mform->setType('iprestriction', PARAM_RAW_TRIMMED);
$mform->addElement('date_selector', 'validuntil',
get_string('validuntil', 'webservice'), array('optional' => true));
// Expires in 30 days.
$expires = new DateTime();
$expires->add(new DateInterval("P30D"));
$mform->setDefault('validuntil', $expires->getTimestamp());
$mform->setType('validuntil', PARAM_INT);
$mform->addElement('hidden', 'action');
$mform->setType('action', PARAM_ALPHANUMEXT);
$this->add_action_buttons(true);
$this->set_data($data);
}
/**
* Validate the submitted data.
*
* @param array $data Submitted data.
* @param array $files Submitted files.
* @return array Validation errors.
*/
public function validation($data, $files) {
global $DB;
$errors = parent::validation($data, $files);
if ($DB->get_field('user', 'suspended', ['id' => $data['user']], MUST_EXIST)) {
$errors['user'] = get_string('suspended', 'core') . ' - ' . get_string('forbiddenwsuser', 'core_webservice');
}
return $errors;
}
}
+350
View File
@@ -0,0 +1,350 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* Contains the class used for the displaying the tokens table.
*
* @package core_webservice
* @copyright 2017 John Okely <john@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_webservice;
defined('MOODLE_INTERNAL') || die;
require_once($CFG->libdir . '/tablelib.php');
require_once($CFG->dirroot . '/webservice/lib.php');
require_once($CFG->dirroot . '/user/lib.php');
/**
* Class for the displaying the participants table.
*
* @package core_webservice
* @copyright 2017 John Okely <john@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class token_table extends \table_sql {
/**
* @var bool $showalltokens Whether or not the user is able to see all tokens.
*/
protected $showalltokens;
/** @var bool $hasviewfullnames Does the user have the viewfullnames capability. */
protected $hasviewfullnames;
/** @var array */
protected $userextrafields;
/** @var object */
protected $filterdata;
/**
* Sets up the table.
*
* @param int $id The id of the table
* @param object $filterdata The data submitted by the {@see token_filter}.
*/
public function __construct($id, $filterdata = null) {
parent::__construct($id);
// Get the context.
$context = \context_system::instance();
// Can we see tokens created by all users?
$this->showalltokens = has_capability('moodle/webservice:managealltokens', $context);
$this->hasviewfullnames = has_capability('moodle/site:viewfullnames', $context);
// List of user identity fields.
$this->userextrafields = \core_user\fields::get_identity_fields(\context_system::instance(), false);
// Filter form values.
$this->filterdata = $filterdata;
// Define the headers and columns.
$headers = [];
$columns = [];
$headers[] = get_string('tokenname', 'webservice');
$columns[] = 'name';
$headers[] = get_string('user');
$columns[] = 'fullname';
$headers[] = get_string('service', 'webservice');
$columns[] = 'servicename';
$headers[] = get_string('iprestriction', 'webservice');
$columns[] = 'iprestriction';
$headers[] = get_string('validuntil', 'webservice');
$columns[] = 'validuntil';
$headers[] = get_string('lastaccess');
$columns[] = 'lastaccess';
if ($this->showalltokens) {
// Only need to show creator if you can see tokens created by other people.
$headers[] = get_string('tokencreator', 'webservice');
$columns[] = 'creatorlastname'; // So we can have semi-useful sorting. Table SQL doesn't two fullname collumns.
}
$headers[] = get_string('operation', 'webservice');
$columns[] = 'operation';
$this->define_columns($columns);
$this->define_headers($headers);
$this->no_sorting('operation');
$this->no_sorting('token');
$this->no_sorting('iprestriction');
$this->set_attribute('id', $id);
}
/**
* Generate the operation column.
*
* @param \stdClass $data Data for the current row
* @return string Content for the column
*/
public function col_operation($data) {
$tokenpageurl = new \moodle_url(
"/admin/webservice/tokens.php",
[
"action" => "delete",
"tokenid" => $data->id
]
);
return \html_writer::link($tokenpageurl, get_string("delete"));
}
/**
* Generate the validuntil column.
*
* @param \stdClass $data Data for the current row
* @return string Content for the column
*/
public function col_validuntil($data) {
if (empty($data->validuntil)) {
return get_string('validuntil_empty', 'webservice');
} else {
return userdate($data->validuntil, get_string('strftimedatetime', 'langconfig'));
}
}
/**
* Generate the last access column
*
* @param \stdClass $data
* @return string
*/
public function col_lastaccess(\stdClass $data): string {
if (empty($data->lastaccess)) {
return get_string('never');
} else {
return userdate($data->lastaccess, get_string('strftimedatetime', 'langconfig'));
}
}
/**
* Generate the fullname column. Also includes capabilities the user is missing for the webservice (if any)
*
* @param \stdClass $data Data for the current row
* @return string Content for the column
*/
public function col_fullname($data) {
global $OUTPUT;
$identity = [];
foreach ($this->userextrafields as $userextrafield) {
$identity[] = s($data->$userextrafield);
}
$userprofilurl = new \moodle_url('/user/profile.php', ['id' => $data->userid]);
$content = \html_writer::link($userprofilurl, fullname($data, $this->hasviewfullnames));
if ($identity) {
$content .= \html_writer::div('<small>' . implode(', ', $identity) . '</small>', 'useridentity text-muted');
}
// Make up list of capabilities that the user is missing for the given webservice.
$webservicemanager = new \webservice();
$usermissingcaps = $webservicemanager->get_missing_capabilities_by_users([['id' => $data->userid]], $data->serviceid);
if ($data->serviceshortname <> MOODLE_OFFICIAL_MOBILE_SERVICE && !is_siteadmin($data->userid)
&& array_key_exists($data->userid, $usermissingcaps)) {
$count = \html_writer::span(count($usermissingcaps[$data->userid]), 'badge bg-danger text-white');
$links = array_map(function($capname) {
return get_capability_docs_link((object)['name' => $capname]) . \html_writer::div($capname, 'text-muted');
}, $usermissingcaps[$data->userid]);
$list = \html_writer::alist($links);
$help = $OUTPUT->help_icon('missingcaps', 'webservice');
$content .= print_collapsible_region(\html_writer::div($list . $help, 'missingcaps'), 'small mt-2',
\html_writer::random_id('usermissingcaps'), get_string('usermissingcaps', 'webservice', $count), '', true, true);
}
return $content;
}
/**
* Generate the token column.
*
* @param \stdClass $data Data for the current row
* @return string Content for the column
*
* @deprecated since Moodle 4.3 MDL-76656. Please do not use this function anymore.
* @todo MDL-78605 Final deprecation in Moodle 4.7.
*/
public function col_token($data) {
debugging('The function ' . __FUNCTION__ . '() is deprecated - please do not use it any more. ', DEBUG_DEVELOPER);
global $USER;
// Hide the token if it wasn't created by the current user.
if ($data->creatorid != $USER->id) {
return \html_writer::tag('small', get_string('onlyseecreatedtokens', 'core_webservice'), ['class' => 'text-muted']);
}
return $data->token;
}
/**
* Generate the name column.
*
* @param \stdClass $data Data for the current row
* @return string Content for the column
*/
public function col_name($data) {
return $data->name;
}
/**
* Generate the creator column.
*
* @param \stdClass $data
* @return string
*/
public function col_creatorlastname($data) {
// We have loaded all the name fields for the creator, with the 'creator' prefix.
// So just remove the prefix and make up a user object.
$user = [];
foreach ($data as $key => $value) {
if (strpos($key, 'creator') !== false) {
$newkey = str_replace('creator', '', $key);
$user[$newkey] = $value;
}
}
$creatorprofileurl = new \moodle_url('/user/profile.php', ['id' => $data->creatorid]);
return \html_writer::link($creatorprofileurl, fullname((object)$user, $this->hasviewfullnames));
}
/**
* Format the service name column.
*
* @param \stdClass $data
* @return string
*/
public function col_servicename($data) {
return \html_writer::div(s($data->servicename)) . \html_writer::div(s($data->serviceshortname), 'small text-muted');
}
/**
* This function is used for the extra user fields.
*
* These are being dynamically added to the table so there are no functions 'col_<userfieldname>' as
* the list has the potential to increase in the future and we don't want to have to remember to add
* a new method to this class. We also don't want to pollute this class with unnecessary methods.
*
* @param string $colname The column name
* @param \stdClass $data
* @return string
*/
public function other_cols($colname, $data) {
return s($data->{$colname});
}
/**
* Query the database for results to display in the table.
*
* Note: Initial bars are not implemented for this table because it includes user details twice and the initial bars do not work
* when the user table is included more than once.
*
* @param int $pagesize size of page for paginated displayed table.
* @param bool $useinitialsbar Not implemented. Please pass false.
*/
public function query_db($pagesize, $useinitialsbar = false) {
global $DB, $USER;
if ($useinitialsbar) {
debugging('Initial bar not implemented yet. Call out($pagesize, false)');
}
$userfieldsapi = \core_user\fields::for_name();
$usernamefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
$creatorfields = $userfieldsapi->get_sql('c', false, 'creator', '', false)->selects;
if (!empty($this->userextrafields)) {
$usernamefields .= ',u.' . implode(',u.', $this->userextrafields);
}
$params = ['tokenmode' => EXTERNAL_TOKEN_PERMANENT];
$selectfields = "SELECT t.id, t.name, t.iprestriction, t.validuntil, t.creatorid, t.lastaccess,
u.id AS userid, $usernamefields,
s.id AS serviceid, s.name AS servicename, s.shortname AS serviceshortname,
$creatorfields ";
$selectcount = "SELECT COUNT(t.id) ";
$sql = " FROM {external_tokens} t
JOIN {user} u ON u.id = t.userid
JOIN {external_services} s ON s.id = t.externalserviceid
JOIN {user} c ON c.id = t.creatorid
WHERE t.tokentype = :tokenmode";
if (!$this->showalltokens) {
// Only show tokens created by the current user.
$sql .= " AND t.creatorid = :userid";
$params['userid'] = $USER->id;
}
if ($this->filterdata->name !== '') {
$sql .= " AND " . $DB->sql_like("t.name", ":name", false, false);
$params['name'] = "%" . $DB->sql_like_escape($this->filterdata->name) . "%";
}
if (!empty($this->filterdata->users)) {
list($sqlusers, $paramsusers) = $DB->get_in_or_equal($this->filterdata->users, SQL_PARAMS_NAMED, 'user');
$sql .= " AND t.userid {$sqlusers}";
$params += $paramsusers;
}
if (!empty($this->filterdata->services)) {
list($sqlservices, $paramsservices) = $DB->get_in_or_equal($this->filterdata->services, SQL_PARAMS_NAMED, 'service');
$sql .= " AND s.id {$sqlservices}";
$params += $paramsservices;
}
$sort = $this->get_sql_sort();
$sortsql = '';
if ($sort) {
$sortsql = " ORDER BY {$sort}";
}
$total = $DB->count_records_sql($selectcount . $sql, $params);
$this->pagesize($pagesize, $total);
$this->rawdata = $DB->get_recordset_sql($selectfields . $sql . $sortsql, $params, $this->get_page_start(),
$this->get_page_size());
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* Entry point for web service via tokens access to draftfile.php.
*
* @package core
* @copyright 2020 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* AJAX_SCRIPT - exception will be converted into JSON.
*/
define('AJAX_SCRIPT', true);
/**
* NO_MOODLE_COOKIES - we don't want any cookie.
*/
define('NO_MOODLE_COOKIES', true);
require_once(__DIR__ . '/../config.php');
require_once($CFG->dirroot . '/webservice/lib.php');
// Allow CORS requests.
header('Access-Control-Allow-Origin: *');
// Authenticate the user.
$token = required_param('token', PARAM_ALPHANUM);
$webservicelib = new webservice();
$authenticationinfo = $webservicelib->authenticate_user($token);
// Check the service allows file download.
if (empty($authenticationinfo['service']->downloadfiles)) {
throw new webservice_access_exception('Web service file downloading must be enabled in external service settings.');
}
require_once($CFG->dirroot . '/draftfile.php');
+297
View File
@@ -0,0 +1,297 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
/**
* Web service related functions
*
* @package core_webservice
* @category external
* @copyright 2011 Jerome Mouneyrac <jerome@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.2
*/
class core_webservice_external extends \core_external\external_api {
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.2
*/
public static function get_site_info_parameters() {
return new external_function_parameters(
array('serviceshortnames' => new external_multiple_structure (
new external_value(
PARAM_ALPHANUMEXT,
'service shortname'),
'DEPRECATED PARAMETER - it was a design error in the original implementation. \
It is ignored now. (parameter kept for backward compatibility)',
VALUE_DEFAULT,
array()
),
)
);
}
/**
* Return user information including profile picture + basic site information
* Note:
* - no capability checking because we return only known information about logged user
*
* @param array $serviceshortnames - DEPRECATED PARAMETER - values will be ignored -
* it was an original design error, we keep for backward compatibility.
* @return array site info
* @since Moodle 2.2
*/
public static function get_site_info($serviceshortnames = array()) {
global $USER, $SITE, $CFG, $DB, $PAGE;
$params = self::validate_parameters(self::get_site_info_parameters(),
array('serviceshortnames'=>$serviceshortnames));
$context = context_user::instance($USER->id);
$systemcontext = context_system::instance();
$userpicture = new user_picture($USER);
$userpicture->size = 1; // Size f1.
$profileimageurl = $userpicture->get_url($PAGE);
// Site information.
$siteinfo = array(
'sitename' => \core_external\util::format_string($SITE->fullname, $systemcontext),
'siteurl' => $CFG->wwwroot,
'username' => $USER->username,
'firstname' => $USER->firstname,
'lastname' => $USER->lastname,
'fullname' => fullname($USER),
'lang' => clean_param(current_language(), PARAM_LANG),
'userid' => $USER->id,
'userpictureurl' => $profileimageurl->out(false),
'siteid' => SITEID
);
// Retrieve the service and functions from the web service linked to the token
// If you call this function directly from external (not a web service call),
// then it will still return site info without information about a service
// Note: wsusername/wspassword ws authentication is not supported.
$functions = array();
if ($CFG->enablewebservices) { // No need to check token if web service are disabled and not a ws call.
$token = optional_param('wstoken', '', PARAM_ALPHANUM);
if (!empty($token)) { // No need to run if not a ws call.
// Retrieve service shortname.
$servicesql = 'SELECT s.*
FROM {external_services} s, {external_tokens} t
WHERE t.externalserviceid = s.id AND token = ? AND t.userid = ? AND s.enabled = 1';
$service = $DB->get_record_sql($servicesql, array($token, $USER->id));
$siteinfo['downloadfiles'] = $service->downloadfiles;
$siteinfo['uploadfiles'] = $service->uploadfiles;
if (!empty($service)) {
// Return the release and version number for web service users only.
$siteinfo['release'] = $CFG->release;
$siteinfo['version'] = $CFG->version;
// Retrieve the functions.
$functionssql = "SELECT f.*
FROM {external_functions} f, {external_services_functions} sf
WHERE f.name = sf.functionname AND sf.externalserviceid = ?";
$functions = $DB->get_records_sql($functionssql, array($service->id));
} else {
throw new coding_exception('No service found in get_site_info: something is buggy, \
it should have fail at the ws server authentication layer.');
}
}
}
// Build up the returned values of the list of functions.
$componentversions = array();
$availablefunctions = array();
foreach ($functions as $function) {
$functioninfo = array();
$functioninfo['name'] = $function->name;
if ($function->component == 'moodle' || $function->component == 'core') {
$version = $CFG->version; // Moodle version.
} else {
$versionpath = core_component::get_component_directory($function->component).'/version.php';
if (is_readable($versionpath)) {
// We store the component version once retrieved (so we don't load twice the version.php).
if (!isset($componentversions[$function->component])) {
$plugin = new stdClass();
include($versionpath);
$componentversions[$function->component] = $plugin->version;
$version = $plugin->version;
} else {
$version = $componentversions[$function->component];
}
} else {
// Ignore this component or plugin, it was probably incorrectly uninstalled.
continue;
}
}
$functioninfo['version'] = $version;
$availablefunctions[] = $functioninfo;
}
$siteinfo['functions'] = $availablefunctions;
// Mobile CSS theme and alternative login url.
$siteinfo['mobilecssurl'] = !empty($CFG->mobilecssurl) ? $CFG->mobilecssurl : '';
// Retrieve some advanced features. Only enable/disable ones (bool).
$advancedfeatures = ["usecomments", "usetags", "enablenotes", "messaging", "enableblogs",
"enablecompletion", "enablebadges", "messagingallusers", "enablecustomreports", "enableglobalsearch"];
foreach ($advancedfeatures as $feature) {
if (isset($CFG->{$feature})) {
$siteinfo['advancedfeatures'][] = array(
'name' => $feature,
'value' => (int) $CFG->{$feature}
);
}
}
// Special case mnet_dispatcher_mode.
$siteinfo['advancedfeatures'][] = array(
'name' => 'mnet_dispatcher_mode',
'value' => ($CFG->mnet_dispatcher_mode == 'strict') ? 1 : 0
);
// Competencies.
$enablecompetencies = get_config('core_competency', 'enabled');
$siteinfo['advancedfeatures'][] = [
'name' => 'enablecompetencies',
'value' => (!empty($enablecompetencies)) ? 1 : 0,
];
// User can manage own files.
$siteinfo['usercanmanageownfiles'] = has_capability('moodle/user:manageownfiles', $context);
// User quota. 0 means user can ignore the quota.
$siteinfo['userquota'] = 0;
if (!has_capability('moodle/user:ignoreuserquota', $context)) {
$siteinfo['userquota'] = (int) $CFG->userquota; // Cast to int to ensure value is not higher than PHP_INT_MAX.
}
// User max upload file size. -1 means the user can ignore the upload file size.
// Cast to int to ensure value is not higher than PHP_INT_MAX.
$siteinfo['usermaxuploadfilesize'] = (int) get_user_max_upload_file_size($context, $CFG->maxbytes);
// User home page.
$siteinfo['userhomepage'] = get_home_page();
// Calendar.
$siteinfo['sitecalendartype'] = $CFG->calendartype;
if (empty($USER->calendartype)) {
$siteinfo['usercalendartype'] = $CFG->calendartype;
} else {
$siteinfo['usercalendartype'] = $USER->calendartype;
}
$siteinfo['userissiteadmin'] = is_siteadmin();
// User key, to avoid using the WS token for fetching assets.
$siteinfo['userprivateaccesskey'] = get_user_key('core_files', $USER->id);
// Current theme.
$siteinfo['theme'] = clean_param($PAGE->theme->name, PARAM_THEME); // We always clean to avoid problem with old sites.
$siteinfo['limitconcurrentlogins'] = (int) $CFG->limitconcurrentlogins;
if (!empty($CFG->limitconcurrentlogins)) {
// For performance, only when enabled.
$siteinfo['usersessionscount'] = $DB->count_records('sessions', ['userid' => $USER->id]);
}
$siteinfo['policyagreed'] = $USER->policyagreed;
return $siteinfo;
}
/**
* Returns description of method result value
*
* @return external_single_structure
* @since Moodle 2.2
*/
public static function get_site_info_returns() {
return new external_single_structure(
array(
'sitename' => new external_value(PARAM_RAW, 'site name'),
'username' => new external_value(PARAM_RAW, 'username'),
'firstname' => new external_value(PARAM_TEXT, 'first name'),
'lastname' => new external_value(PARAM_TEXT, 'last name'),
'fullname' => new external_value(PARAM_TEXT, 'user full name'),
'lang' => new external_value(PARAM_LANG, 'Current language.'),
'userid' => new external_value(PARAM_INT, 'user id'),
'siteurl' => new external_value(PARAM_RAW, 'site url'),
'userpictureurl' => new external_value(PARAM_URL, 'the user profile picture.
Warning: this url is the public URL that only works when forcelogin is set to NO and guestaccess is set to YES.
In order to retrieve user profile pictures independently of the Moodle config, replace "pluginfile.php" by
"webservice/pluginfile.php?token=WSTOKEN&file="
Of course the user can only see profile picture depending
on his/her permissions. Moreover it is recommended to use HTTPS too.'),
'functions' => new external_multiple_structure(
new external_single_structure(
array(
'name' => new external_value(PARAM_RAW, 'function name'),
'version' => new external_value(PARAM_TEXT,
'The version number of the component to which the function belongs')
), 'functions that are available')
),
'downloadfiles' => new external_value(PARAM_INT, '1 if users are allowed to download files, 0 if not',
VALUE_OPTIONAL),
'uploadfiles' => new external_value(PARAM_INT, '1 if users are allowed to upload files, 0 if not',
VALUE_OPTIONAL),
'release' => new external_value(PARAM_TEXT, 'Moodle release number', VALUE_OPTIONAL),
'version' => new external_value(PARAM_TEXT, 'Moodle version number', VALUE_OPTIONAL),
'mobilecssurl' => new external_value(PARAM_URL, 'Mobile custom CSS theme', VALUE_OPTIONAL),
'advancedfeatures' => new external_multiple_structure(
new external_single_structure(
array(
'name' => new external_value(PARAM_ALPHANUMEXT, 'feature name'),
'value' => new external_value(PARAM_INT, 'feature value. Usually 1 means enabled.')
),
'Advanced features availability'
),
'Advanced features availability',
VALUE_OPTIONAL
),
'usercanmanageownfiles' => new external_value(PARAM_BOOL,
'true if the user can manage his own files', VALUE_OPTIONAL),
'userquota' => new external_value(PARAM_INT,
'user quota (bytes). 0 means user can ignore the quota', VALUE_OPTIONAL),
'usermaxuploadfilesize' => new external_value(PARAM_INT,
'user max upload file size (bytes). -1 means the user can ignore the upload file size',
VALUE_OPTIONAL),
'userhomepage' => new external_value(PARAM_INT,
'the default home page for the user: 0 for the site home, 1 for dashboard',
VALUE_OPTIONAL),
'userprivateaccesskey' => new external_value(PARAM_ALPHANUM, 'Private user access key for fetching files.',
VALUE_OPTIONAL),
'siteid' => new external_value(PARAM_INT, 'Site course ID', VALUE_OPTIONAL),
'sitecalendartype' => new external_value(PARAM_PLUGIN, 'Calendar type set in the site.', VALUE_OPTIONAL),
'usercalendartype' => new external_value(PARAM_PLUGIN, 'Calendar typed used by the user.', VALUE_OPTIONAL),
'userissiteadmin' => new external_value(PARAM_BOOL, 'Whether the user is a site admin or not.', VALUE_OPTIONAL),
'theme' => new external_value(PARAM_THEME, 'Current theme for the user.', VALUE_OPTIONAL),
'limitconcurrentlogins' => new external_value(PARAM_INT, 'Number of concurrent sessions allowed', VALUE_OPTIONAL),
'usersessionscount' => new external_value(PARAM_INT, 'Number of active sessions for current user.
Only returned when limitconcurrentlogins is used.', VALUE_OPTIONAL),
'policyagreed' => new external_value(PARAM_INT, 'Whether user accepted all the policies.', VALUE_OPTIONAL),
)
);
}
}
+1846
View File
File diff suppressed because it is too large Load Diff
+63
View File
@@ -0,0 +1,63 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* A script to serve files from web service client
*
* @package core_webservice
* @copyright 2011 Dongsheng Cai <dongsheng@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* AJAX_SCRIPT - exception will be converted into JSON
*/
define('AJAX_SCRIPT', true);
/**
* NO_MOODLE_COOKIES - we don't want any cookie
*/
define('NO_MOODLE_COOKIES', true);
require_once(__DIR__ . '/../config.php');
require_once($CFG->libdir . '/filelib.php');
require_once($CFG->dirroot . '/webservice/lib.php');
// Allow CORS requests.
header('Access-Control-Allow-Origin: *');
// Authenticate the user.
$token = required_param('token', PARAM_ALPHANUM);
// Use preview in order to display the preview of the file (e.g. "thumb" for a thumbnail).
$preview = optional_param('preview', null, PARAM_ALPHANUM);
// Offline means download the file from the repository and serve it, even if it was an external link.
// The repository may have to export the file to an offline format.
$offline = optional_param('offline', 0, PARAM_BOOL);
$webservicelib = new webservice();
$authenticationinfo = $webservicelib->authenticate_user($token);
// Check the service allows file download.
$enabledfiledownload = (int) ($authenticationinfo['service']->downloadfiles);
if (empty($enabledfiledownload)) {
throw new webservice_access_exception('Web service file downloading must be enabled in external service settings');
}
// Finally we can serve the file :).
$relativepath = get_file_argument();
file_pluginfile($relativepath, 0, $preview, $offline);
+89
View File
@@ -0,0 +1,89 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* A script to display a ReCaptcha for the site.
*
* ReCaptcha V2 is restricted by domain, so it cannot be displayed in mobile and desktop apps.
*
* This script will display and initialize the reCaptcha, setting some empty callbacks by default. The client can override
* those Javascript callbacks (in the "window" object).
*
* This script won't work in mobile and desktop apps unless $CFG->allowframembedding is enabled.
*
* @package core_webservice
* @copyright 2018 Dani Palou
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('NO_MOODLE_COOKIES', true);
require_once(__DIR__ . '/../config.php');
$lang = optional_param('lang', '', PARAM_LANG);
$content = '';
// Check that reCAPTCHA is configured.
if (!empty($CFG->recaptchapublickey) && !empty($CFG->recaptchaprivatekey)) {
require_once($CFG->libdir . '/recaptchalib_v2.php');
$apiurl = RECAPTCHA_API_URL;
$pubkey = $CFG->recaptchapublickey;
$jscode = "
// Create empty callbacks by default. They can be overridden by the client.
var recaptchacallback = function() {};
var recaptchaexpiredcallback = function() {};
var recaptchaerrorcallback = function() {};
var recaptchaloaded = function() {
grecaptcha.render('recaptcha_element', {
'sitekey' : '$pubkey',
'callback' : 'recaptchacallback',
'expired-callback' : 'recaptchaexpiredcallback',
'error-callback' : 'recaptchaerrorcallback'
});
}";
$lang = recaptcha_lang($lang);
$apicode = "\n<script type=\"text/javascript\" ";
$apicode .= "src=\"$apiurl?onload=recaptchaloaded&render=explicit&hl=$lang\" async defer>";
$apicode .= "</script>\n";
$content = html_writer::script($jscode, '');
$content .= html_writer::div('', 'recaptcha_element', array('id' => 'recaptcha_element'));
$content .= $apicode;
} else {
// To use reCAPTCHA you must have an API key.
require_once($CFG->libdir . '/filelib.php');
send_header_404();
throw new \moodle_exception('cannotusepage2');
}
$output = <<<OET
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
</head>
<body style="margin:0; padding:0">
$content
</body>
</html>
OET;
echo $output;
+846
View File
@@ -0,0 +1,846 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
use core_external\external_api;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
/**
* Web service documentation renderer.
*
* @package core_webservice
* @category output
* @copyright 2009 Jerome Mouneyrac <jerome@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_webservice_renderer extends plugin_renderer_base {
/**
* Display the authorised user selector
*
* @param stdClass $options It contains alloweduserselector, potentialuserselector and serviceid
* @return string html
*/
public function admin_authorised_user_selector(&$options) {
global $CFG;
$formcontent = html_writer::empty_tag('input',
array('name' => 'sesskey', 'value' => sesskey(), 'type' => 'hidden'));
$table = new html_table();
$table->size = array('45%', '10%', '45%');
$table->attributes['class'] = 'roleassigntable generaltable generalbox boxaligncenter';
$table->summary = '';
$table->cellspacing = 0;
$table->cellpadding = 0;
// LTR/RTL support, for drawing button arrows in the right direction
if (right_to_left()) {
$addarrow = '▶';
$removearrow = '◀';
} else {
$addarrow = '◀';
$removearrow = '▶';
}
//create the add and remove button
$addinput = html_writer::empty_tag('input',
array('name' => 'add', 'id' => 'add', 'type' => 'submit',
'value' => $addarrow . ' ' . get_string('add'),
'title' => get_string('add')));
$addbutton = html_writer::tag('div', $addinput, array('id' => 'addcontrols'));
$removeinput = html_writer::empty_tag('input',
array('name' => 'remove', 'id' => 'remove', 'type' => 'submit',
'value' => $removearrow . ' ' . get_string('remove'),
'title' => get_string('remove')));
$removebutton = html_writer::tag('div', $removeinput, array('id' => 'removecontrols'));
//create the three cells
$label = html_writer::tag('label', get_string('serviceusers', 'webservice'),
array('for' => 'removeselect'));
$label = html_writer::tag('p', $label);
$authoriseduserscell = new html_table_cell($label .
$options->alloweduserselector->display(true));
$authoriseduserscell->id = 'existingcell';
$buttonscell = new html_table_cell($addbutton . html_writer::empty_tag('br') . $removebutton);
$buttonscell->id = 'buttonscell';
$label = html_writer::tag('label', get_string('potusers', 'webservice'),
array('for' => 'addselect'));
$label = html_writer::tag('p', $label);
$otheruserscell = new html_table_cell($label .
$options->potentialuserselector->display(true));
$otheruserscell->id = 'potentialcell';
$cells = array($authoriseduserscell, $buttonscell, $otheruserscell);
$row = new html_table_row($cells);
$table->data[] = $row;
$formcontent .= html_writer::table($table);
$formcontent = html_writer::tag('div', $formcontent);
$actionurl = new moodle_url('/' . $CFG->admin . '/webservice/service_users.php',
array('id' => $options->serviceid));
$html = html_writer::tag('form', $formcontent,
array('id' => 'assignform', 'action' => $actionurl, 'method' => 'post'));
return $html;
}
/**
* Display list of authorised users for the given external service.
*
* @param array $users authorised users
* @param int $serviceid service id
* @return string $html
*/
public function admin_authorised_user_list($users, $serviceid) {
global $CFG;
$listitems = [];
$extrafields = \core_user\fields::get_identity_fields(context_system::instance());
foreach ($users as $user) {
$settingsurl = new moodle_url('/admin/webservice/service_user_settings.php',
['userid' => $user->id, 'serviceid' => $serviceid]);
$identity = [];
foreach ($extrafields as $extrafield) {
if (isset($user->{$extrafield})) {
$identity[] = s($user->{$extrafield});
}
}
$identity = $identity ? html_writer::div(implode(', ', $identity), 'small') : '';
$link = html_writer::link($settingsurl, fullname($user));
if (!empty($user->missingcapabilities)) {
$count = html_writer::span(count($user->missingcapabilities), 'badge bg-danger text-white');
$links = array_map(function($capname) {
return get_capability_docs_link((object)['name' => $capname]) . html_writer::div($capname, 'text-muted');
}, $user->missingcapabilities);
$list = html_writer::alist($links);
$help = $this->output->help_icon('missingcaps', 'webservice');
$missingcaps = print_collapsible_region(html_writer::div($list . $help, 'missingcaps'), 'small',
html_writer::random_id('usermissingcaps'), get_string('usermissingcaps', 'webservice', $count), '', true, true);
} else {
$missingcaps = '';
}
$listitems[] = $link . $identity . $missingcaps;
}
$html = html_writer::div(html_writer::alist($listitems), 'alloweduserlist');
return $html;
}
/**
* Display a confirmation page to remove a function from a service
*
* @param stdClass $function It needs function id + function name properties.
* @param stdClass $service It needs service id + service name properties.
* @return string html
*/
public function admin_remove_service_function_confirmation($function, $service) {
$optionsyes = array('id' => $service->id, 'action' => 'delete',
'confirm' => 1, 'sesskey' => sesskey(), 'fid' => $function->id);
$optionsno = array('id' => $service->id);
$formcontinue = new single_button(new moodle_url('service_functions.php',
$optionsyes), get_string('remove'));
$formcancel = new single_button(new moodle_url('service_functions.php',
$optionsno), get_string('cancel'), 'get');
return $this->output->confirm(get_string('removefunctionconfirm', 'webservice',
(object) array('service' => $service->name, 'function' => $function->name)),
$formcontinue, $formcancel);
}
/**
* Display a confirmation page to remove a service
*
* @param stdClass $service It needs service id + service name properties.
* @return string html
*/
public function admin_remove_service_confirmation($service) {
global $CFG;
$optionsyes = array('id' => $service->id, 'action' => 'delete',
'confirm' => 1, 'sesskey' => sesskey());
$optionsno = array('section' => 'externalservices');
$formcontinue = new single_button(new moodle_url('service.php', $optionsyes),
get_string('delete'), 'post');
$formcancel = new single_button(
new moodle_url($CFG->wwwroot . "/" . $CFG->admin . "/settings.php", $optionsno),
get_string('cancel'), 'get');
return $this->output->confirm(get_string('deleteserviceconfirm', 'webservice', $service->name),
$formcontinue, $formcancel);
}
/**
* Display a list of functions for a given service
* If the service is built-in, do not display remove/add operation (read-only)
*
* @param array $functions list of functions
* @param stdClass $service the given service
* @return string the table html + add operation html
*/
public function admin_service_function_list($functions, $service) {
global $CFG;
if (!empty($functions)) {
$table = new html_table();
$table->head = array(get_string('function', 'webservice'),
get_string('description'), get_string('requiredcaps', 'webservice'));
$table->align = array('left', 'left', 'left');
$table->size = array('15%', '40%', '40%');
$table->width = '100%';
$table->align[] = 'left';
//display remove function operation (except for build-in service)
if (empty($service->component)) {
$table->head[] = get_string('edit');
$table->align[] = 'center';
}
$anydeprecated = false;
foreach ($functions as $function) {
$function = external_api::external_function_info($function);
if (!empty($function->deprecated)) {
$anydeprecated = true;
}
$requiredcaps = html_writer::tag('div',
empty($function->capabilities) ? '' : $function->capabilities,
array('class' => 'functiondesc'));
;
$description = html_writer::tag('div', $function->description,
array('class' => 'functiondesc'));
//display remove function operation (except for build-in service)
if (empty($service->component)) {
$removeurl = new moodle_url('/' . $CFG->admin . '/webservice/service_functions.php',
array('sesskey' => sesskey(), 'fid' => $function->id,
'id' => $service->id,
'action' => 'delete'));
$removelink = html_writer::tag('a',
get_string('removefunction', 'webservice'),
array('href' => $removeurl));
$table->data[] = array($function->name, $description, $requiredcaps, $removelink);
} else {
$table->data[] = array($function->name, $description, $requiredcaps);
}
}
$html = html_writer::table($table);
} else {
$html = get_string('nofunctions', 'webservice') . html_writer::empty_tag('br');
}
//display add function operation (except for build-in service)
if (empty($service->component)) {
if (!empty($anydeprecated)) {
debugging('This service uses deprecated functions, replace them by the proposed ones and update your client/s.', DEBUG_DEVELOPER);
}
$addurl = new moodle_url('/' . $CFG->admin . '/webservice/service_functions.php',
array('sesskey' => sesskey(), 'id' => $service->id, 'action' => 'add'));
$html .= html_writer::tag('a', get_string('addfunctions', 'webservice'), array('href' => $addurl));
}
return $html;
}
/**
* Display Reset token confirmation box
*
* @param stdClass $token token to reset
* @return string html
*/
public function user_reset_token_confirmation($token) {
$managetokenurl = '/user/managetoken.php';
$optionsyes = ['tokenid' => $token->id, 'action' => 'resetwstoken', 'confirm' => 1];
$formcontinue = new single_button(new moodle_url($managetokenurl, $optionsyes), get_string('reset'));
$formcancel = new single_button(new moodle_url($managetokenurl), get_string('cancel'), 'get');
$html = $this->output->confirm(get_string('resettokenconfirm', 'webservice',
(object) array('user' => $token->firstname . " " .
$token->lastname, 'service' => $token->name)),
$formcontinue, $formcancel);
return $html;
}
/**
* Display user tokens with buttons to reset them
*
* @param stdClass $tokens user tokens
* @param int $userid user id
* @param bool $documentation if true display a link to the API documentation
* @return string html code
*/
public function user_webservice_tokens_box($tokens, $userid, $documentation = false) {
global $CFG, $DB;
// display strings
$stroperation = get_string('operation', 'webservice');
$strtoken = get_string('tokenname', 'webservice');
$strservice = get_string('service', 'webservice');
$strcreator = get_string('tokencreator', 'webservice');
$strvaliduntil = get_string('validuntil', 'webservice');
$strlastaccess = get_string('lastaccess');
$return = $this->output->heading(get_string('securitykeys', 'webservice'), 3, 'main', true);
$return .= $this->output->box_start('generalbox webservicestokenui');
$return .= get_string('keyshelp', 'webservice');
$table = new html_table();
$table->head = array($strtoken, $strservice, $strvaliduntil, $strlastaccess, $strcreator, $stroperation);
$table->align = array('left', 'left', 'left', 'center', 'center', 'left', 'center');
$table->width = '100%';
$table->data = array();
if ($documentation) {
$table->head[] = get_string('doc', 'webservice');
$table->align[] = 'center';
}
if (!empty($tokens)) {
foreach ($tokens as $token) {
if ($token->creatorid == $userid) {
$reset = html_writer::link(new moodle_url('/user/managetoken.php', [
'action' => 'resetwstoken',
'tokenid' => $token->id,
]), get_string('reset'));
$creator = $token->firstname . " " . $token->lastname;
} else {
//retrieve administrator name
$admincreator = $DB->get_record('user', array('id'=>$token->creatorid));
$creator = $admincreator->firstname . " " . $admincreator->lastname;
$reset = '';
}
$userprofilurl = new moodle_url('/user/view.php?id=' . $token->creatorid);
$creatoratag = html_writer::start_tag('a', array('href' => $userprofilurl));
$creatoratag .= $creator;
$creatoratag .= html_writer::end_tag('a');
$validuntil = '';
if (!empty($token->validuntil)) {
$validuntil = userdate($token->validuntil, get_string('strftimedatetime', 'langconfig'));
}
$lastaccess = '';
if (!empty($token->lastaccess)) {
$lastaccess = userdate($token->lastaccess, get_string('strftimedatetime', 'langconfig'));
}
$servicename = $token->servicename;
if (!$token->enabled) { // That is the (1 token-1ws) related ws is not enabled.
$servicename = '<span class="dimmed_text">'.$token->servicename.'</span>';
}
$row = array($token->tokenname, $servicename, $validuntil, $lastaccess, $creatoratag, $reset);
if ($documentation) {
$doclink = new moodle_url('/webservice/wsdoc.php',
array('id' => $token->id));
$row[] = html_writer::tag('a', get_string('doc', 'webservice'),
array('href' => $doclink));
}
$table->data[] = $row;
}
$return .= html_writer::table($table);
} else {
$return .= get_string('notoken', 'webservice');
}
$return .= $this->output->box_end();
return $return;
}
/**
* Return documentation for a ws description object
* ws description object can be 'external_multiple_structure', 'external_single_structure'
* or 'external_value'
*
* Example of documentation for core_group_create_groups function:
* list of (
* object {
* courseid int //id of course
* name string //multilang compatible name, course unique
* description string //group description text
* enrolmentkey string //group enrol secret phrase
* }
* )
*
* @param stdClass $params a part of parameter/return description
* @return string the html to display
*/
public function detailed_description_html($params) {
// retrieve the description of the description object
$paramdesc = "";
if (!empty($params->desc)) {
$paramdesc .= html_writer::start_tag('span', array('style' => "color:#2A33A6"));
if ($params->required == VALUE_REQUIRED) {
$required = '';
}
if ($params->required == VALUE_DEFAULT) {
if ($params->default === null) {
$params->default = "null";
}
$required = html_writer::start_tag('b', array()) .
get_string('default', 'webservice', print_r($params->default, true))
. html_writer::end_tag('b');
}
if ($params->required == VALUE_OPTIONAL) {
$required = html_writer::start_tag('b', array()) .
get_string('optional', 'webservice') . html_writer::end_tag('b');
}
$paramdesc .= " " . $required . " ";
$paramdesc .= html_writer::start_tag('i', array());
$paramdesc .= "//";
$paramdesc .= s($params->desc);
$paramdesc .= html_writer::end_tag('i');
$paramdesc .= html_writer::end_tag('span');
$paramdesc .= html_writer::empty_tag('br', array());
}
// description object is a list
if ($params instanceof external_multiple_structure) {
return $paramdesc . "list of ( " . html_writer::empty_tag('br', array())
. $this->detailed_description_html($params->content) . ")";
} else if ($params instanceof external_single_structure) {
// description object is an object
$singlestructuredesc = $paramdesc . "object {" . html_writer::empty_tag('br', array());
foreach ($params->keys as $attributname => $attribut) {
$singlestructuredesc .= html_writer::start_tag('b', array());
$singlestructuredesc .= $attributname;
$singlestructuredesc .= html_writer::end_tag('b');
$singlestructuredesc .= " " .
$this->detailed_description_html($params->keys[$attributname]);
}
$singlestructuredesc .= "} ";
$singlestructuredesc .= html_writer::empty_tag('br', array());
return $singlestructuredesc;
} else {
// description object is a primary type (string, integer)
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;
}
}
/**
* Return a description object in indented xml format (for REST response)
* It is indented to be output within <pre> tags
*
* @param external_description $returndescription the description structure of the web service function returned value
* @param string $indentation Indentation in the generated HTML code; should contain only spaces.
* @return string the html to diplay
*/
public function description_in_indented_xml_format($returndescription, $indentation = "") {
$indentation = $indentation . " ";
$brakeline = <<<EOF
EOF;
// description object is a list
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) {
// description object is an object
$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 {
// description object is a primary type (string, integer)
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;
}
}
/**
* Create indented XML-RPC param description
*
* @todo MDL-76078 - Incorrect inter-communication, core cannot have plugin dependencies like this.
*
* @param external_description $paramdescription the description structure of the web service function parameters
* @param string $indentation Indentation in the generated HTML code; should contain only spaces.
* @return string the html to diplay
*/
public function xmlrpc_param_description_html($paramdescription, $indentation = "") {
$indentation = $indentation . " ";
$brakeline = <<<EOF
EOF;
// description object is a list
if ($paramdescription instanceof external_multiple_structure) {
$return = $brakeline . $indentation . "Array ";
$indentation = $indentation . " ";
$return .= $brakeline . $indentation . "(";
$return .= $brakeline . $indentation . "[0] =>";
$return .= $this->xmlrpc_param_description_html($paramdescription->content, $indentation);
$return .= $brakeline . $indentation . ")";
return $return;
} else if ($paramdescription instanceof external_single_structure) {
// description object is an object
$singlestructuredesc = $brakeline . $indentation . "Array ";
$keyindentation = $indentation . " ";
$singlestructuredesc .= $brakeline . $keyindentation . "(";
foreach ($paramdescription->keys as $attributname => $attribut) {
$singlestructuredesc .= $brakeline . $keyindentation . "[" . $attributname . "] =>" .
$this->xmlrpc_param_description_html(
$paramdescription->keys[$attributname], $keyindentation) .
$keyindentation;
}
$singlestructuredesc .= $brakeline . $keyindentation . ")";
return $singlestructuredesc;
} else {
// description object is a primary type (string, integer)
switch ($paramdescription->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;
}
}
/**
* Return the html of a coloured box with content
*
* @param string $title - the title of the box
* @param string $content - the content to displayed
* @param string $rgb - the background color of the box
* @return string HTML code
*/
public function colored_box_with_pre_tag($title, $content, $rgb = 'FEEBE5') {
$coloredbox = html_writer::start_tag('div', array());
$coloredbox .= html_writer::start_tag('div',
array('style' => "border:solid 1px #DEDEDE;background:#" . $rgb
. ";color:#222222;padding:4px;"));
$coloredbox .= html_writer::start_tag('pre', array());
$coloredbox .= html_writer::start_tag('b', array());
$coloredbox .= $title;
$coloredbox .= html_writer::end_tag('b', array());
$coloredbox .= html_writer::empty_tag('br', array());
$coloredbox .= "\n" . $content . "\n";
$coloredbox .= html_writer::end_tag('pre', array());
$coloredbox .= html_writer::end_tag('div', array());
$coloredbox .= html_writer::end_tag('div', array());
return $coloredbox;
}
/**
* Return indented REST param description
*
* @todo MDL-76078 - Incorrect inter-communication, core cannot have plugin dependencies like this.
*
* @param external_description $paramdescription the description structure of the web service function parameters
* @param string $paramstring parameter
* @return string the html to diplay
*/
public function rest_param_description_html($paramdescription, $paramstring) {
$brakeline = <<<EOF
EOF;
// description object is a list
if ($paramdescription instanceof external_multiple_structure) {
$paramstring = $paramstring . '[0]';
$return = $this->rest_param_description_html($paramdescription->content, $paramstring);
return $return;
} else if ($paramdescription instanceof external_single_structure) {
// description object is an object
$singlestructuredesc = "";
$initialparamstring = $paramstring;
foreach ($paramdescription->keys as $attributname => $attribut) {
$paramstring = $initialparamstring . '[' . $attributname . ']';
$singlestructuredesc .= $this->rest_param_description_html(
$paramdescription->keys[$attributname], $paramstring);
}
return $singlestructuredesc;
} else {
// description object is a primary type (string, integer)
$paramstring = $paramstring . '=';
switch ($paramdescription->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 $paramstring . " " . $type . $brakeline;
}
}
/**
* Displays all the documentation
*
* @todo MDL-76078 - Incorrect inter-communication, core cannot have plugin dependencies like this.
*
* @param array $functions external_description of all the web service functions
* @param boolean $printableformat true if we want to display the documentation in a printable format
* @param array $activatedprotocol the currently enabled protocol
* @param array $authparams url parameters (it contains 'tokenid' and sometimes 'print')
* @param string $parenturl url of the calling page - needed for the print button url:
* '/admin/documentation.php' or '/webservice/wsdoc.php' (default)
* @return string the html to diplay
*/
public function documentation_html($functions, $printableformat, $activatedprotocol,
$authparams, $parenturl = '/webservice/wsdoc.php') {
$documentationhtml = $this->output->heading(get_string('wsdocapi', 'webservice'));
$br = html_writer::empty_tag('br', array());
$brakeline = <<<EOF
EOF;
// Some general information
$docinfo = new stdClass();
$docurl = new moodle_url('http://docs.moodle.org/dev/Creating_a_web_service_client');
$docinfo->doclink = html_writer::tag('a',
get_string('wsclientdoc', 'webservice'), array('href' => $docurl));
$documentationhtml .= get_string('wsdocumentationintro', 'webservice', $docinfo);
$documentationhtml .= $br . $br;
// Print button
$authparams['print'] = true;
$url = new moodle_url($parenturl, $authparams); // Required
$documentationhtml .= $this->output->single_button($url, get_string('print', 'webservice'));
$documentationhtml .= $br;
// each functions will be displayed into a collapsible region
//(opened if printableformat = true)
foreach ($functions as $functionname => $description) {
$tags = '';
if (!empty($description->deprecated)) {
$tags .= ' ' . html_writer::span(get_string('deprecated', 'core_webservice'), 'badge bg-warning text-dark');
}
if (empty($printableformat)) {
$documentationhtml .= print_collapsible_region_start('',
'aera_' . $functionname,
html_writer::start_tag('strong', array())
. $functionname . html_writer::end_tag('strong') . $tags,
false,
!$printableformat,
true);
} else {
$documentationhtml .= html_writer::tag('strong', $functionname) . $tags;
$documentationhtml .= $br;
}
// function global description
$documentationhtml .= $br;
$documentationhtml .= html_writer::start_tag('div',
array('style' => 'border:solid 1px #DEDEDE;background:#E2E0E0;
color:#222222;padding:4px;'));
$documentationhtml .= s($description->description);
$documentationhtml .= html_writer::end_tag('div');
$documentationhtml .= $br . $br;
// function arguments documentation
$documentationhtml .= html_writer::start_tag('span', array('style' => 'color:#EA33A6'));
$documentationhtml .= get_string('arguments', 'webservice');
$documentationhtml .= html_writer::end_tag('span');
$documentationhtml .= $br;
foreach ($description->parameters_desc->keys as $paramname => $paramdesc) {
// a argument documentation
$documentationhtml .= html_writer::start_tag('div', ['style' => 'font-size:80%']);
if ($paramdesc->required == VALUE_REQUIRED) {
$required = get_string('required', 'webservice');
}
if ($paramdesc->required == VALUE_DEFAULT) {
if ($paramdesc->default === null) {
$default = "null";
} else {
$default = print_r($paramdesc->default, true);
}
$required = get_string('default', 'webservice', $default);
}
if ($paramdesc->required == VALUE_OPTIONAL) {
$required = get_string('optional', 'webservice');
}
$documentationhtml .= html_writer::start_tag('b', array());
$documentationhtml .= $paramname;
$documentationhtml .= html_writer::end_tag('b');
$documentationhtml .= " (" . $required . ")"; // argument is required or optional ?
$documentationhtml .= $br;
$documentationhtml .= "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
. s($paramdesc->desc); // Argument description.
$documentationhtml .= $br . $br;
// general structure of the argument
$documentationhtml .= $this->colored_box_with_pre_tag(
get_string('generalstructure', 'webservice'),
$this->detailed_description_html($paramdesc),
'FFF1BC');
// xml-rpc structure of the argument in PHP format
if (!empty($activatedprotocol['xmlrpc'])) {
$documentationhtml .= $this->colored_box_with_pre_tag(
get_string('phpparam', 'webservice'),
htmlentities('[' . $paramname . '] =>'
. $this->xmlrpc_param_description_html($paramdesc), ENT_COMPAT),
'DFEEE7');
}
// POST format for the REST protocol for the argument
if (!empty($activatedprotocol['rest'])) {
$documentationhtml .= $this->colored_box_with_pre_tag(
get_string('restparam', 'webservice'),
htmlentities($this->rest_param_description_html(
$paramdesc, $paramname), ENT_COMPAT),
'FEEBE5');
}
$documentationhtml .= html_writer::end_tag('div');
}
$documentationhtml .= $br . $br;
// function response documentation
$documentationhtml .= html_writer::start_tag('span', array('style' => 'color:#EA33A6'));
$documentationhtml .= get_string('response', 'webservice');
$documentationhtml .= html_writer::end_tag('span');
$documentationhtml .= $br;
// function response description
$documentationhtml .= html_writer::start_tag('div', ['style' => 'font-size:80%']);
if (!empty($description->returns_desc->desc)) {
$documentationhtml .= $description->returns_desc->desc;
$documentationhtml .= $br . $br;
}
if (!empty($description->returns_desc)) {
// general structure of the response
$documentationhtml .= $this->colored_box_with_pre_tag(
get_string('generalstructure', 'webservice'),
$this->detailed_description_html($description->returns_desc),
'FFF1BC');
// xml-rpc structure of the response in PHP format
if (!empty($activatedprotocol['xmlrpc'])) {
$documentationhtml .= $this->colored_box_with_pre_tag(
get_string('phpresponse', 'webservice'),
htmlentities($this->xmlrpc_param_description_html(
$description->returns_desc), ENT_COMPAT),
'DFEEE7');
}
// XML response for the REST protocol
if (!empty($activatedprotocol['rest'])) {
$restresponse = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
. $brakeline . "<RESPONSE>" . $brakeline;
$restresponse .= $this->description_in_indented_xml_format(
$description->returns_desc);
$restresponse .="</RESPONSE>" . $brakeline;
$documentationhtml .= $this->colored_box_with_pre_tag(
get_string('restcode', 'webservice'),
htmlentities($restresponse, ENT_COMPAT),
'FEEBE5');
}
}
$documentationhtml .= html_writer::end_tag('div');
$documentationhtml .= $br . $br;
// function errors documentation for REST protocol
if (!empty($activatedprotocol['rest'])) {
$documentationhtml .= html_writer::start_tag('span', array('style' => 'color:#EA33A6'));
$documentationhtml .= get_string('errorcodes', 'webservice');
$documentationhtml .= html_writer::end_tag('span');
$documentationhtml .= $br . $br;
$documentationhtml .= html_writer::start_tag('div', ['style' => 'font-size:80%']);
$errormessage = get_string('invalidparameter', 'debug');
$restexceptiontext = <<<EOF
<?xml version="1.0" encoding="UTF-8"?>
<EXCEPTION class="invalid_parameter_exception">
<MESSAGE>{$errormessage}</MESSAGE>
<DEBUGINFO></DEBUGINFO>
</EXCEPTION>
EOF;
$documentationhtml .= $this->colored_box_with_pre_tag(
get_string('restexception', 'webservice'),
htmlentities($restexceptiontext, ENT_COMPAT),
'FEEBE5');
$documentationhtml .= html_writer::end_tag('div');
}
$documentationhtml .= $br . $br;
// Login required info.
$documentationhtml .= html_writer::start_tag('span', array('style' => 'color:#EA33A6'));
$documentationhtml .= get_string('loginrequired', 'webservice') . $br;
$documentationhtml .= html_writer::end_tag('span');
$documentationhtml .= $description->loginrequired ? get_string('yes') : get_string('no');
$documentationhtml .= $br . $br;
// Ajax info.
$documentationhtml .= html_writer::start_tag('span', array('style' => 'color:#EA33A6'));
$documentationhtml .= get_string('callablefromajax', 'webservice') . $br;
$documentationhtml .= html_writer::end_tag('span');
$documentationhtml .= $description->allowed_from_ajax ? get_string('yes') : get_string('no');
$documentationhtml .= $br . $br;
if (empty($printableformat)) {
$documentationhtml .= print_collapsible_region_end(true);
}
}
return $documentationhtml;
}
}
@@ -0,0 +1,46 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* Privacy provider implementation for webservice_rest.
*
* @package webservice_rest
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace webservice_rest\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy provider implementation for webservice_rest.
*
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* REST server related capabilities
*
* @package webservice_rest
* @category access
* @copyright 2009 Petr Skodak
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$capabilities = array(
'webservice/rest:use' => array(
'captype' => 'read', // in fact this may be considered read and write at the same time
'contextlevel' => CONTEXT_COURSE, // the context level should be probably CONTEXT_MODULE
'archetypes' => array(
),
),
);
@@ -0,0 +1,29 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* Strings for component 'webservice_rest', language 'en', branch 'MOODLE_20_STABLE'
*
* @package webservice_rest
* @category string
* @copyright 2009 Petr Skodak
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['pluginname'] = 'REST protocol';
$string['privacy:metadata'] = 'The REST protocol plugin does not store any personal data.';
$string['rest:use'] = 'Use REST protocol';
+99
View File
@@ -0,0 +1,99 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* Moodle REST library
*
* @package webservice_rest
* @copyright 2009 Jerome Mouneyrac
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Moodle REST client
*
* It has been implemented for unit testing purpose (all protocols have similar client)
*
* @package webservice_rest
* @copyright 2010 Jerome Mouneyrac
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class webservice_rest_client {
/** @var moodle_url the REST server url */
private $serverurl;
/** @var string token */
private $token;
/** @var string returned value format: xml or json */
private $format;
/**
* Constructor
*
* @param string $serverurl a Moodle URL
* @param string $token the token used to do the web service call
* @param string $format returned value format: xml or json
*/
public function __construct($serverurl, $token, $format = 'xml') {
$this->serverurl = new moodle_url($serverurl);
$this->token = $token;
$this->format = $format;
}
/**
* Set the token used to do the REST call
*
* @param string $token the token used to do the web service call
*/
public function set_token($token) {
$this->token = $token;
}
/**
* Execute client WS request with token authentication
*
* @param string $functionname the function name
* @param array $params the parameters of the function
* @return mixed
*/
public function call($functionname, $params) {
global $DB, $CFG;
if ($this->format == 'json') {
$formatparam = '&moodlewsrestformat=json';
$this->serverurl->param('moodlewsrestformat','json');
} else {
$formatparam = ''; //to keep retro compability with old server that only support xml (they don't expect this param)
}
$this->serverurl->param('wstoken',$this->token);
$this->serverurl->param('wsfunction',$functionname); //you could also use params().
$result = download_file_content($this->serverurl->out(false), null, $params);
//TODO MDL-22965 transform the XML result into PHP values
if ($this->format == 'json') {
$result = json_decode($result);
}
return $result;
}
}
+284
View File
@@ -0,0 +1,284 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* REST web service implementation classes and methods.
*
* @package webservice_rest
* @copyright 2009 Jerome Mouneyrac
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
require_once("$CFG->dirroot/webservice/lib.php");
/**
* REST service server implementation.
*
* @package webservice_rest
* @copyright 2009 Petr Skoda (skodak)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class webservice_rest_server extends webservice_base_server {
/** @var string return method ('xml' or 'json') */
protected $restformat;
/**
* Contructor
*
* @param string $authmethod authentication method of the web service (WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN, ...)
* @param string $restformat Format of the return values: 'xml' or 'json'
*/
public function __construct($authmethod) {
parent::__construct($authmethod);
$this->wsname = 'rest';
}
/**
* Set the request format to.
*/
public function set_rest_format(): void {
// Get GET and POST parameters.
$methodvariables = array_merge($_GET, $_POST);
// Retrieve REST format parameter - 'xml' (default) or 'json'.
$restformatisset = isset($methodvariables['moodlewsrestformat'])
&& (($methodvariables['moodlewsrestformat'] == 'xml' || $methodvariables['moodlewsrestformat'] == 'json'));
$this->restformat = $restformatisset ? $methodvariables['moodlewsrestformat'] : 'xml';
}
/**
* This method parses the $_POST and $_GET superglobals and looks for
* the following information:
* 1/ user authentication - username+password or token (wsusername, wspassword and wstoken parameters)
* 2/ function name (wsfunction parameter)
* 3/ function parameters (all other parameters except those above)
* 4/ text format parameters
* 5/ return rest format xml/json
*/
protected function parse_request() {
// Retrieve and clean the POST/GET parameters from the parameters specific to the server.
parent::set_web_service_call_settings();
// Get GET and POST parameters.
$methodvariables = array_merge($_GET, $_POST);
$this->set_rest_format();
unset($methodvariables['moodlewsrestformat']);
if ($this->authmethod == WEBSERVICE_AUTHMETHOD_USERNAME) {
$this->username = isset($methodvariables['wsusername']) ? $methodvariables['wsusername'] : null;
unset($methodvariables['wsusername']);
$this->password = isset($methodvariables['wspassword']) ? $methodvariables['wspassword'] : null;
unset($methodvariables['wspassword']);
$this->functionname = isset($methodvariables['wsfunction']) ? $methodvariables['wsfunction'] : null;
unset($methodvariables['wsfunction']);
$this->parameters = $methodvariables;
} else {
$this->token = isset($methodvariables['wstoken']) ? $methodvariables['wstoken'] : null;
unset($methodvariables['wstoken']);
$this->functionname = isset($methodvariables['wsfunction']) ? $methodvariables['wsfunction'] : null;
unset($methodvariables['wsfunction']);
$this->parameters = $methodvariables;
}
}
/**
* Send the result of function call to the WS client
* formatted as XML document.
*/
protected function send_response() {
//Check that the returned values are valid
try {
if ($this->function->returns_desc != null) {
$validatedvalues = \core_external\external_api::clean_returnvalue(
$this->function->returns_desc,
$this->returns
);
} else {
$validatedvalues = null;
}
} catch (Exception $ex) {
$exception = $ex;
}
if (!empty($exception)) {
$response = $this->generate_error($exception);
} else {
//We can now convert the response to the requested REST format
if ($this->restformat == 'json') {
$response = json_encode($validatedvalues);
} else {
$response = '<?xml version="1.0" encoding="UTF-8" ?>'."\n";
$response .= '<RESPONSE>'."\n";
$response .= self::xmlize_result($validatedvalues, $this->function->returns_desc);
$response .= '</RESPONSE>'."\n";
}
}
$this->send_headers();
echo $response;
}
/**
* Send the error information to the WS client
* formatted as XML document.
* Note: the exception is never passed as null,
* it only matches the abstract function declaration.
* @param exception $ex the exception that we are sending
*/
protected function send_error($ex=null) {
// Unless debugging is completely off, log the error to server error log.
if (debugging('', DEBUG_MINIMAL)) {
$info = get_exception_info($ex);
// This format is the same as default_exception_handler() in setuplib.php but with the
// word 'REST' instead of 'Default', to make it easy to reuse any existing processing.
error_log('REST exception handler: ' . $info->message . ' Debug: ' .
$info->debuginfo . "\n" . format_backtrace($info->backtrace, true));
}
$this->send_headers();
echo $this->generate_error($ex);
}
/**
* Build the error information matching the REST returned value format (JSON or XML)
* @param exception $ex the exception we are converting in the server rest format
* @return string the error in the requested REST format
*/
protected function generate_error($ex) {
if ($this->restformat == 'json') {
$errorobject = new stdClass;
$errorobject->exception = get_class($ex);
if (isset($ex->errorcode)) {
$errorobject->errorcode = $ex->errorcode;
}
$errorobject->message = $ex->getMessage();
if (debugging() and isset($ex->debuginfo)) {
$errorobject->debuginfo = $ex->debuginfo;
}
$error = json_encode($errorobject);
} else {
$error = '<?xml version="1.0" encoding="UTF-8" ?>'."\n";
$error .= '<EXCEPTION class="'.get_class($ex).'">'."\n";
if (isset($ex->errorcode)) {
$error .= '<ERRORCODE>' . htmlspecialchars($ex->errorcode, ENT_COMPAT, 'UTF-8')
. '</ERRORCODE>' . "\n";
}
$error .= '<MESSAGE>'.htmlspecialchars($ex->getMessage(), ENT_COMPAT, 'UTF-8').'</MESSAGE>'."\n";
if (debugging() and isset($ex->debuginfo)) {
$error .= '<DEBUGINFO>'.htmlspecialchars($ex->debuginfo, ENT_COMPAT, 'UTF-8').'</DEBUGINFO>'."\n";
}
$error .= '</EXCEPTION>'."\n";
}
return $error;
}
/**
* Internal implementation - sending of page headers.
*/
protected function send_headers() {
if ($this->restformat == 'json') {
header('Content-type: application/json');
} else {
header('Content-Type: application/xml; charset=utf-8');
header('Content-Disposition: inline; filename="response.xml"');
}
header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
header('Pragma: no-cache');
header('Accept-Ranges: none');
// Allow cross-origin requests only for Web Services.
// This allow to receive requests done by Web Workers or webapps in different domains.
header('Access-Control-Allow-Origin: *');
}
/**
* Internal implementation - recursive function producing XML markup.
*
* @param mixed $returns the returned values
* @param external_description $desc
* @return string
*/
protected static function xmlize_result($returns, $desc) {
if ($desc === null) {
return '';
} else if ($desc instanceof external_value) {
if (is_bool($returns)) {
// we want 1/0 instead of true/false here
$returns = (int)$returns;
}
if (is_null($returns)) {
return '<VALUE null="null"/>'."\n";
} else {
return '<VALUE>'.htmlspecialchars($returns, ENT_COMPAT, 'UTF-8').'</VALUE>'."\n";
}
} else if ($desc instanceof external_multiple_structure) {
$mult = '<MULTIPLE>'."\n";
if (!empty($returns)) {
foreach ($returns as $val) {
$mult .= self::xmlize_result($val, $desc->content);
}
}
$mult .= '</MULTIPLE>'."\n";
return $mult;
} else if ($desc instanceof external_single_structure) {
$single = '<SINGLE>'."\n";
foreach ($desc->keys as $key=>$subdesc) {
$value = isset($returns[$key]) ? $returns[$key] : null;
$single .= '<KEY name="'.$key.'">'.self::xmlize_result($value, $subdesc).'</KEY>'."\n";
}
$single .= '</SINGLE>'."\n";
return $single;
}
}
}
/**
* REST test client class
*
* @package webservice_rest
* @copyright 2009 Petr Skoda (skodak)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class webservice_rest_test_client implements webservice_test_client_interface {
/**
* Execute test client WS request
* @param string $serverurl server url (including token parameter or username/password parameters)
* @param string $function function name
* @param array $params parameters of the called function
* @return mixed
*/
public function simpletest($serverurl, $function, $params) {
return download_file_content($serverurl.'&wsfunction='.$function, null, $params);
}
}
+58
View File
@@ -0,0 +1,58 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* REST web service entry point. The authentication is done via tokens.
*
* @package webservice_rest
* @copyright 2009 Jerome Mouneyrac
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* NO_DEBUG_DISPLAY - disable moodle specific debug messages and any errors in output
*/
define('NO_DEBUG_DISPLAY', true);
define('WS_SERVER', true);
require('../../config.php');
require_once("$CFG->dirroot/webservice/rest/locallib.php");
if (!webservice_protocol_is_enabled('rest')) {
header("HTTP/1.0 403 Forbidden");
debugging('The server died because the web services or the REST protocol are not enable',
DEBUG_DEVELOPER);
die;
}
$server = new webservice_rest_server(WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN);
$server->run();
die;
/**
* Raises Early WS Exception in REST format.
*
* @param Exception $ex Raised exception.
*/
function raise_early_ws_exception(Exception $ex): void {
global $CFG;
require_once("$CFG->dirroot/webservice/rest/locallib.php");
$server = new webservice_rest_server(WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN);
$server->set_rest_format();
$server->exception_handler($ex);
}
+44
View File
@@ -0,0 +1,44 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* REST web service entry point. The authentication is done via username/password.
*
* @package webservice_rest
* @copyright 2009 Jerome Mouneyrac
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* NO_DEBUG_DISPLAY - disable moodle specific debug messages and any errors in output
*/
define('NO_DEBUG_DISPLAY', true);
define('WS_SERVER', true);
require('../../config.php');
require_once("$CFG->dirroot/webservice/rest/locallib.php");
if (!webservice_protocol_is_enabled('rest')) {
die;
}
$server = new webservice_rest_server(WEBSERVICE_AUTHMETHOD_USERNAME);
$server->run();
die;
+213
View File
@@ -0,0 +1,213 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
namespace webservice_rest;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/rest/locallib.php');
/**
* Rest server testcase.
*
* @package webservice_rest
* @copyright 2016 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class server_test extends \advanced_testcase {
/**
* Data provider for test_xmlize.
* @return array
*/
public function xmlize_provider() {
$data = [];
$data[] = [null, null, ''];
$data[] = [new external_value(PARAM_BOOL), false, "<VALUE>0</VALUE>\n"];
$data[] = [new external_value(PARAM_BOOL), true, "<VALUE>1</VALUE>\n"];
$data[] = [new external_value(PARAM_ALPHA), null, "<VALUE null=\"null\"/>\n"];
$data[] = [new external_value(PARAM_ALPHA), 'a', "<VALUE>a</VALUE>\n"];
$data[] = [new external_value(PARAM_INT), 123, "<VALUE>123</VALUE>\n"];
$data[] = [
new external_multiple_structure(new external_value(PARAM_INT)),
[1, 2, 3],
"<MULTIPLE>\n" .
"<VALUE>1</VALUE>\n" .
"<VALUE>2</VALUE>\n" .
"<VALUE>3</VALUE>\n" .
"</MULTIPLE>\n"
];
$data[] = [ // Multiple structure with null value.
new external_multiple_structure(new external_value(PARAM_ALPHA)),
['A', null, 'C'],
"<MULTIPLE>\n" .
"<VALUE>A</VALUE>\n" .
"<VALUE null=\"null\"/>\n" .
"<VALUE>C</VALUE>\n" .
"</MULTIPLE>\n"
];
$data[] = [ // Multiple structure without values.
new external_multiple_structure(new external_value(PARAM_ALPHA)),
[],
"<MULTIPLE>\n" .
"</MULTIPLE>\n"
];
$data[] = [
new external_single_structure([
'one' => new external_value(PARAM_INT),
'two' => new external_value(PARAM_INT),
'three' => new external_value(PARAM_INT),
]),
['one' => 1, 'two' => 2, 'three' => 3],
"<SINGLE>\n" .
"<KEY name=\"one\"><VALUE>1</VALUE>\n</KEY>\n" .
"<KEY name=\"two\"><VALUE>2</VALUE>\n</KEY>\n" .
"<KEY name=\"three\"><VALUE>3</VALUE>\n</KEY>\n" .
"</SINGLE>\n"
];
$data[] = [ // Single structure with null value.
new external_single_structure([
'one' => new external_value(PARAM_INT),
'two' => new external_value(PARAM_INT),
'three' => new external_value(PARAM_INT),
]),
['one' => 1, 'two' => null, 'three' => 3],
"<SINGLE>\n" .
"<KEY name=\"one\"><VALUE>1</VALUE>\n</KEY>\n" .
"<KEY name=\"two\"><VALUE null=\"null\"/>\n</KEY>\n" .
"<KEY name=\"three\"><VALUE>3</VALUE>\n</KEY>\n" .
"</SINGLE>\n"
];
$data[] = [ // Single structure missing keys.
new external_single_structure([
'one' => new external_value(PARAM_INT),
'two' => new external_value(PARAM_INT),
'three' => new external_value(PARAM_INT),
]),
['two' => null, 'three' => 3],
"<SINGLE>\n" .
"<KEY name=\"one\"><VALUE null=\"null\"/>\n</KEY>\n" .
"<KEY name=\"two\"><VALUE null=\"null\"/>\n</KEY>\n" .
"<KEY name=\"three\"><VALUE>3</VALUE>\n</KEY>\n" .
"</SINGLE>\n"
];
$data[] = [ // Nested structure.
new external_single_structure([
'one' => new external_multiple_structure(
new external_value(PARAM_INT)
),
'two' => new external_multiple_structure(
new external_single_structure([
'firstname' => new external_value(PARAM_RAW),
'lastname' => new external_value(PARAM_RAW),
])
),
'three' => new external_single_structure([
'firstname' => new external_value(PARAM_RAW),
'lastname' => new external_value(PARAM_RAW),
]),
]),
[
'one' => [2, 3, 4],
'two' => [
['firstname' => 'Louis', 'lastname' => 'Armstrong'],
['firstname' => 'Neil', 'lastname' => 'Armstrong'],
],
'three' => ['firstname' => 'Neil', 'lastname' => 'Armstrong'],
],
"<SINGLE>\n" .
"<KEY name=\"one\"><MULTIPLE>\n".
"<VALUE>2</VALUE>\n" .
"<VALUE>3</VALUE>\n" .
"<VALUE>4</VALUE>\n" .
"</MULTIPLE>\n</KEY>\n" .
"<KEY name=\"two\"><MULTIPLE>\n".
"<SINGLE>\n" .
"<KEY name=\"firstname\"><VALUE>Louis</VALUE>\n</KEY>\n" .
"<KEY name=\"lastname\"><VALUE>Armstrong</VALUE>\n</KEY>\n" .
"</SINGLE>\n" .
"<SINGLE>\n" .
"<KEY name=\"firstname\"><VALUE>Neil</VALUE>\n</KEY>\n" .
"<KEY name=\"lastname\"><VALUE>Armstrong</VALUE>\n</KEY>\n" .
"</SINGLE>\n" .
"</MULTIPLE>\n</KEY>\n" .
"<KEY name=\"three\"><SINGLE>\n" .
"<KEY name=\"firstname\"><VALUE>Neil</VALUE>\n</KEY>\n" .
"<KEY name=\"lastname\"><VALUE>Armstrong</VALUE>\n</KEY>\n" .
"</SINGLE>\n</KEY>\n" .
"</SINGLE>\n"
];
$data[] = [ // Nested structure with missing keys.
new external_single_structure([
'one' => new external_multiple_structure(
new external_value(PARAM_INT)
),
'two' => new external_multiple_structure(
new external_single_structure([
'firstname' => new external_value(PARAM_RAW),
'lastname' => new external_value(PARAM_RAW),
])
),
'three' => new external_single_structure([
'firstname' => new external_value(PARAM_RAW),
'lastname' => new external_value(PARAM_RAW),
]),
]),
[
'two' => [
['firstname' => 'Louis'],
['lastname' => 'Armstrong'],
],
'three' => ['lastname' => 'Armstrong'],
],
"<SINGLE>\n" .
"<KEY name=\"one\"><MULTIPLE>\n</MULTIPLE>\n</KEY>\n" .
"<KEY name=\"two\"><MULTIPLE>\n".
"<SINGLE>\n" .
"<KEY name=\"firstname\"><VALUE>Louis</VALUE>\n</KEY>\n" .
"<KEY name=\"lastname\"><VALUE null=\"null\"/>\n</KEY>\n" .
"</SINGLE>\n" .
"<SINGLE>\n" .
"<KEY name=\"firstname\"><VALUE null=\"null\"/>\n</KEY>\n" .
"<KEY name=\"lastname\"><VALUE>Armstrong</VALUE>\n</KEY>\n" .
"</SINGLE>\n" .
"</MULTIPLE>\n</KEY>\n" .
"<KEY name=\"three\"><SINGLE>\n" .
"<KEY name=\"firstname\"><VALUE null=\"null\"/>\n</KEY>\n" .
"<KEY name=\"lastname\"><VALUE>Armstrong</VALUE>\n</KEY>\n" .
"</SINGLE>\n</KEY>\n" .
"</SINGLE>\n"
];
return $data;
}
/**
* @dataProvider xmlize_provider
* @param \core_external\external_description $description The data structure.
* @param mixed $value The value to xmlise.
* @param mixed $expected The expected output.
*/
public function test_xmlize($description, $value, $expected): void {
$method = new \ReflectionMethod('webservice_rest_server', 'xmlize_result');
$this->assertEquals($expected, $method->invoke(null, $value, $description));
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* Version details
*
* @package webservice_rest
* @copyright 2009 Jerome Mouneyrac
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'webservice_rest'; // Full name of the plugin (used for diagnostics)
@@ -0,0 +1,46 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* Privacy provider implementation for webservice_soap.
*
* @package webservice_soap
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace webservice_soap\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy provider implementation for webservice_soap.
*
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+261
View File
@@ -0,0 +1,261 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* WSDL generator for the SOAP web service.
*
* @package webservice_soap
* @copyright 2016 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace webservice_soap;
/**
* WSDL generator for the SOAP web service.
*
* @package webservice_soap
* @copyright 2016 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class wsdl {
/** Namespace URI for the WSDL framework. */
const NS_WSDL = 'http://schemas.xmlsoap.org/wsdl/';
/** Encoding namespace URI as defined by SOAP 1.1 */
const NS_SOAP_ENC = 'http://schemas.xmlsoap.org/soap/encoding/';
/** Namespace URI for the WSDL SOAP binding. */
const NS_SOAP = 'http://schemas.xmlsoap.org/wsdl/soap/';
/** Schema namespace URI as defined by XSD. */
const NS_XSD = 'http://www.w3.org/2001/XMLSchema';
/** WSDL namespace for the WSDL HTTP GET and POST binding. */
const NS_SOAP_TRANSPORT = 'http://schemas.xmlsoap.org/soap/http';
/** BINDING - string constant attached to the service class name to identify binding nodes. */
const BINDING = 'Binding';
/** IN - string constant attached to the function name to identify input nodes. */
const IN = 'In';
/** OUT - string constant attached to the function name to identify output nodes. */
const OUT = 'Out';
/** PORT - string constant attached to the service class name to identify port nodes. */
const PORT = 'Port';
/** SERVICE string constant attached to the service class name to identify service nodes. */
const SERVICE = 'Service';
/** @var string The name of the service class. */
private $serviceclass;
/** @var string The WSDL namespace. */
private $namespace;
/** @var array The WSDL's message nodes. */
private $messagenodes;
/** @var \SimpleXMLElement The WSDL's binding node. */
private $nodebinding;
/** @var \SimpleXMLElement The WSDL's definitions node. */
private $nodedefinitions;
/** @var \SimpleXMLElement The WSDL's portType node. */
private $nodeporttype;
/** @var \SimpleXMLElement The WSDL's service node. */
private $nodeservice;
/** @var \SimpleXMLElement The WSDL's types node. */
private $nodetypes;
/**
* webservice_soap_wsdl constructor.
*
* @param string $serviceclass The service class' name.
* @param string $namespace The WSDL namespace.
*/
public function __construct($serviceclass, $namespace) {
$this->serviceclass = $serviceclass;
$this->namespace = $namespace;
// Initialise definitions node.
$this->nodedefinitions = new \SimpleXMLElement('<definitions />');
$this->nodedefinitions->addAttribute('xmlns', self::NS_WSDL);
$this->nodedefinitions->addAttribute('x:xmlns:tns', $namespace);
$this->nodedefinitions->addAttribute('x:xmlns:soap', self::NS_SOAP);
$this->nodedefinitions->addAttribute('x:xmlns:xsd', self::NS_XSD);
$this->nodedefinitions->addAttribute('x:xmlns:soap-enc', self::NS_SOAP_ENC);
$this->nodedefinitions->addAttribute('x:xmlns:wsdl', self::NS_WSDL);
$this->nodedefinitions->addAttribute('name', $serviceclass);
$this->nodedefinitions->addAttribute('targetNamespace', $namespace);
// Initialise types node.
$this->nodetypes = $this->nodedefinitions->addChild('types');
$typeschema = $this->nodetypes->addChild('x:xsd:schema');
$typeschema->addAttribute('targetNamespace', $namespace);
// Initialise the portType node.
$this->nodeporttype = $this->nodedefinitions->addChild('portType');
$this->nodeporttype->addAttribute('name', $serviceclass . self::PORT);
// Initialise the binding node.
$this->nodebinding = $this->nodedefinitions->addChild('binding');
$this->nodebinding->addAttribute('name', $serviceclass . self::BINDING);
$this->nodebinding->addAttribute('type', 'tns:' . $serviceclass . self::PORT);
$soapbinding = $this->nodebinding->addChild('x:soap:binding');
$soapbinding->addAttribute('style', 'rpc');
$soapbinding->addAttribute('transport', self::NS_SOAP_TRANSPORT);
// Initialise the service node.
$this->nodeservice = $this->nodedefinitions->addChild('service');
$this->nodeservice->addAttribute('name', $serviceclass . self::SERVICE);
$serviceport = $this->nodeservice->addChild('port');
$serviceport->addAttribute('name', $serviceclass . self::PORT);
$serviceport->addAttribute('binding', 'tns:' . $serviceclass . self::BINDING);
$soapaddress = $serviceport->addChild('x:soap:address');
$soapaddress->addAttribute('location', $namespace);
// Initialise message nodes.
$this->messagenodes = array();
}
/**
* Adds a complex type to the WSDL.
*
* @param string $classname The complex type's class name.
* @param array $properties An associative array containing the properties of the complex type class.
*/
public function add_complex_type($classname, $properties) {
$typeschema = $this->nodetypes->children();
// Append the complex type.
$complextype = $typeschema->addChild('x:xsd:complexType');
$complextype->addAttribute('name', $classname);
$child = $complextype->addChild('x:xsd:all');
foreach ($properties as $name => $options) {
$param = $child->addChild('x:xsd:element');
$param->addAttribute('name', $name);
$param->addAttribute('type', $this->get_soap_type($options['type']));
if (!empty($options['nillable'])) {
$param->addAttribute('nillable', 'true');
}
}
}
/**
* Registers the external service method to the WSDL.
*
* @param string $functionname The name of the web service function to be registered.
* @param array $inputparams Contains the function's input parameters with their associated types.
* @param array $outputparams Contains the function's output parameters with their associated types.
* @param string $documentation The function's description.
*/
public function register($functionname, $inputparams = array(), $outputparams = array(), $documentation = '') {
// Process portType operation nodes.
$porttypeoperation = $this->nodeporttype->addChild('operation');
$porttypeoperation->addAttribute('name', $functionname);
// Documentation node.
$porttypeoperation->addChild('documentation', $documentation);
// Process binding operation nodes.
$bindingoperation = $this->nodebinding->addChild('operation');
$bindingoperation->addAttribute('name', $functionname);
$soapoperation = $bindingoperation->addChild('x:soap:operation');
$soapoperation->addAttribute('soapAction', $this->namespace . '#' . $functionname);
// Input nodes.
$this->process_params($functionname, $porttypeoperation, $bindingoperation, $inputparams);
// Output nodes.
$this->process_params($functionname, $porttypeoperation, $bindingoperation, $outputparams, true);
}
/**
* Outputs the WSDL in XML format.
*
* @return mixed The string value of the WSDL in XML format. False, otherwise.
*/
public function to_xml() {
// Return WSDL in XML format.
return $this->nodedefinitions->asXML();
}
/**
* Utility method that returns the encoded SOAP type based on the given type string.
*
* @param string $type The input type string.
* @return string The encoded type for the WSDL.
*/
private function get_soap_type($type) {
switch($type) {
case 'int':
case 'double':
case 'string':
return 'xsd:' . $type;
case 'array':
return 'soap-enc:Array';
default:
return 'tns:' . $type;
}
}
/**
* Utility method that creates input/output nodes from input/output params.
*
* @param string $functionname The name of the function being registered.
* @param \SimpleXMLElement $porttypeoperation The port type operation node.
* @param \SimpleXMLElement $bindingoperation The binding operation node.
* @param array $params The function's input/output parameters.
* @param bool $isoutput Flag to indicate if the nodes to be generated are for input or for output.
*/
private function process_params($functionname, \SimpleXMLElement $porttypeoperation, \SimpleXMLElement $bindingoperation,
array $params = null, $isoutput = false) {
// Do nothing if parameter array is empty.
if (empty($params)) {
return;
}
$postfix = self::IN;
$childtype = 'input';
if ($isoutput) {
$postfix = self::OUT;
$childtype = 'output';
}
// For portType operation node.
$child = $porttypeoperation->addChild($childtype);
$child->addAttribute('message', 'tns:' . $functionname . $postfix);
// For binding operation node.
$child = $bindingoperation->addChild($childtype);
$soapbody = $child->addChild('x:soap:body');
$soapbody->addAttribute('use', 'encoded');
$soapbody->addAttribute('encodingStyle', self::NS_SOAP_ENC);
$soapbody->addAttribute('namespace', $this->namespace);
// Process message nodes.
$messagein = $this->nodedefinitions->addChild('message');
$messagein->addAttribute('name', $functionname . $postfix);
foreach ($params as $name => $options) {
$part = $messagein->addChild('part');
$part->addAttribute('name', $name);
$part->addAttribute('type', $this->get_soap_type($options['type']));
}
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* SOAP server related capabilities
*
* @package webservice_soap
* @category access
* @copyright 2009 Petr Skodak
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$capabilities = array(
'webservice/soap:use' => array(
'captype' => 'read', // in fact this may be considered read and write at the same time
'contextlevel' => CONTEXT_COURSE, // the context level should be probably CONTEXT_MODULE
'archetypes' => array(
),
),
);
@@ -0,0 +1,29 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* Strings for component 'webservice_soap', language 'en', branch 'MOODLE_20_STABLE'
*
* @package webservice_soap
* @category string
* @copyright 2010 Petr Skodak
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['pluginname'] = 'SOAP protocol';
$string['privacy:metadata'] = 'The SOAP protocol plugin does not store any personal data.';
$string['soap:use'] = 'Use SOAP protocol';
+93
View File
@@ -0,0 +1,93 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* Moodle SOAP library
*
* @package webservice_soap
* @copyright 2009 Jerome Mouneyrac
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Moodle SOAP client
*
* It has been implemented for unit testing purpose (all protocols have similar client)
*
* @package webservice_soap
* @copyright 2010 Jerome Mouneyrac
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class webservice_soap_client {
/** @var moodle_url The server url. */
private $serverurl;
/** @var string The WS token. */
private $token;
/** @var array|null SOAP options. */
private $options;
/**
* Constructor
*
* @param string $serverurl a Moodle URL
* @param string $token the token used to do the web service call
* @param array $options PHP SOAP client options - see php.net
*/
public function __construct($serverurl, $token = null, array $options = null) {
$this->serverurl = new moodle_url($serverurl);
$this->token = $token ?: $this->serverurl->get_param('wstoken');
$this->options = $options ?: array();
}
/**
* Set the token used to do the SOAP call
*
* @param string $token the token used to do the web service call
*/
public function set_token($token) {
$this->token = $token;
}
/**
* Execute client WS request with token authentication
*
* @param string $functionname the function name
* @param array $params the parameters of the function
* @return mixed
*/
public function call($functionname, $params) {
if ($this->token) {
$this->serverurl->param('wstoken', $this->token);
}
$this->serverurl->param('wsdl', 1);
$opts = array(
'http' => array(
'user_agent' => 'Moodle SOAP Client'
)
);
$context = stream_context_create($opts);
$this->options['stream_context'] = $context;
$this->options['cache_wsdl'] = WSDL_CACHE_NONE;
$client = new SoapClient($this->serverurl->out(false), $this->options);
return $client->__soapCall($functionname, $params);
}
}
+336
View File
@@ -0,0 +1,336 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* SOAP web service implementation classes and methods.
*
* @package webservice_soap
* @copyright 2009 Petr Skodak
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
global $CFG;
require_once($CFG->dirroot . '/webservice/lib.php');
use webservice_soap\wsdl;
/**
* SOAP service server implementation.
*
* @package webservice_soap
* @copyright 2009 Petr Skodak
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.0
*/
class webservice_soap_server extends webservice_base_server {
/** @var moodle_url The server URL. */
protected $serverurl;
/** @var SoapServer The Soap */
protected $soapserver;
/** @var string The response. */
protected $response;
/** @var string The class name of the virtual class generated for this web service. */
protected $serviceclass;
/** @var bool WSDL mode flag. */
protected $wsdlmode;
/** @var \webservice_soap\wsdl The object for WSDL generation. */
protected $wsdl;
/**
* Contructor.
*
* @param string $authmethod authentication method of the web service (WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN, ...)
*/
public function __construct($authmethod) {
parent::__construct($authmethod);
// Must not cache wsdl - the list of functions is created on the fly.
ini_set('soap.wsdl_cache_enabled', '0');
$this->wsname = 'soap';
$this->wsdlmode = false;
}
/**
* This method parses the $_POST and $_GET superglobals and looks for the following information:
* - User authentication parameters:
* - Username + password (wsusername and wspassword), or
* - Token (wstoken)
*/
protected function parse_request() {
// Retrieve and clean the POST/GET parameters from the parameters specific to the server.
parent::set_web_service_call_settings();
if ($this->authmethod == WEBSERVICE_AUTHMETHOD_USERNAME) {
$this->username = optional_param('wsusername', null, PARAM_RAW);
$this->password = optional_param('wspassword', null, PARAM_RAW);
if (!$this->username or !$this->password) {
// Workaround for the trouble with & in soap urls.
$authdata = get_file_argument();
$authdata = explode('/', trim($authdata, '/'));
if (count($authdata) == 2) {
list($this->username, $this->password) = $authdata;
}
}
$this->serverurl = new moodle_url('/webservice/soap/simpleserver.php/' . $this->username . '/' . $this->password);
} else {
$this->token = optional_param('wstoken', null, PARAM_RAW);
$this->serverurl = new moodle_url('/webservice/soap/server.php');
$this->serverurl->param('wstoken', $this->token);
}
if ($wsdl = optional_param('wsdl', 0, PARAM_INT)) {
$this->wsdlmode = true;
}
}
/**
* Runs the SOAP web service.
*
* @throws coding_exception
* @throws moodle_exception
* @throws webservice_access_exception
*/
public function run() {
// We will probably need a lot of memory in some functions.
raise_memory_limit(MEMORY_EXTRA);
// Set some longer timeout since operations may need longer time to finish.
\core_external\external_api::set_timeout();
// Set up exception handler.
set_exception_handler(array($this, 'exception_handler'));
// Init all properties from the request data.
$this->parse_request();
// Authenticate user, this has to be done after the request parsing. This also sets up $USER and $SESSION.
$this->authenticate_user();
// Make a list of all functions user is allowed to execute.
$this->init_service_class();
if ($this->wsdlmode) {
// Generate the WSDL.
$this->generate_wsdl();
}
// Log the web service request.
$params = array(
'other' => array(
'function' => 'unknown'
)
);
/** @var \core\event\webservice_function_called $event */
$event = \core\event\webservice_function_called::create($params);
$event->trigger();
// Handle the SOAP request.
$this->handle();
// Session cleanup.
$this->session_cleanup();
die;
}
/**
* Generates the WSDL.
*/
protected function generate_wsdl() {
// Initialise WSDL.
$this->wsdl = new wsdl($this->serviceclass, $this->serverurl);
// Register service struct classes as complex types.
foreach ($this->servicestructs as $structinfo) {
$this->wsdl->add_complex_type($structinfo->classname, $structinfo->properties);
}
// Register the method for the WSDL generation.
foreach ($this->servicemethods as $methodinfo) {
$this->wsdl->register($methodinfo->name, $methodinfo->inputparams, $methodinfo->outputparams, $methodinfo->description);
}
}
/**
* Handles the web service function call.
*/
protected function handle() {
if ($this->wsdlmode) {
// Prepare the response.
$this->response = $this->wsdl->to_xml();
// Send the results back in correct format.
$this->send_response();
} else {
$wsdlurl = clone($this->serverurl);
$wsdlurl->param('wsdl', 1);
$options = array(
'uri' => $this->serverurl->out(false)
);
// Initialise the SOAP server.
$this->soapserver = new SoapServer($wsdlurl->out(false), $options);
if (!empty($this->serviceclass)) {
$this->soapserver->setClass($this->serviceclass);
// Get all the methods for the generated service class then register to the SOAP server.
$functions = get_class_methods($this->serviceclass);
$this->soapserver->addFunction($functions);
}
// Get soap request from raw POST data.
$soaprequest = file_get_contents('php://input');
// Handle the request.
try {
$this->soapserver->handle($soaprequest);
} catch (Exception $e) {
$this->fault($e);
}
}
}
/**
* Send the error information to the WS client formatted as an XML document.
*
* @param Exception $ex the exception to send back
*/
protected function send_error($ex = null) {
if ($ex) {
$info = $ex->getMessage();
if (debugging() and isset($ex->debuginfo)) {
$info .= ' - '.$ex->debuginfo;
}
} else {
$info = 'Unknown error';
}
// Initialise new DOM document object.
$dom = new DOMDocument('1.0', 'UTF-8');
// Fault node.
$fault = $dom->createElement('SOAP-ENV:Fault');
// Faultcode node.
$fault->appendChild($dom->createElement('faultcode', 'MOODLE:error'));
// Faultstring node.
$fault->appendChild($dom->createElement('faultstring', $info));
// Body node.
$body = $dom->createElement('SOAP-ENV:Body');
$body->appendChild($fault);
// Envelope node.
$envelope = $dom->createElement('SOAP-ENV:Envelope');
$envelope->setAttribute('xmlns:SOAP-ENV', 'http://schemas.xmlsoap.org/soap/envelope/');
$envelope->appendChild($body);
$dom->appendChild($envelope);
$this->response = $dom->saveXML();
$this->send_response();
}
/**
* Send the result of function call to the WS client.
*/
protected function send_response() {
$this->send_headers();
echo $this->response;
}
/**
* Internal implementation - sending of page headers.
*/
protected function send_headers() {
header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
header('Expires: ' . gmdate('D, d M Y H:i:s', 0) . ' GMT');
header('Pragma: no-cache');
header('Accept-Ranges: none');
header('Content-Length: ' . strlen($this->response));
header('Content-Type: application/xml; charset=utf-8');
header('Content-Disposition: inline; filename="response.xml"');
}
/**
* Generate a server fault.
*
* Note that the parameter order is the reverse of SoapFault's constructor parameters.
*
* Moodle note: basically we return the faultactor (errorcode) and faultdetails (debuginfo).
*
* If an exception is passed as the first argument, its message and code
* will be used to create the fault object.
*
* @link http://www.w3.org/TR/soap12-part1/#faultcodes
* @param string|Exception $fault
* @param string $code SOAP Fault Codes
*/
public function fault($fault = null, $code = 'Receiver') {
$allowedfaultmodes = array(
'VersionMismatch', 'MustUnderstand', 'DataEncodingUnknown',
'Sender', 'Receiver', 'Server'
);
if (!in_array($code, $allowedfaultmodes)) {
$code = 'Receiver';
}
// Intercept any exceptions and add the errorcode and debuginfo (optional).
$actor = null;
$details = null;
$errorcode = 'unknownerror';
$message = get_string($errorcode);
if ($fault instanceof Exception) {
// Add the debuginfo to the exception message if debuginfo must be returned.
$actor = isset($fault->errorcode) ? $fault->errorcode : null;
$errorcode = $actor;
if (debugging()) {
$message = $fault->getMessage();
$details = isset($fault->debuginfo) ? $fault->debuginfo : null;
}
} else if (is_string($fault)) {
$message = $fault;
}
$this->soapserver->fault($code, $message . ' | ERRORCODE: ' . $errorcode, $actor, $details);
}
}
/**
* SOAP test client class
*
* @package webservice_soap
* @copyright 2009 Petr Skodak
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 2.0
*/
class webservice_soap_test_client implements webservice_test_client_interface {
/**
* Execute test client WS request
*
* @param string $serverurl server url (including token parameter or username/password parameters)
* @param string $function function name
* @param array $params parameters of the called function
* @return mixed
*/
public function simpletest($serverurl, $function, $params) {
global $CFG;
require_once($CFG->dirroot . '/webservice/soap/lib.php');
$client = new webservice_soap_client($serverurl);
return $client->call($function, $params);
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* SOAP web service entry point. The authentication is done via tokens.
*
* @package webservice_soap
* @copyright 2009 Jerome Mouneyrac
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* NO_DEBUG_DISPLAY - disable moodle specific debug messages and any errors in output
*/
define('NO_DEBUG_DISPLAY', true);
define('WS_SERVER', true);
require('../../config.php');
require_once("$CFG->dirroot/webservice/soap/locallib.php");
if (!webservice_protocol_is_enabled('soap')) {
debugging('The server died because the web services or the SOAP protocol are not enable',
DEBUG_DEVELOPER);
die;
}
$server = new webservice_soap_server(WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN);
$server->run();
die;
/**
* Raises Early WS Exception in SOAP format.
*
* @param Exception $ex Raised exception.
*/
function raise_early_ws_exception(Exception $ex): void {
global $CFG;
require_once("$CFG->dirroot/webservice/soap/locallib.php");
$server = new webservice_soap_server(WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN);
$server->exception_handler($ex);
}
+44
View File
@@ -0,0 +1,44 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* SOAP web service entry point. The authentication is done via username/password.
*
* @package webservice_soap
* @copyright 2009 Petr Skodak
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* NO_DEBUG_DISPLAY - disable moodle specific debug messages and any errors in output
*/
define('NO_DEBUG_DISPLAY', true);
define('WS_SERVER', true);
require('../../config.php');
require_once("$CFG->dirroot/webservice/soap/locallib.php");
if (!webservice_protocol_is_enabled('soap')) {
die;
}
$server = new webservice_soap_server(WEBSERVICE_AUTHMETHOD_USERNAME);
$server->run();
die;
+372
View File
@@ -0,0 +1,372 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* Unit tests for the WSDL class.
*
* @package webservice_soap
* @category test
* @copyright 2016 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace webservice_soap;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/soap/classes/wsdl.php');
/**
* Unit tests for the WSDL class.
*
* @package webservice_soap
* @category test
* @copyright 2016 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class wsdl_test extends \advanced_testcase {
/**
* Test generated WSDL with no added complex types nor functions.
*/
public function test_minimum_wsdl(): void {
$this->resetAfterTest();
$serviceclass = 'testserviceclass';
$namespace = 'testnamespace';
$wsdl = new wsdl($serviceclass, $namespace);
// Test definitions node.
$definitions = new \SimpleXMLElement($wsdl->to_xml());
$defattrs = $definitions->attributes();
$this->assertEquals($serviceclass, $defattrs->name);
$this->assertEquals($namespace, $defattrs->targetNamespace);
// Test types node and attributes.
$this->assertNotNull($definitions->types);
$this->assertEquals($namespace, $definitions->types->children('xsd', true)->schema->attributes()->targetNamespace);
// Test portType node and attributes.
$this->assertNotNull($definitions->portType);
$this->assertEquals($serviceclass . wsdl::PORT, $definitions->portType->attributes()->name);
// Test binding node and attributes.
$this->assertNotNull($definitions->binding);
$this->assertEquals($serviceclass . wsdl::BINDING, $definitions->binding->attributes()->name);
$this->assertEquals('tns:' . $serviceclass . wsdl::PORT, $definitions->binding->attributes()->type);
$bindingattrs = $definitions->binding->children('soap', true)->binding->attributes();
$this->assertNotEmpty('rpc', $bindingattrs);
$this->assertEquals('rpc', $bindingattrs->style);
$this->assertEquals(wsdl::NS_SOAP_TRANSPORT, $bindingattrs->transport);
// Test service node.
$this->assertNotNull($definitions->service);
$this->assertEquals($serviceclass . wsdl::SERVICE, $definitions->service->attributes()->name);
$serviceport = $definitions->service->children()->port;
$this->assertNotEmpty($serviceport);
$this->assertEquals($serviceclass . wsdl::PORT, $serviceport->attributes()->name);
$this->assertEquals('tns:' . $serviceclass . wsdl::BINDING, $serviceport->attributes()->binding);
$serviceportaddress = $serviceport->children('soap', true)->address;
$this->assertNotEmpty($serviceportaddress);
$this->assertEquals($namespace, $serviceportaddress->attributes()->location);
}
/**
* Test output WSDL with complex type added.
*/
public function test_add_complex_type(): void {
$this->resetAfterTest();
$classname = 'testcomplextype';
$classattrs = array(
'doubleparam' => array(
'type' => 'double',
'nillable' => true
),
'stringparam' => array(
'type' => 'string',
'nillable' => true
),
'intparam' => array(
'type' => 'int',
'nillable' => true
),
'boolparam' => array(
'type' => 'int',
'nillable' => true
),
'classparam' => array(
'type' => 'teststruct'
),
'arrayparam' => array(
'type' => 'array',
'nillable' => true
),
);
$serviceclass = 'testserviceclass';
$namespace = 'testnamespace';
$wsdl = new wsdl($serviceclass, $namespace);
$wsdl->add_complex_type($classname, $classattrs);
$definitions = new \SimpleXMLElement($wsdl->to_xml());
// Test types node and attributes.
$this->assertNotNull($definitions->types);
$this->assertEquals($namespace, $definitions->types->children('xsd', true)->schema->attributes()->targetNamespace);
$complextype = $definitions->types->children('xsd', true)->schema->children('xsd', true);
$this->assertNotEmpty($complextype);
// Test the complex type's attributes.
foreach ($complextype->children('xsd', true)->all->children('xsd', true) as $element) {
foreach ($classattrs as $name => $options) {
if (strcmp($name, $element->attributes()->name) != 0) {
continue;
}
switch ($options['type']) {
case 'double':
case 'int':
case 'string':
$this->assertEquals('xsd:' . $options['type'], $element->attributes()->type);
break;
case 'array':
$this->assertEquals('soap-enc:' . ucfirst($options['type']), $element->attributes()->type);
break;
default:
$this->assertEquals('tns:' . $options['type'], $element->attributes()->type);
break;
}
if (!empty($options['nillable'])) {
$this->assertEquals('true', $element->attributes()->nillable);
}
break;
}
}
}
/**
* Test output WSDL when registering a web service function.
*/
public function test_register(): void {
$this->resetAfterTest();
$serviceclass = 'testserviceclass';
$namespace = 'testnamespace';
$wsdl = new wsdl($serviceclass, $namespace);
$functionname = 'testfunction';
$documentation = 'This is a test function';
$in = array(
'doubleparam' => array(
'type' => 'double'
),
'stringparam' => array(
'type' => 'string'
),
'intparam' => array(
'type' => 'int'
),
'boolparam' => array(
'type' => 'int'
),
'classparam' => array(
'type' => 'teststruct'
),
'arrayparam' => array(
'type' => 'array'
)
);
$out = array(
'doubleparam' => array(
'type' => 'double'
),
'stringparam' => array(
'type' => 'string'
),
'intparam' => array(
'type' => 'int'
),
'boolparam' => array(
'type' => 'int'
),
'classparam' => array(
'type' => 'teststruct'
),
'arrayparam' => array(
'type' => 'array'
),
'return' => array(
'type' => 'teststruct2'
)
);
$wsdl->register($functionname, $in, $out, $documentation);
$definitions = new \SimpleXMLElement($wsdl->to_xml());
// Test portType operation node.
$porttypeoperation = $definitions->portType->operation;
$this->assertEquals($documentation, $porttypeoperation->documentation);
$this->assertEquals('tns:' . $functionname . wsdl::IN, $porttypeoperation->input->attributes()->message);
$this->assertEquals('tns:' . $functionname . wsdl::OUT, $porttypeoperation->output->attributes()->message);
// Test binding operation nodes.
$bindingoperation = $definitions->binding->operation;
$soapoperation = $bindingoperation->children('soap', true)->operation;
$this->assertEquals($namespace . '#' . $functionname, $soapoperation->attributes()->soapAction);
$inputbody = $bindingoperation->input->children('soap', true);
$this->assertEquals('encoded', $inputbody->attributes()->use);
$this->assertEquals(wsdl::NS_SOAP_ENC, $inputbody->attributes()->encodingStyle);
$this->assertEquals($namespace, $inputbody->attributes()->namespace);
$outputbody = $bindingoperation->output->children('soap', true);
$this->assertEquals('encoded', $outputbody->attributes()->use);
$this->assertEquals(wsdl::NS_SOAP_ENC, $outputbody->attributes()->encodingStyle);
$this->assertEquals($namespace, $outputbody->attributes()->namespace);
// Test messages.
$messagein = $definitions->message[0];
$this->assertEquals($functionname . wsdl::IN, $messagein->attributes()->name);
foreach ($messagein->children() as $part) {
foreach ($in as $name => $options) {
if (strcmp($name, $part->attributes()->name) != 0) {
continue;
}
switch ($options['type']) {
case 'double':
case 'int':
case 'string':
$this->assertEquals('xsd:' . $options['type'], $part->attributes()->type);
break;
case 'array':
$this->assertEquals('soap-enc:' . ucfirst($options['type']), $part->attributes()->type);
break;
default:
$this->assertEquals('tns:' . $options['type'], $part->attributes()->type);
break;
}
break;
}
}
$messageout = $definitions->message[1];
$this->assertEquals($functionname . wsdl::OUT, $messageout->attributes()->name);
foreach ($messageout->children() as $part) {
foreach ($out as $name => $options) {
if (strcmp($name, $part->attributes()->name) != 0) {
continue;
}
switch ($options['type']) {
case 'double':
case 'int':
case 'string':
$this->assertEquals('xsd:' . $options['type'], $part->attributes()->type);
break;
case 'array':
$this->assertEquals('soap-enc:' . ucfirst($options['type']), $part->attributes()->type);
break;
default:
$this->assertEquals('tns:' . $options['type'], $part->attributes()->type);
break;
}
break;
}
}
}
/**
* Test output WSDL when registering a web service function with no input parameters.
*/
public function test_register_without_input(): void {
$this->resetAfterTest();
$serviceclass = 'testserviceclass';
$namespace = 'testnamespace';
$wsdl = new wsdl($serviceclass, $namespace);
$functionname = 'testfunction';
$documentation = 'This is a test function';
$out = array(
'return' => array(
'type' => 'teststruct2'
)
);
$wsdl->register($functionname, null, $out, $documentation);
$definitions = new \SimpleXMLElement($wsdl->to_xml());
// Test portType operation node.
$porttypeoperation = $definitions->portType->operation;
$this->assertEquals($documentation, $porttypeoperation->documentation);
$this->assertFalse(isset($porttypeoperation->input));
$this->assertTrue(isset($porttypeoperation->output));
// Test binding operation nodes.
$bindingoperation = $definitions->binding->operation;
// Confirm that there is no input node.
$this->assertFalse(isset($bindingoperation->input));
$this->assertTrue(isset($bindingoperation->output));
// Test messages.
// Assert there's only the output message node.
$this->assertEquals(1, count($definitions->message));
$messageout = $definitions->message[0];
$this->assertEquals($functionname . wsdl::OUT, $messageout->attributes()->name);
}
/**
* Test output WSDL when registering a web service function with no output parameters.
*/
public function test_register_without_output(): void {
$this->resetAfterTest();
$serviceclass = 'testserviceclass';
$namespace = 'testnamespace';
$wsdl = new wsdl($serviceclass, $namespace);
$functionname = 'testfunction';
$documentation = 'This is a test function';
$in = array(
'return' => array(
'type' => 'teststruct2'
)
);
$wsdl->register($functionname, $in, null, $documentation);
$definitions = new \SimpleXMLElement($wsdl->to_xml());
// Test portType operation node.
$porttypeoperation = $definitions->portType->operation;
$this->assertEquals($documentation, $porttypeoperation->documentation);
$this->assertTrue(isset($porttypeoperation->input));
$this->assertFalse(isset($porttypeoperation->output));
// Test binding operation nodes.
$bindingoperation = $definitions->binding->operation;
// Confirm that there is no input node.
$this->assertTrue(isset($bindingoperation->input));
$this->assertFalse(isset($bindingoperation->output));
// Test messages.
// Assert there's only the output message node.
$this->assertEquals(1, count($definitions->message));
$messagein = $definitions->message[0];
$this->assertEquals($functionname . wsdl::IN, $messagein->attributes()->name);
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* Version details
*
* @package webservice_soap
* @copyright 2009 Petr Skodak
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'webservice_soap'; // Full name of the plugin (used for diagnostics)
+334
View File
@@ -0,0 +1,334 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* Unit tests for Web service events.
*
* @package webservice
* @category phpunit
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_webservice\event;
/**
* Unit tests for Web service events.
*
* @package webservice
* @category phpunit
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class events_test extends \advanced_testcase {
public function setUp(): void {
$this->resetAfterTest();
}
public function test_function_called(): void {
// The Web service API doesn't allow the testing of the events directly by
// calling some functions which trigger the events, so what we are going here
// is just checking that the event returns the expected information.
$sink = $this->redirectEvents();
$params = array(
'other' => array(
'function' => 'A function'
)
);
$event = \core\event\webservice_function_called::create($params);
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertEquals(\context_system::instance(), $event->get_context());
$this->assertEquals('A function', $event->other['function']);
$this->assertEventContextNotUsed($event);
}
public function test_login_failed(): void {
// The Web service API doesn't allow the testing of the events directly by
// calling some functions which trigger the events, so what we are going here
// is just checking that the event returns the expected information.
$sink = $this->redirectEvents();
$params = array(
'other' => array(
'reason' => 'Unit Test',
'method' => 'Some method',
'tokenid' => '123'
)
);
$event = \core\event\webservice_login_failed::create($params);
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertEquals(\context_system::instance(), $event->get_context());
$this->assertEquals($params['other']['reason'], $event->other['reason']);
$this->assertEquals($params['other']['method'], $event->other['method']);
$this->assertEquals($params['other']['tokenid'], $event->other['tokenid']);
// We cannot set the token in the other properties.
$params['other']['token'] = 'I should not be set';
try {
$event = \core\event\webservice_login_failed::create($params);
$this->fail('The token cannot be allowed in \core\event\webservice_login_failed');
} catch (\coding_exception $e) {
}
$this->assertEventContextNotUsed($event);
}
public function test_service_created(): void {
global $CFG, $DB;
// The Web service API doesn't allow the testing of the events directly by
// calling some functions which trigger the events, so what we are going here
// is just checking that the event returns the expected information.
$sink = $this->redirectEvents();
// Creating a fake service.
$service = (object) array(
'name' => 'Test',
'enabled' => 1,
'requiredcapability' => '',
'restrictedusers' => 0,
'component' => null,
'timecreated' => time(),
'timemodified' => time(),
'shortname' => null,
'downloadfiles' => 0,
'uploadfiles' => 0
);
$service->id = $DB->insert_record('external_services', $service);
// Trigger the event.
$params = array(
'objectid' => $service->id,
);
$event = \core\event\webservice_service_created::create($params);
$event->add_record_snapshot('external_services', $service);
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Assert that the event contains the right information.
$this->assertEquals(\context_system::instance(), $event->get_context());
$this->assertEquals($service->id, $event->objectid);
$this->assertEventContextNotUsed($event);
}
public function test_service_updated(): void {
global $CFG, $DB;
// The Web service API doesn't allow the testing of the events directly by
// calling some functions which trigger the events, so what we are going here
// is just checking that the event returns the expected information.
$sink = $this->redirectEvents();
// Creating a fake service.
$service = (object) array(
'name' => 'Test',
'enabled' => 1,
'requiredcapability' => '',
'restrictedusers' => 0,
'component' => null,
'timecreated' => time(),
'timemodified' => time(),
'shortname' => null,
'downloadfiles' => 0,
'uploadfiles' => 0
);
$service->id = $DB->insert_record('external_services', $service);
// Trigger the event.
$params = array(
'objectid' => $service->id,
);
$event = \core\event\webservice_service_updated::create($params);
$event->add_record_snapshot('external_services', $service);
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Assert that the event contains the right information.
$this->assertEquals(\context_system::instance(), $event->get_context());
$this->assertEquals($service->id, $event->objectid);
$this->assertEventContextNotUsed($event);
}
public function test_service_deleted(): void {
global $CFG, $DB;
// The Web service API doesn't allow the testing of the events directly by
// calling some functions which trigger the events, so what we are going here
// is just checking that the event returns the expected information.
$sink = $this->redirectEvents();
// Creating a fake service.
$service = (object) array(
'name' => 'Test',
'enabled' => 1,
'requiredcapability' => '',
'restrictedusers' => 0,
'component' => null,
'timecreated' => time(),
'timemodified' => time(),
'shortname' => null,
'downloadfiles' => 0,
'uploadfiles' => 0
);
$service->id = $DB->insert_record('external_services', $service);
// Trigger the event.
$params = array(
'objectid' => $service->id,
);
$event = \core\event\webservice_service_deleted::create($params);
$event->add_record_snapshot('external_services', $service);
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Assert that the event contains the right information.
$this->assertEquals(\context_system::instance(), $event->get_context());
$this->assertEquals($service->id, $event->objectid);
$this->assertEventContextNotUsed($event);
}
public function test_service_user_added(): void {
global $CFG;
// The Web service API doesn't allow the testing of the events directly by
// calling some functions which trigger the events, so what we are going here
// is just checking that the event returns the expected information.
$sink = $this->redirectEvents();
$params = array(
'objectid' => 1,
'relateduserid' => 2
);
$event = \core\event\webservice_service_user_added::create($params);
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertEquals(\context_system::instance(), $event->get_context());
$this->assertEquals(1, $event->objectid);
$this->assertEquals(2, $event->relateduserid);
$this->assertEventContextNotUsed($event);
}
public function test_service_user_removed(): void {
global $CFG;
// The Web service API doesn't allow the testing of the events directly by
// calling some functions which trigger the events, so what we are going here
// is just checking that the event returns the expected information.
$sink = $this->redirectEvents();
$params = array(
'objectid' => 1,
'relateduserid' => 2
);
$event = \core\event\webservice_service_user_removed::create($params);
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertEquals(\context_system::instance(), $event->get_context());
$this->assertEquals(1, $event->objectid);
$this->assertEquals(2, $event->relateduserid);
$this->assertEventContextNotUsed($event);
}
public function test_token_created(): void {
// The Web service API doesn't allow the testing of the events directly by
// calling some functions which trigger the events, so what we are going here
// is just checking that the event returns the expected information.
$sink = $this->redirectEvents();
$params = array(
'objectid' => 1,
'relateduserid' => 2,
'other' => array(
'auto' => true
)
);
$event = \core\event\webservice_token_created::create($params);
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertEquals(\context_system::instance(), $event->get_context());
$this->assertEquals(1, $event->objectid);
$this->assertEquals(2, $event->relateduserid);
$this->assertEventContextNotUsed($event);
}
public function test_token_sent(): void {
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
// The Web service API doesn't allow the testing of the events directly by
// calling some functions which trigger the events, so what we are going here
// is just checking that the event returns the expected information.
$sink = $this->redirectEvents();
$params = array(
'objectid' => 1,
'other' => array(
'auto' => true
)
);
$event = \core\event\webservice_token_sent::create($params);
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertEquals(\context_system::instance(), $event->get_context());
$this->assertEquals(1, $event->objectid);
$this->assertEventContextNotUsed($event);
}
}
+281
View File
@@ -0,0 +1,281 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
namespace core_webservice;
use core_external\external_api;
use externallib_advanced_testcase;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/externallib.php');
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
/**
* External course functions unit tests
*
* @package core_webservice
* @category external
* @copyright 2012 Paul Charsley
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class externallib_test extends externallib_advanced_testcase {
public function setUp(): void {
// Calling parent is good, always
parent::setUp();
// We always need enabled WS for this testcase
set_config('enablewebservices', '1');
}
public function test_get_site_info(): void {
global $DB, $USER, $CFG, $PAGE;
$this->resetAfterTest(true);
$maxbytes = 10485760;
$userquota = 5242880;
set_config('maxbytes', $maxbytes);
set_config('userquota', $userquota);
// Set current user
set_config('allowuserthemes', 1);
$user = array();
$user['username'] = 'johnd';
$user['firstname'] = 'John';
$user['lastname'] = 'Doe';
$user['theme'] = 'boost';
self::setUser(self::getDataGenerator()->create_user($user));
// Add a web service and token.
$webservice = new \stdClass();
$webservice->name = 'Test web service';
$webservice->enabled = true;
$webservice->restrictedusers = false;
$webservice->component = 'moodle';
$webservice->timecreated = time();
$webservice->downloadfiles = true;
$webservice->uploadfiles = true;
$externalserviceid = $DB->insert_record('external_services', $webservice);
// Add a function to the service
$DB->insert_record('external_services_functions', array('externalserviceid' => $externalserviceid,
'functionname' => 'core_course_get_contents'));
$_POST['wstoken'] = 'testtoken';
$externaltoken = new \stdClass();
$externaltoken->token = 'testtoken';
$externaltoken->tokentype = 0;
$externaltoken->userid = $USER->id;
$externaltoken->externalserviceid = $externalserviceid;
$externaltoken->contextid = 1;
$externaltoken->creatorid = $USER->id;
$externaltoken->timecreated = time();
$externaltoken->name = \core_external\util::generate_token_name();
$DB->insert_record('external_tokens', $externaltoken);
$siteinfo = \core_webservice_external::get_site_info();
// We need to execute the return values cleaning process to simulate the web service server.
$siteinfo = external_api::clean_returnvalue(\core_webservice_external::get_site_info_returns(), $siteinfo);
$this->assertEquals('johnd', $siteinfo['username']);
$this->assertEquals('John', $siteinfo['firstname']);
$this->assertEquals('Doe', $siteinfo['lastname']);
$this->assertEquals(current_language(), $siteinfo['lang']);
$this->assertEquals($USER->id, $siteinfo['userid']);
$this->assertEquals(SITEID, $siteinfo['siteid']);
$this->assertEquals(true, $siteinfo['downloadfiles']);
$this->assertEquals($CFG->release, $siteinfo['release']);
$this->assertEquals($CFG->version, $siteinfo['version']);
$this->assertEquals('', $siteinfo['mobilecssurl']);
$this->assertEquals(count($siteinfo['functions']), 1);
$function = array_pop($siteinfo['functions']);
$this->assertEquals($function['name'], 'core_course_get_contents');
$this->assertEquals($function['version'], $siteinfo['version']);
$this->assertEquals(1, $siteinfo['downloadfiles']);
$this->assertEquals(1, $siteinfo['uploadfiles']);
$this->assertCount(12, $siteinfo['advancedfeatures']);
foreach ($siteinfo['advancedfeatures'] as $feature) {
if ($feature['name'] == 'mnet_dispatcher_mode') {
if ($CFG->mnet_dispatcher_mode == 'off') {
$this->assertEquals(0, $feature['value']);
} else {
$this->assertEquals(1, $feature['value']);
}
} else if ($feature['name'] == 'enablecompetencies') {
$expected = (!empty(get_config('core_competency', 'enabled'))) ? 1 : 0;
$this->assertEquals($expected, $feature['value']);
} else {
$this->assertEquals($CFG->{$feature['name']}, $feature['value']);
}
}
$this->assertEquals($userquota, $siteinfo['userquota']);
// We can use the function for the expectation because USER_CAN_IGNORE_FILE_SIZE_LIMITS is
// covered below for admin user. This test is for user not allowed to ignore limits.
$this->assertEquals(get_max_upload_file_size($maxbytes), $siteinfo['usermaxuploadfilesize']);
$this->assertEquals(true, $siteinfo['usercanmanageownfiles']);
$userkey = get_user_key('core_files', $USER->id);
$this->assertEquals($userkey, $siteinfo['userprivateaccesskey']);
$this->assertEquals(HOMEPAGE_MY, $siteinfo['userhomepage']);
$this->assertEquals($CFG->calendartype, $siteinfo['sitecalendartype']);
if (!empty($USER->calendartype)) {
$this->assertEquals($USER->calendartype, $siteinfo['usercalendartype']);
} else {
$this->assertEquals($CFG->calendartype, $siteinfo['usercalendartype']);
}
$this->assertFalse($siteinfo['userissiteadmin']);
$this->assertEquals($CFG->calendartype, $siteinfo['sitecalendartype']);
$this->assertEquals($user['theme'], $siteinfo['theme']);
$this->assertEquals($USER->policyagreed, $siteinfo['policyagreed']);
// Now as admin.
$this->setAdminUser();
// Set a fake token for the user admin.
$_POST['wstoken'] = 'testtoken';
$externaltoken = new \stdClass();
$externaltoken->token = 'testtoken';
$externaltoken->tokentype = 0;
$externaltoken->userid = $USER->id;
$externaltoken->externalserviceid = $externalserviceid;
$externaltoken->contextid = 1;
$externaltoken->creatorid = $USER->id;
$externaltoken->timecreated = time();
$externaltoken->name = \core_external\util::generate_token_name();
$DB->insert_record('external_tokens', $externaltoken);
// Set a home page by user preferences.
$CFG->defaulthomepage = HOMEPAGE_USER;
set_user_preference('user_home_page_preference', HOMEPAGE_SITE);
$siteinfo = \core_webservice_external::get_site_info();
// We need to execute the return values cleaning process to simulate the web service server.
$siteinfo = external_api::clean_returnvalue(\core_webservice_external::get_site_info_returns(), $siteinfo);
$this->assertEquals(0, $siteinfo['userquota']);
$this->assertEquals(USER_CAN_IGNORE_FILE_SIZE_LIMITS, $siteinfo['usermaxuploadfilesize']);
$this->assertEquals(true, $siteinfo['usercanmanageownfiles']);
$this->assertTrue($siteinfo['userissiteadmin']);
$this->assertEmpty($USER->theme);
$this->assertEquals($PAGE->theme->name, $siteinfo['theme']);
$this->assertEquals($CFG->limitconcurrentlogins, $siteinfo['limitconcurrentlogins']);
$this->assertFalse(isset($siteinfo['usersessionscount']));
$CFG->limitconcurrentlogins = 1;
$record = new \stdClass();
$record->state = 0;
$record->sessdata = null;
$record->userid = $USER->id;
$record->timemodified = time();
$record->firstip = $record->lastip = '10.0.0.1';
$record->sid = md5('hokus1');
$record->timecreated = time();
$DB->insert_record('sessions', $record);
$siteinfo = \core_webservice_external::get_site_info();
$siteinfo = external_api::clean_returnvalue(\core_webservice_external::get_site_info_returns(), $siteinfo);
$this->assertEquals($CFG->limitconcurrentlogins, $siteinfo['limitconcurrentlogins']);
$this->assertEquals(1, $siteinfo['usersessionscount']);
}
/**
* Test get_site_info with values > PHP_INT_MAX. We check only userquota since maxbytes require PHP ini changes.
*/
public function test_get_site_info_max_int(): void {
$this->resetAfterTest(true);
self::setUser(self::getDataGenerator()->create_user());
// Check values higher than PHP_INT_MAX. This value may come from settings (as string).
$userquota = PHP_INT_MAX . '000';
set_config('userquota', $userquota);
$result = \core_webservice_external::get_site_info();
$result = external_api::clean_returnvalue(\core_webservice_external::get_site_info_returns(), $result);
$this->assertEquals(PHP_INT_MAX, $result['userquota']);
}
/**
* Test get_site_info with missing components.
*/
public function test_get_site_missing_components(): void {
global $USER, $DB;
$this->resetAfterTest(true);
$this->setAdminUser();
// Add a web service and token.
$webservice = new \stdClass();
$webservice->name = 'Test web service';
$webservice->enabled = true;
$webservice->restrictedusers = false;
$webservice->component = 'moodle';
$webservice->timecreated = time();
$webservice->downloadfiles = true;
$webservice->uploadfiles = true;
$externalserviceid = $DB->insert_record('external_services', $webservice);
// Add a function to the service (missing plugin).
$DB->insert_record('external_functions',
[
'component' => 'mod_random',
'name' => 'mod_random_get_info'
]
);
// Insert one from missing component.
$DB->insert_record('external_services_functions',
[
'externalserviceid' => $externalserviceid,
'functionname' => 'mod_random_get_info'
]
);
// Insert a core one.
$DB->insert_record('external_services_functions',
[
'externalserviceid' => $externalserviceid,
'functionname' => 'core_user_get_users'
]
);
$_POST['wstoken'] = 'testtoken';
$externaltoken = new \stdClass();
$externaltoken->token = 'testtoken';
$externaltoken->tokentype = 0;
$externaltoken->userid = $USER->id;
$externaltoken->externalserviceid = $externalserviceid;
$externaltoken->contextid = 1;
$externaltoken->creatorid = $USER->id;
$externaltoken->timecreated = time();
$externaltoken->name = \core_external\util::generate_token_name();
$DB->insert_record('external_tokens', $externaltoken);
// Execution should complete.
$result = \core_webservice_external::get_site_info();
$result = external_api::clean_returnvalue(\core_webservice_external::get_site_info_returns(), $result);
// Check we ignore the missing component function.
$this->assertCount(1, $result['functions']);
$this->assertEquals('core_user_get_users', $result['functions'][0]['name']);
}
}
@@ -0,0 +1,57 @@
<?php
// This file is part of Moodle - https://moodle.org/
//
// 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 <https://www.gnu.org/licenses/>.
/**
* Behat data generator for core_webservice.
*
* @package core_webservice
* @category test
* @copyright 2021 Andrew Nicols <andrew@nicols.co.uk>
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_core_webservice_generator extends behat_generator_base {
/**
* Get the list of creatable entities for a web service.
*
* @return array
*/
protected function get_creatable_entities(): array {
return [
'Services' => [
'singular' => 'Service',
'datagenerator' => 'service',
'required' => ['name'],
],
'Service functions' => [
'singular' => 'Service function',
'datagenerator' => 'service_functions',
'required' => ['service', 'functions'],
],
'Tokens' => [
'singular' => 'Token',
'datagenerator' => 'token',
'required' => ['user'],
'switchids' => [
'user' => 'userid',
],
],
];
}
}
+142
View File
@@ -0,0 +1,142 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once(__DIR__ . '/../../lib.php');
/**
* Data generator for core_webservice plugin.
*
* @package core_webservice
* @category test
* @copyright 2021 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_webservice_generator extends component_generator_base {
/**
* Create a new webservice service.
*
* @param array $data
* @return stdClass
*/
public function create_service(array $data): \stdClass {
$webservicemanager = new webservice();
$requiredfields = [
'name',
'shortname',
];
foreach ($requiredfields as $fieldname) {
if (!array_key_exists($fieldname, $data)) {
throw new \coding_exception("Field '{$fieldname}' missing when creating new service");
}
}
$optionalfields = [
'enabled' => false,
'requiredcapability' => '',
'restrictedusers' => 0,
'component' => '',
'timemodified' => time(),
];
foreach ($optionalfields as $fieldname => $value) {
if (!array_key_exists($fieldname, $data)) {
$data[$fieldname] = $value;
}
}
$serviceid = $webservicemanager->add_external_service((object) $data);
return $webservicemanager->get_external_service_by_id($serviceid);
}
/**
* Associate a webservice function with service.
*
* @param array $data
*/
public function create_service_functions(array $data): void {
$webservicemanager = new webservice();
$requiredfields = [
'service',
'functions',
];
foreach ($requiredfields as $fieldname) {
if (!array_key_exists($fieldname, $data)) {
throw new \coding_exception("Field '{$fieldname}' missing when creating new service");
}
}
$service = $webservicemanager->get_external_service_by_shortname($data['service']);
$functions = explode(',', $data['functions']);
foreach ($functions as $functionname) {
$functionname = trim($functionname);
$webservicemanager->add_external_function_to_service($functionname, $service->id);
}
}
/**
* Create a new webservice token.
*
* @param array $data
*/
public function create_token(array $data): void {
$webservicemanager = new webservice();
$requiredfields = [
'userid',
'service',
];
foreach ($requiredfields as $fieldname) {
if (!array_key_exists($fieldname, $data)) {
throw new \coding_exception("Field '{$fieldname}' missing when creating new service");
}
}
$optionalfields = [
'context' => context_system::instance(),
'validuntil' => 0,
'iprestriction' => '',
'name' => '',
];
foreach ($optionalfields as $fieldname => $value) {
if (!array_key_exists($fieldname, $data)) {
$data[$fieldname] = $value;
}
}
$service = $webservicemanager->get_external_service_by_shortname($data['service']);
\core_external\util::generate_token(
EXTERNAL_TOKEN_PERMANENT,
$service,
$data['userid'],
$data['context'],
$data['validuntil'],
$data['iprestriction'],
$data['name']
);
}
}
+129
View File
@@ -0,0 +1,129 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
use core_external\external_settings;
/**
* Helper base class for external tests. Helpfull to test capabilities.
*
* @package core_webservice
* @copyright 2012 Jerome Mouneyrac
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class externallib_advanced_testcase extends advanced_testcase {
/**
* Assign a capability to $USER
* The function creates a student $USER if $USER->id is empty
*
* @param string $capability capability name
* @param int|context $contextid
* @param int $roleid
* @return int the role id - mainly returned for creation, so calling function can reuse it
*/
public static function assignUserCapability($capability, $contextid, $roleid = null) {
global $USER;
// Create a new student $USER if $USER doesn't exist
if (empty($USER->id)) {
$user = self::getDataGenerator()->create_user();
self::setUser($user);
}
if (empty($roleid)) {
$roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
}
assign_capability($capability, CAP_ALLOW, $roleid, $contextid);
role_assign($roleid, $USER->id, $contextid);
accesslib_clear_all_caches_for_unit_testing();
return $roleid;
}
/**
* Configure some filters for external tests.
*
* @param array $filters Filters to enable. Each filter should contain:
* - name: name of the filter.
* - state: the state of the filter.
* - move: -1 means up, 0 means the same, 1 means down.
* - applytostrings: true to apply the filter to content and headings, false for just content.
*/
public static function configure_filters($filters) {
global $CFG;
$filterstrings = false;
// Enable the filters.
foreach ($filters as $filter) {
$filter = (array) $filter;
filter_set_global_state($filter['name'], $filter['state'], $filter['move']);
filter_set_applies_to_strings($filter['name'], $filter['applytostrings']);
$filterstrings = $filterstrings || $filter['applytostrings'];
}
// Set WS filtering.
$wssettings = external_settings::get_instance();
$wssettings->set_filter(true);
// Reset filter caches.
$filtermanager = filter_manager::instance();
$filtermanager->reset_caches();
if ($filterstrings) {
// Don't strip tags in strings.
$CFG->formatstringstriptags = false;
}
}
/**
* Unassign a capability to $USER.
*
* @param string $capability capability name.
* @param int $contextid set the context id if you used assignUserCapability.
* @param int $roleid set the role id if you used assignUserCapability.
* @param int $courseid set the course id if you used getDataGenerator->enrol_users.
* @param string $enrol set the enrol plugin name if you used getDataGenerator->enrol_users with a different plugin than 'manual'.
*/
public static function unassignUserCapability($capability, $contextid = null, $roleid = null, $courseid = null, $enrol = 'manual') {
global $DB;
if (!empty($courseid)) {
// Retrieve the role id.
$instances = $DB->get_records('enrol', array('courseid'=>$courseid, 'enrol'=>$enrol));
if (count($instances) != 1) {
throw new coding_exception('No found enrol instance for courseid: ' . $courseid . ' and enrol: ' . $enrol);
}
$instance = reset($instances);
if (is_null($roleid) and $instance->roleid) {
$roleid = $instance->roleid;
}
} else {
if (empty($contextid) or empty($roleid)) {
throw new coding_exception('unassignUserCapaibility requires contextid/roleid or courseid');
}
}
unassign_capability($capability, $roleid, $contextid);
accesslib_clear_all_caches_for_unit_testing();
}
}
+454
View File
@@ -0,0 +1,454 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* Unit tests for the webservice component.
*
* @package core_webservice
* @category test
* @copyright 2016 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_webservice;
use core_external\external_api;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use webservice;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/lib.php');
/**
* Unit tests for the webservice component.
*
* @package core_webservice
* @category test
* @copyright 2016 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class lib_test extends \advanced_testcase {
/**
* Setup.
*/
public function setUp(): void {
// Calling parent is good, always.
parent::setUp();
// We always need enabled WS for this testcase.
set_config('enablewebservices', '1');
}
/**
* Test init_service_class().
*/
public function test_init_service_class(): void {
global $DB, $USER;
$this->resetAfterTest(true);
// Set current user.
$this->setAdminUser();
// Add a web service.
$webservice = new \stdClass();
$webservice->name = 'Test web service';
$webservice->enabled = true;
$webservice->restrictedusers = false;
$webservice->component = 'moodle';
$webservice->timecreated = time();
$webservice->downloadfiles = true;
$webservice->uploadfiles = true;
$externalserviceid = $DB->insert_record('external_services', $webservice);
// Add token.
$externaltoken = new \stdClass();
$externaltoken->token = 'testtoken';
$externaltoken->tokentype = 0;
$externaltoken->userid = $USER->id;
$externaltoken->externalserviceid = $externalserviceid;
$externaltoken->contextid = 1;
$externaltoken->creatorid = $USER->id;
$externaltoken->timecreated = time();
$externaltoken->name = \core_external\util::generate_token_name();
$DB->insert_record('external_tokens', $externaltoken);
// Add a function to the service.
$wsmethod = new \stdClass();
$wsmethod->externalserviceid = $externalserviceid;
$wsmethod->functionname = 'core_course_get_contents';
$DB->insert_record('external_services_functions', $wsmethod);
// Initialise the dummy web service.
$dummy = new webservice_dummy(WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN);
// Set the token.
$dummy->set_token($externaltoken->token);
// Run the web service.
$dummy->run();
// Get service methods and structs.
$servicemethods = $dummy->get_service_methods();
$servicestructs = $dummy->get_service_structs();
$this->assertNotEmpty($servicemethods);
// The function core_course_get_contents should be only the only web service function in the moment.
$this->assertEquals(1, count($servicemethods));
// The function core_course_get_contents doesn't have a struct class, so the list of service structs should be empty.
$this->assertEmpty($servicestructs);
// Add other functions to the service.
// The function core_comment_get_comments has one struct class in its output.
$wsmethod->functionname = 'core_comment_get_comments';
$DB->insert_record('external_services_functions', $wsmethod);
// The function core_grades_update_grades has one struct class in its input.
$wsmethod->functionname = 'core_grades_update_grades';
$DB->insert_record('external_services_functions', $wsmethod);
// Run the web service again.
$dummy->run();
// Get service methods and structs.
$servicemethods = $dummy->get_service_methods();
$servicestructs = $dummy->get_service_structs();
$this->assertEquals(3, count($servicemethods));
$this->assertEquals(2, count($servicestructs));
// Check the contents of service methods.
foreach ($servicemethods as $method) {
// Get the external function info.
$function = external_api::external_function_info($method->name);
// Check input params.
foreach ($function->parameters_desc->keys as $name => $keydesc) {
$this->check_params($method->inputparams[$name]['type'], $keydesc, $servicestructs);
}
// Check output params.
$this->check_params($method->outputparams['return']['type'], $function->returns_desc, $servicestructs);
// Check description.
$this->assertEquals($function->description, $method->description);
}
}
/**
* Tests update_token_lastaccess() function.
*/
public function test_update_token_lastaccess(): void {
global $DB, $USER;
$this->resetAfterTest(true);
// Set current user.
$this->setAdminUser();
// Add a web service.
$webservice = new \stdClass();
$webservice->name = 'Test web service';
$webservice->enabled = true;
$webservice->restrictedusers = false;
$webservice->component = 'moodle';
$webservice->timecreated = time();
$webservice->downloadfiles = true;
$webservice->uploadfiles = true;
$DB->insert_record('external_services', $webservice);
// Add token.
$tokenstr = \core_external\util::generate_token(
EXTERNAL_TOKEN_EMBEDDED,
\core_external\util::get_service_by_name($webservice->name),
$USER->id,
\core\context\system::instance()
);
$token = $DB->get_record('external_tokens', ['token' => $tokenstr]);
// Trigger last access once (at current time).
webservice::update_token_lastaccess($token);
// Check last access.
$token = $DB->get_record('external_tokens', ['token' => $tokenstr]);
$this->assertLessThan(5, abs(time() - $token->lastaccess));
// Try setting it to +1 second. This should not update yet.
$before = (int)$token->lastaccess;
webservice::update_token_lastaccess($token, $before + 1);
$token = $DB->get_record('external_tokens', ['token' => $tokenstr]);
$this->assertEquals($before, $token->lastaccess);
// To -1000 seconds. This should not update.
webservice::update_token_lastaccess($token, $before - 1000);
$token = $DB->get_record('external_tokens', ['token' => $tokenstr]);
$this->assertEquals($before, $token->lastaccess);
// To +59 seconds. This should also not quite update.
webservice::update_token_lastaccess($token, $before + 59);
$token = $DB->get_record('external_tokens', ['token' => $tokenstr]);
$this->assertEquals($before, $token->lastaccess);
// Finally to +60 seconds, where it should update.
webservice::update_token_lastaccess($token, $before + 60);
$token = $DB->get_record('external_tokens', ['token' => $tokenstr]);
$this->assertEquals($before + 60, $token->lastaccess);
}
/**
* Tests for the {@see webservice::get_missing_capabilities_by_users()} implementation.
*/
public function test_get_missing_capabilities_by_users(): void {
global $DB;
$this->resetAfterTest(true);
$wsman = new webservice();
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
// Add a test web service.
$serviceid = $wsman->add_external_service((object)[
'name' => 'Test web service',
'enabled' => 1,
'requiredcapability' => '',
'restrictedusers' => false,
'component' => 'moodle',
'downloadfiles' => false,
'uploadfiles' => false,
]);
// Add a function to the service that does not declare any capability as required.
$wsman->add_external_function_to_service('core_webservice_get_site_info', $serviceid);
// Users can be provided as an array of objects, arrays or integers (ids).
$this->assertEmpty($wsman->get_missing_capabilities_by_users([$user1, array($user2), $user3->id], $serviceid));
// Add a function to the service that declares some capability as required, but that capability is common for
// any user. Here we use 'core_message_delete_conversation' which declares 'moodle/site:deleteownmessage' which
// in turn is granted to the authenticated user archetype by default.
$wsman->add_external_function_to_service('core_message_delete_conversation', $serviceid);
// So all three users should have this capability implicitly.
$this->assertEmpty($wsman->get_missing_capabilities_by_users([$user1, $user2, $user3], $serviceid));
// Add a function to the service that declares some non-common capability. Here we use
// 'core_group_add_group_members' that wants 'moodle/course:managegroups'.
$wsman->add_external_function_to_service('core_group_add_group_members', $serviceid);
// Make it so that the $user1 has the capability in some course.
$course1 = $this->getDataGenerator()->create_course();
$this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'editingteacher');
// Check that no missing capability is reported for the $user1. We don't care at what actual context the
// external function call will evaluate the permission. We just check that there is a chance that the user has
// the capability somewhere.
$this->assertEmpty($wsman->get_missing_capabilities_by_users([$user1], $serviceid));
// But there is no place at the site where the capability would be granted to the other two users, so it is
// reported as missing.
$missing = $wsman->get_missing_capabilities_by_users([$user1, $user2, $user3], $serviceid);
$this->assertArrayNotHasKey($user1->id, $missing);
$this->assertContains('moodle/course:managegroups', $missing[$user2->id]);
$this->assertContains('moodle/course:managegroups', $missing[$user3->id]);
}
/**
* Data provider for {@see test_get_active_tokens}
*
* @return array
*/
public function get_active_tokens_provider(): array {
return [
'No expiration' => [0, true],
'Active' => [time() + DAYSECS, true],
'Expired' => [time() - DAYSECS, false],
];
}
/**
* Test getting active tokens for a user
*
* @param int $validuntil
* @param bool $expectedactive
*
* @dataProvider get_active_tokens_provider
*/
public function test_get_active_tokens(int $validuntil, bool $expectedactive): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
/** @var \core_webservice_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_webservice');
$service = $generator->create_service(['name' => 'My test service', 'shortname' => 'mytestservice']);
$generator->create_token(['userid' => $user->id, 'service' => $service->shortname, 'validuntil' => $validuntil]);
$tokens = webservice::get_active_tokens($user->id);
if ($expectedactive) {
$this->assertCount(1, $tokens);
$this->assertEquals($service->id, reset($tokens)->externalserviceid);
} else {
$this->assertEmpty($tokens);
}
}
/**
* Utility method that tests the parameter type of a method info's input/output parameter.
*
* @param string $type The parameter type that is being evaluated.
* @param mixed $methoddesc The method description of the WS function.
* @param array $servicestructs The list of generated service struct classes.
*/
private function check_params($type, $methoddesc, $servicestructs) {
if ($methoddesc instanceof external_value) {
// Test for simple types.
if (in_array($methoddesc->type, [PARAM_INT, PARAM_FLOAT, PARAM_BOOL])) {
$this->assertEquals($methoddesc->type, $type);
} else {
$this->assertEquals('string', $type);
}
} else if ($methoddesc instanceof external_single_structure) {
// Test that the class name of the struct class is in the array of service structs.
$structinfo = $this->get_struct_info($servicestructs, $type);
$this->assertNotNull($structinfo);
// Test that the properties of the struct info exist in the method description.
foreach ($structinfo->properties as $propname => $proptype) {
$this->assertTrue($this->in_keydesc($methoddesc, $propname));
}
} else if ($methoddesc instanceof external_multiple_structure) {
// Test for array types.
$this->assertEquals('array', $type);
}
}
/**
* Gets the struct information from the list of struct classes based on the given struct class name.
*
* @param array $structarray The list of generated struct classes.
* @param string $structclass The name of the struct class.
* @return object|null The struct class info, or null if it's not found.
*/
private function get_struct_info($structarray, $structclass) {
foreach ($structarray as $struct) {
if ($struct->classname === $structclass) {
return $struct;
}
}
return null;
}
/**
* Searches the keys of the given external_single_structure object if it contains a certain property name.
*
* @param external_single_structure $keydesc
* @param string $propertyname The property name to be searched for.
* @return bool True if the property name is found in $keydesc. False, otherwise.
*/
private function in_keydesc(external_single_structure $keydesc, $propertyname) {
foreach ($keydesc->keys as $key => $desc) {
if ($key === $propertyname) {
return true;
}
}
return false;
}
}
/**
* Class webservice_dummy.
*
* Dummy webservice class for testing the \webservice_base_server class and enable us to expose variables we want to test.
*
* @package core_webservice
* @category test
* @copyright 2016 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class webservice_dummy extends \webservice_base_server {
/**
* webservice_dummy constructor.
*
* @param int $authmethod The authentication method.
*/
public function __construct($authmethod) {
parent::__construct($authmethod);
// Arbitrarily naming this as REST in order not to have to register another WS protocol and set capabilities.
$this->wsname = 'rest';
}
/**
* Token setter method.
*
* @param string $token The web service token.
*/
public function set_token($token) {
$this->token = $token;
}
/**
* This method parses the request input, it needs to get:
* 1/ user authentication - username+password or token
* 2/ function name
* 3/ function parameters
*/
protected function parse_request() {
// Just a method stub. No need to implement at the moment since it's not really being used for this test case for now.
}
/**
* Send the result of function call to the WS client.
*/
protected function send_response() {
// Just a method stub. No need to implement at the moment since it's not really being used for this test case for now.
}
/**
* Send the error information to the WS client.
*
* @param \Exception $ex
*/
protected function send_error($ex = null) {
// Just a method stub. No need to implement at the moment since it's not really being used for this test case for now.
}
/**
* run() method implementation.
*/
public function run() {
$this->authenticate_user();
$this->init_service_class();
}
/**
* Getter method of servicemethods array.
*
* @return array
*/
public function get_service_methods() {
return $this->servicemethods;
}
/**
* Getter method of servicestructs array.
*
* @return array
*/
public function get_service_structs() {
return $this->servicestructs;
}
}
+157
View File
@@ -0,0 +1,157 @@
This files describes API changes in /webservice/*
information provided here is intended especially for developers.
This information is intended for authors of webservices, not people writing webservice clients.
=== 4.2 ===
* External function core_webservice_external::get_site_info() does not throw exceptions for missing components anymore.
=== 4.1 ===
* The XMLRPC webservice (protocol) has been completely removed. It's now available in the plugins directory.
=== 4.0 ===
* User access related exceptions have been changed to use the moodle_exception class instead of the
generic webservice_access_exception, the main reason for this change is to allow clients to
implement some code logic against an access error.
=== 3.11 ===
* The method webservice::get_user_capabilities() is deprecated now without a replacement. It has been used
internally only to populate the list of missing capabilities. That functionality has been improved so that
it no longer needs this standalone method.
=== 3.10 ===
* The class externallib_advanced_testcase, used in unit tests, has a new function called "configure_filters" to easily configure filters for external functions testing.
=== 3.8 ===
* Ajax calls can now specify a cache key. This allows for better caching capabilities on servers. If a cache key
is passed and the web service call does not require the user to be logged in we will attempt to use GET for the
request. This allows for things like proxy caching on URLs. The cache key must be changed if we do not want to
retrieve what has been cached and want to perform the request again.
* External function core_webservice_external::get_site_info() now returns the user private access key "userprivateaccesskey".
This key could be used for fetching files via the tokenpluginfile.php script instead webservice/pluginfile.php to avoid
multiple GET requests that include the WS token as a visible parameter.
* External function core_webservice_external::get_site_info() now returns a new field "userissiteadmin" indicating if
the current user is a site administrator.
=== 3.7 ===
* External function core_webservice_external::get_site_info() now returns the current site theme (for the user).
=== 3.4 ===
* External function core_webservice_external::get_site_info() now returns the calendar type used in the site and
by the user in the sitecalendartype and usercalendartype fields.
* Implementations of forms for test clients now must follow naming schema: WSFUNCTIONNAME_testclient_form
The old naming schema WSFUNCTIONNAME_form caused conflicts with existing classes.
New class webservice_test_client_base_form can be used as a base class for such forms.
=== 3.2 ===
* webservice->get_external_functions now returns the external function list ordered by name ASC.
* The filearea optional parameter has been removed from webservice/upload.php.
Since Moodle 3.1 all the uploads go to the draft area.
* external_format_text() function: component, filearea and itemid are now optional parameters.
In some contexts those parameteres are not necessary because is not required to do a file rewrite via
file_rewrite_pluginfile_urls.
* External function get_site_info now returns the site course ID. This new field is marked as VALUE_OPTIONAL for backwards compatibility.
* A new field "privatetoken" has been added to the "external_tokens" table.
This private token must be safely stored (or not stored at all) by the client because it will be used in places where a request
must be double-checked.
This token should not be passed via GET paramaters and it must be transmitted only via https.
This token is generated only in login/token.php after the user credential has been confirmed. It can't be generated by admins.
=== 3.1 ===
* The xmlrpc backend has changed, Zend_XmlRpc has been dropped and there might be slight differences in
responses. Fault strings that were generated by Zend_XmlRpc_XXX_Exception exceptions (i.e. 'Method
"[methodname]" does not exist') are no longer used which may display a different error message depending
on the string returned by the getMessage() method of the thrown exception.
* The xmlrpc server is no longer enabled when the Mobile service is activated.
* Support for the AMF protocol has been dropped completely.
* As Zend Framework has been removed, the webservice_zend_* classes have also been removed.
* Zend_SOAP has been dropped. The native PHP SoapClient and SoapServer classes are now being used instead. WSDL is now
generated by the new class webservice_soap_wsdl. For fault strings, a different error message might be shown depending
on the string returned by the getMessage() method of the thrown exception.
* With Zend_SOAP dropped, moodle_zend_soap_server is now also deprecated.
* As mentioned in the 2.9 notes, deprecated web service functions have now been removed.
* Since our new XML-RPC server implementation does not support introspection, it is critical that all clients send
parameters in the correct order.
* File uploading to the user private file area via the webservice/upload.php script is not supported anymore.
Only uploads to the draft area are allowed.
=== 3.0 ===
* WS protocols webservice/myprotocol:use capabilities were defined with a high riskbitmask value
when the fact that a user has that capability does not imply any risk, but other capabilities
that the user may have do. If your ws protocol does not imply and risk by itself, you can remove the
riskbitmask from your $capabilities array in webservice/myprotocol/db/access.php
* New function for formatting external strings: external_format_strings, it should be used as a replacement of format_string in
external functions.
All the occurrences of format_strings have been replaced with this new function.
=== 2.9 ===
* The deprecated functions can not be added to services anymore and
a debugging message for developers is triggered when viewing an existing
services using them. It is recommended to replace calls to the deprecated
functions for calls to the proposed replacements. If you are using a moodle
mobile app fork, it is recommended to update your customisations on top of
the latest moodle mobile app version.
The web services functions that will be finally deprecated in the next
moodle version are:
- moodle_course_create_courses
- moodle_course_get_courses
- moodle_enrol_get_enrolled_users
- moodle_enrol_get_users_courses
- moodle_enrol_manual_enrol_users
- moodle_file_get_files
- moodle_file_upload
- moodle_group_add_groupmembers
- moodle_group_create_groups
- moodle_group_delete_groupmembers
- moodle_group_delete_groups
- moodle_group_get_course_groups
- moodle_group_get_groupmembers
- moodle_group_get_groups
- moodle_message_send_instantmessages
- moodle_notes_create_notes
- moodle_role_assign
- moodle_role_unassign
- moodle_user_create_users
- moodle_user_delete_users
- moodle_user_get_course_participants_by_id
- moodle_user_get_users_by_courseid
- moodle_user_get_users_by_id
- moodle_user_update_users
- core_grade_get_definitions
- core_user_get_users_by_id
- moodle_webservice_get_siteinfo
* External function core_webservice_external::get_site_info now returns additional optional fields:
- advancedfeatures: Array listing Moodle advanced features and if enabled or not.
- usercanmanageownfiles: Whether the my files option is disabled.
- userquota: User storage quota.
- usermaxuploadfilesize: Files upload size limit.
=== 2.7 ===
* All webservice server.php and simpleserver.php scripts must define('WS_SERVER', true)
before including config.php file.
=== 2.6 ===
* webservice/upload.php
Accepts 2 new post parameters to allow uploading of files to a users draft area.
- filearea should be either 'private' (default) or 'draft'
- itemid unused if the filearea is 'private', for 'draft' it can be the id of a previously
created draft area - or 0 which will generate a new draft area for the files.
+181
View File
@@ -0,0 +1,181 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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/>.
/**
* Accept uploading files by web service token to the user draft file area.
*
* POST params:
* token => the web service user token (needed for authentication)
* filepath => file path (where files will be stored)
* [_FILES] => for example you can send the files with <input type=file>,
* or with curl magic: 'file_1' => '@/path/to/file', or ...
* itemid => The draftid - this can be used to add a list of files
* to a draft area in separate requests. If it is 0, a new draftid will be generated.
*
* @package core_webservice
* @copyright 2011 Dongsheng Cai <dongsheng@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* AJAX_SCRIPT - exception will be converted into JSON
*/
define('AJAX_SCRIPT', true);
/**
* NO_MOODLE_COOKIES - we don't want any cookie
*/
define('NO_MOODLE_COOKIES', true);
require_once(__DIR__ . '/../config.php');
require_once($CFG->dirroot . '/webservice/lib.php');
// Allow CORS requests.
header('Access-Control-Allow-Origin: *');
$filepath = optional_param('filepath', '/', PARAM_PATH);
$itemid = optional_param('itemid', 0, PARAM_INT);
echo $OUTPUT->header();
// Authenticate the user.
$token = required_param('token', PARAM_ALPHANUM);
$webservicelib = new webservice();
$authenticationinfo = $webservicelib->authenticate_user($token);
$fileuploaddisabled = empty($authenticationinfo['service']->uploadfiles);
if ($fileuploaddisabled) {
throw new webservice_access_exception('Web service file upload must be enabled in external service settings');
}
$context = context_user::instance($USER->id);
$fs = get_file_storage();
$totalsize = 0;
$files = array();
foreach ($_FILES as $fieldname => $uploadedfile) {
// Check upload errors.
if (!empty($_FILES[$fieldname]['error'])) {
switch ($_FILES[$fieldname]['error']) {
case UPLOAD_ERR_INI_SIZE:
throw new moodle_exception('upload_error_ini_size', 'repository_upload');
break;
case UPLOAD_ERR_FORM_SIZE:
throw new moodle_exception('upload_error_form_size', 'repository_upload');
break;
case UPLOAD_ERR_PARTIAL:
throw new moodle_exception('upload_error_partial', 'repository_upload');
break;
case UPLOAD_ERR_NO_FILE:
throw new moodle_exception('upload_error_no_file', 'repository_upload');
break;
case UPLOAD_ERR_NO_TMP_DIR:
throw new moodle_exception('upload_error_no_tmp_dir', 'repository_upload');
break;
case UPLOAD_ERR_CANT_WRITE:
throw new moodle_exception('upload_error_cant_write', 'repository_upload');
break;
case UPLOAD_ERR_EXTENSION:
throw new moodle_exception('upload_error_extension', 'repository_upload');
break;
default:
throw new moodle_exception('nofile');
}
}
// Scan for viruses.
\core\antivirus\manager::scan_file($_FILES[$fieldname]['tmp_name'], $_FILES[$fieldname]['name'], true);
$file = new stdClass();
$file->filename = clean_param($_FILES[$fieldname]['name'], PARAM_FILE);
// Check system maxbytes setting.
if (($_FILES[$fieldname]['size'] > get_max_upload_file_size($CFG->maxbytes))) {
// Oversize file will be ignored, error added to array to notify
// web service client.
$file->errortype = 'fileoversized';
$file->error = get_string('maxbytes', 'error');
} else {
$file->filepath = $_FILES[$fieldname]['tmp_name'];
// Calculate total size of upload.
$totalsize += $_FILES[$fieldname]['size'];
// Size of individual file.
$file->size = $_FILES[$fieldname]['size'];
}
$files[] = $file;
}
$fs = get_file_storage();
if ($itemid <= 0) {
$itemid = file_get_unused_draft_itemid();
}
// Get any existing file size limits.
$maxupload = get_user_max_upload_file_size($context, $CFG->maxbytes);
// Check the size of this upload.
if ($maxupload !== USER_CAN_IGNORE_FILE_SIZE_LIMITS && $totalsize > $maxupload) {
throw new file_exception('userquotalimit');
}
$results = array();
foreach ($files as $file) {
if (!empty($file->error)) {
// Including error and filename.
$results[] = $file;
continue;
}
$filerecord = new stdClass;
$filerecord->component = 'user';
$filerecord->contextid = $context->id;
$filerecord->userid = $USER->id;
$filerecord->filearea = 'draft';
$filerecord->filename = $file->filename;
$filerecord->filepath = $filepath;
$filerecord->itemid = $itemid;
$filerecord->license = $CFG->sitedefaultlicense;
$filerecord->author = fullname($authenticationinfo['user']);
$filerecord->source = serialize((object)array('source' => $file->filename));
$filerecord->filesize = $file->size;
// Check if the file already exist.
$existingfile = $fs->file_exists($filerecord->contextid, $filerecord->component, $filerecord->filearea,
$filerecord->itemid, $filerecord->filepath, $filerecord->filename);
if ($existingfile) {
$file->errortype = 'filenameexist';
$file->error = get_string('filenameexist', 'webservice', $file->filename);
$results[] = $file;
} else {
$storedfile = $fs->create_file_from_pathname($filerecord, $file->filepath);
$results[] = $filerecord;
// Log the event when a file is uploaded to the draft area.
$logevent = \core\event\draft_file_added::create([
'objectid' => $storedfile->get_id(),
'context' => $context,
'other' => [
'itemid' => $filerecord->itemid,
'filename' => $filerecord->filename,
'filesize' => $filerecord->filesize,
'filepath' => $filerecord->filepath,
'contenthash' => $storedfile->get_contenthash(),
],
]);
$logevent->trigger();
}
}
echo json_encode($results);
+92
View File
@@ -0,0 +1,92 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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 services auto-generated documentation
*
* @package core_webservice
* @copyright 2009 Jerome Mouneyrac <jerome@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../config.php');
require_once($CFG->dirroot . '/webservice/lib.php');
require_login();
$usercontext = context_user::instance($USER->id);
$tokenid = required_param('id', PARAM_INT);
// PAGE settings
$PAGE->set_context($usercontext);
$PAGE->set_url('/user/wsdoc.php');
$PAGE->set_title(get_string('wsdocumentation', 'webservice'));
$PAGE->set_pagelayout('standard');
// nav bar
$PAGE->navbar->ignore_active(true);
$PAGE->navbar->add(get_string('preferences'), new moodle_url('/user/preferences.php'));
$PAGE->navbar->add(get_string('useraccount'));
$PAGE->navbar->add(get_string('securitykeys', 'webservice'), new moodle_url('/user/managetoken.php'));
$PAGE->navbar->add(get_string('wsdocumentation', 'webservice'));
// check web service are enabled
if (empty($CFG->enablewsdocumentation)) {
echo get_string('wsdocumentationdisable', 'webservice');
die;
}
// check that the current user is the token user
$webservice = new webservice();
$token = $webservice->get_token_by_id($tokenid);
if (empty($token) or empty($token->userid) or empty($USER->id)
or ($token->userid != $USER->id)) {
throw new moodle_exception('docaccessrefused', 'webservice');
}
// get the list of all functions related to the token
$functions = $webservice->get_external_functions(array($token->externalserviceid));
// get all the function descriptions
$functiondescs = array();
foreach ($functions as $function) {
$functiondescs[$function->name] = \core_external\external_api::external_function_info($function);
}
// TODO: MDL-76078 - Incorrect inter-communication, core cannot have plugin dependencies like this.
// get activated protocol
$activatedprotocol = array();
$activatedprotocol['rest'] = webservice_protocol_is_enabled('rest');
$activatedprotocol['xmlrpc'] = webservice_protocol_is_enabled('xmlrpc');
// Check if we are in printable mode
$printableformat = optional_param('print', false, PARAM_BOOL);
// OUTPUT
echo $OUTPUT->header();
$renderer = $PAGE->get_renderer('core', 'webservice');
echo $renderer->documentation_html($functiondescs,
$printableformat, $activatedprotocol, array('id' => $tokenid));
// trigger browser print operation
if (!empty($printableformat)) {
$PAGE->requires->js_function_call('window.print', array());
}
echo $OUTPUT->footer();