ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç PK09\ draftfile.phpnu[. /** * Entry point for web service via tokens access to draftfile.php. * * @package core * @copyright 2020 Juan Leyva * @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'); PK09\5 i=i=externallib.phpnu[. 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 * @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(); if ($siteinfo['userhomepage'] === HOMEPAGE_URL) { $siteinfo['userhomepageurl'] = (string) get_default_home_page_url(); } // 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'] = count(\core\session\manager::get_sessions_by_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), 'userhomepageurl' => new external_value(PARAM_LOCALURL, 'The URL of default home page when userhomepage is 4 (HOMEPAGE_URL).', 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), ) ); } } PK09\Zsoap/db/access.phpnu[. /** * 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( ), ), ); PK09\rzaa soap/lang/en/webservice_soap.phpnu[. /** * 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'; PK09\\ \ soap/lib.phpnu[. /** * 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); } } PK09\@3b||soap/version.phpnu[. /** * 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 = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'webservice_soap'; // Full name of the plugin (used for diagnostics) PK09\ ]7!soap/classes/privacy/provider.phpnu[. /** * Privacy provider implementation for webservice_soap. * * @package webservice_soap * @copyright 2018 Mihail Geshoski * @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 * @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'; } } PK09\))soap/classes/wsdl.phpnu[. /** * 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(''); $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'])); } } } PK09\as--soap/locallib.phpnu[. /** * 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); } } PK09\gGKsoap/server.phpnu[. /** * 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); } PK09\Pr88soap/tests/wsdl_test.phpnu[. /** * Unit tests for the WSDL class. * * @package webservice_soap * @category test * @copyright 2016 Jun Pataleta * @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 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final 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); } } PK09\((soap/simpleserver.phpnu[. /** * 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; PK09\6M. renderer.phpnu[. 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 * @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 table-hover'; $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 = ''.$token->servicename.''; } $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
 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 = <<" . $brakeline;
            $return .= $this->description_in_indented_xml_format($returndescription->content,
                            $indentation);
            $return .= $indentation . "" . $brakeline;
            return $return;
        } else if ($returndescription instanceof external_single_structure) {
            // description object is an object
            $singlestructuredesc = $indentation . "" . $brakeline;
            $keyindentation = $indentation . "    ";
            foreach ($returndescription->keys as $attributname => $attribut) {
                $singlestructuredesc .= $keyindentation . ""
                        . $brakeline .
                        $this->description_in_indented_xml_format(
                                $returndescription->keys[$attributname], $keyindentation) .
                        $keyindentation . "" . $brakeline;
            }
            $singlestructuredesc .= $indentation . "" . $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 . "" . $type . "" . $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 = <<";
            $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 = <<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 = <<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 .= "        "
                        . 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 = ""
                            . $brakeline . "" . $brakeline;
                    $restresponse .= $this->description_in_indented_xml_format(
                                    $description->returns_desc);
                    $restresponse .="" . $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 = <<

    {$errormessage}
    

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;
    }

}
PK09\^lib.phpnu[.


/**
 * Web services utility functions and classes
 *
 * @package    core_webservice
 * @copyright  2009 Jerome Mouneyrac 
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

use core_external\external_api;
use core_external\external_multiple_structure;
use core_external\external_settings;
use core_external\external_single_structure;
use core_external\external_value;

/**
 * WEBSERVICE_AUTHMETHOD_USERNAME - username/password authentication (also called simple authentication)
 */
define('WEBSERVICE_AUTHMETHOD_USERNAME', 0);

/**
 * WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN - most common token authentication (external app, mobile app...)
 */
define('WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN', 1);

/**
 * WEBSERVICE_AUTHMETHOD_SESSION_TOKEN - token for embedded application (requires Moodle session)
 */
define('WEBSERVICE_AUTHMETHOD_SESSION_TOKEN', 2);

/**
 * General web service library
 *
 * @package    core_webservice
 * @copyright  2010 Jerome Mouneyrac 
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class webservice {
    /**
     * Only update token last access once per this many seconds. (This constant controls update of
     * the external tokens last access field. There is a similar define LASTACCESS_UPDATE_SECS
     * which controls update of the web site last access fields.)
     *
     * @var int
     */
    const TOKEN_LASTACCESS_UPDATE_SECS = 60;

    /**
     * Authenticate user (used by download/upload file scripts)
     *
     * @param string $token
     * @return array - contains the authenticated user, token and service objects
     */
    public function authenticate_user($token) {
        global $DB, $CFG;

        // web service must be enabled to use this script
        if (!$CFG->enablewebservices) {
            throw new webservice_access_exception('Web services are not enabled in Advanced features.');
        }

        // Obtain token record
        if (!$token = $DB->get_record('external_tokens', array('token' => $token))) {
            //client may want to display login form => moodle_exception
            throw new moodle_exception('invalidtoken', 'webservice');
        }

        $loginfaileddefaultparams = array(
            'other' => array(
                'method' => WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN,
                'reason' => null,
                'tokenid' => $token->id
            )
        );

        // Validate token date
        if ($token->validuntil and $token->validuntil < time()) {
            $params = $loginfaileddefaultparams;
            $params['other']['reason'] = 'token_expired';
            $event = \core\event\webservice_login_failed::create($params);
            $event->add_record_snapshot('external_tokens', $token);
            $event->trigger();
            $DB->delete_records('external_tokens', array('token' => $token->token));
            throw new webservice_access_exception('Invalid token - token expired - check validuntil time for the token');
        }

        // Check ip
        if ($token->iprestriction and !address_in_subnet(getremoteaddr(), $token->iprestriction)) {
            $params = $loginfaileddefaultparams;
            $params['other']['reason'] = 'ip_restricted';
            $event = \core\event\webservice_login_failed::create($params);
            $event->add_record_snapshot('external_tokens', $token);
            $event->trigger();
            throw new webservice_access_exception('Invalid token - IP:' . getremoteaddr()
                    . ' is not supported');
        }

        //retrieve user link to the token
        $user = $DB->get_record('user', array('id' => $token->userid, 'deleted' => 0), '*', MUST_EXIST);

        // let enrol plugins deal with new enrolments if necessary
        enrol_check_plugins($user, false);

        // setup user session to check capability
        \core\session\manager::set_user($user);
        set_login_session_preferences();

        //assumes that if sid is set then there must be a valid associated session no matter the token type
        if ($token->sid) {
            if (!\core\session\manager::session_exists($token->sid)) {
                $DB->delete_records('external_tokens', array('sid' => $token->sid));
                throw new webservice_access_exception('Invalid session based token - session not found or expired');
            }
        }

        // Cannot authenticate unless maintenance access is granted.
        $hasmaintenanceaccess = has_capability('moodle/site:maintenanceaccess', context_system::instance(), $user);
        if (!empty($CFG->maintenance_enabled) and !$hasmaintenanceaccess) {
            //this is usually temporary, client want to implement code logic  => moodle_exception
            throw new moodle_exception('sitemaintenance', 'admin');
        }

        //retrieve web service record
        $service = $DB->get_record('external_services', array('id' => $token->externalserviceid, 'enabled' => 1));
        if (empty($service)) {
            // will throw exception if no token found
            throw new webservice_access_exception('Web service is not available (it doesn\'t exist or might be disabled)');
        }

        //check if there is any required system capability
        if ($service->requiredcapability and !has_capability($service->requiredcapability, context_system::instance(), $user)) {
            throw new webservice_access_exception('The capability ' . $service->requiredcapability . ' is required.');
        }

        //specific checks related to user restricted service
        if ($service->restrictedusers) {
            $authoriseduser = $DB->get_record('external_services_users', array('externalserviceid' => $service->id, 'userid' => $user->id));

            if (empty($authoriseduser)) {
                throw new webservice_access_exception(
                        'The user is not allowed for this service. First you need to allow this user on the '
                        . $service->name . '\'s allowed users administration page.');
            }

            if (!empty($authoriseduser->validuntil) and $authoriseduser->validuntil < time()) {
                throw new webservice_access_exception('Invalid service - service expired - check validuntil time for this allowed user');
            }

            if (!empty($authoriseduser->iprestriction) and !address_in_subnet(getremoteaddr(), $authoriseduser->iprestriction)) {
                throw new webservice_access_exception('Invalid service - IP:' . getremoteaddr()
                    . ' is not supported - check this allowed user');
            }
        }

        //only confirmed user should be able to call web service
        if (empty($user->confirmed)) {
            $params = $loginfaileddefaultparams;
            $params['other']['reason'] = 'user_unconfirmed';
            $event = \core\event\webservice_login_failed::create($params);
            $event->add_record_snapshot('external_tokens', $token);
            $event->trigger();
            throw new moodle_exception('usernotconfirmed', 'moodle', '', $user->username);
        }

        //check the user is suspended
        if (!empty($user->suspended)) {
            $params = $loginfaileddefaultparams;
            $params['other']['reason'] = 'user_suspended';
            $event = \core\event\webservice_login_failed::create($params);
            $event->add_record_snapshot('external_tokens', $token);
            $event->trigger();
            throw new moodle_exception('wsaccessusersuspended', 'moodle', '', $user->username);
        }

        //check if the auth method is nologin (in this case refuse connection)
        if ($user->auth == 'nologin') {
            $params = $loginfaileddefaultparams;
            $params['other']['reason'] = 'nologin';
            $event = \core\event\webservice_login_failed::create($params);
            $event->add_record_snapshot('external_tokens', $token);
            $event->trigger();
            throw new moodle_exception('wsaccessusernologin', 'moodle', '', $user->username);
        }

        //Check if the user password is expired
        $auth = get_auth_plugin($user->auth);
        if (!empty($auth->config->expiration) and $auth->config->expiration == 1) {
            $days2expire = $auth->password_expire($user->username);
            if (intval($days2expire) < 0) {
                $params = $loginfaileddefaultparams;
                $params['other']['reason'] = 'password_expired';
                $event = \core\event\webservice_login_failed::create($params);
                $event->add_record_snapshot('external_tokens', $token);
                $event->trigger();
                throw new moodle_exception('passwordisexpired', 'webservice');
            }
        }

        // log token access
        self::update_token_lastaccess($token);

        return array('user' => $user, 'token' => $token, 'service' => $service);
    }

    /**
     * Updates the last access time for a token.
     *
     * @param \stdClass $token Token object (must include id, lastaccess fields)
     * @param int $time Time of access (0 = use current time)
     * @throws dml_exception If database error
     */
    public static function update_token_lastaccess($token, int $time = 0) {
        global $DB;

        if (!$time) {
            $time = time();
        }

        // Only update the field if it is a different time from previous request,
        // so as not to waste database effort.
        if ($time >= $token->lastaccess + self::TOKEN_LASTACCESS_UPDATE_SECS) {
            $DB->set_field('external_tokens', 'lastaccess', $time, array('id' => $token->id));
        }
    }

    /**
     * Allow user to call a service
     *
     * @param stdClass $user a user
     */
    public function add_ws_authorised_user($user) {
        global $DB;
        $user->timecreated = time();
        $DB->insert_record('external_services_users', $user);
    }

    /**
     * Disallow a user to call a service
     *
     * @param stdClass $user a user
     * @param int $serviceid
     */
    public function remove_ws_authorised_user($user, $serviceid) {
        global $DB;
        $DB->delete_records('external_services_users',
                array('externalserviceid' => $serviceid, 'userid' => $user->id));
    }

    /**
     * Update allowed user settings (ip restriction, valid until...)
     *
     * @param stdClass $user
     */
    public function update_ws_authorised_user($user) {
        global $DB;
        $DB->update_record('external_services_users', $user);
    }

    /**
     * Return list of allowed users with their options (ip/timecreated / validuntil...)
     * for a given service
     *
     * @param int $serviceid the service id to search against
     * @return array $users
     */
    public function get_ws_authorised_users($serviceid) {
        global $DB, $CFG;

        $params = array($CFG->siteguest, $serviceid);

        $userfields = \core_user\fields::for_identity(context_system::instance())->with_name()->excluding('id');
        $fieldsql = $userfields->get_sql('u');

        $sql = " SELECT u.id as id, esu.id as serviceuserid {$fieldsql->selects},
                        esu.iprestriction as iprestriction, esu.validuntil as validuntil,
                        esu.timecreated as timecreated
                   FROM {user} u
                   JOIN {external_services_users} esu ON esu.userid = u.id
                        {$fieldsql->joins}
                  WHERE u.id <> ? AND u.deleted = 0 AND u.confirmed = 1
                        AND esu.externalserviceid = ?";

        $users = $DB->get_records_sql($sql, array_merge($fieldsql->params, $params));

        return $users;
    }

    /**
     * Return an authorised user with their options (ip/timecreated / validuntil...)
     *
     * @param int $serviceid the service id to search against
     * @param int $userid the user to search against
     * @return stdClass
     */
    public function get_ws_authorised_user($serviceid, $userid) {
        global $DB, $CFG;
        $params = array($CFG->siteguest, $serviceid, $userid);
        $sql = " SELECT u.id as id, esu.id as serviceuserid, u.email as email, u.firstname as firstname,
                        u.lastname as lastname,
                        esu.iprestriction as iprestriction, esu.validuntil as validuntil,
                        esu.timecreated as timecreated
                   FROM {user} u, {external_services_users} esu
                  WHERE u.id <> ? AND u.deleted = 0 AND u.confirmed = 1
                        AND esu.userid = u.id
                        AND esu.externalserviceid = ?
                        AND u.id = ?";
        $user = $DB->get_record_sql($sql, $params);
        return $user;
    }

    /**
     * Generate all tokens of a specific user
     *
     * @param int $userid user id
     */
    public function generate_user_ws_tokens($userid) {
        global $CFG, $DB;

        // generate a token for non admin if web service are enable and the user has the capability to create a token
        if (!is_siteadmin() && has_capability('moodle/webservice:createtoken', context_system::instance(), $userid) && !empty($CFG->enablewebservices)) {
            // for every service than the user is authorised on, create a token (if it doesn't already exist)

            // get all services which are set to all user (no restricted to specific users)
            $norestrictedservices = $DB->get_records('external_services', array('restrictedusers' => 0));
            $serviceidlist = array();
            foreach ($norestrictedservices as $service) {
                $serviceidlist[] = $service->id;
            }

            // get all services which are set to the current user (the current user is specified in the restricted user list)
            $servicesusers = $DB->get_records('external_services_users', array('userid' => $userid));
            foreach ($servicesusers as $serviceuser) {
                if (!in_array($serviceuser->externalserviceid,$serviceidlist)) {
                     $serviceidlist[] = $serviceuser->externalserviceid;
                }
            }

            // get all services which already have a token set for the current user
            $usertokens = $DB->get_records('external_tokens', array('userid' => $userid, 'tokentype' => EXTERNAL_TOKEN_PERMANENT));
            $tokenizedservice = array();
            foreach ($usertokens as $token) {
                    $tokenizedservice[]  = $token->externalserviceid;
            }

            // create a token for the service which have no token already
            foreach ($serviceidlist as $serviceid) {
                if (!in_array($serviceid, $tokenizedservice)) {
                    // create the token for this service
                    $newtoken = new stdClass();
                    $newtoken->token = md5(uniqid(rand(),1));
                    // check that the user has capability on this service
                    $newtoken->tokentype = EXTERNAL_TOKEN_PERMANENT;
                    $newtoken->userid = $userid;
                    $newtoken->externalserviceid = $serviceid;
                    // TODO MDL-31190 find a way to get the context - UPDATE FOLLOWING LINE
                    $newtoken->contextid = context_system::instance()->id;
                    $newtoken->creatorid = $userid;
                    $newtoken->timecreated = time();
                    $newtoken->name = \core_external\util::generate_token_name();
                    // Generate the private token, it must be transmitted only via https.
                    $newtoken->privatetoken = random_string(64);

                    $DB->insert_record('external_tokens', $newtoken);
                }
            }


        }
    }

    /**
     * Return all tokens of a specific user
     * + the service state (enabled/disabled)
     * + the authorised user mode (restricted/not restricted)
     *
     * @param int $userid user id
     * @return array
     */
    public function get_user_ws_tokens($userid) {
        global $DB;
        //here retrieve token list (including linked users firstname/lastname and linked services name)
        $sql = "SELECT
                    t.id, t.creatorid, t.name as tokenname, u.firstname, u.lastname,
                    s.id as wsid, s.name as servicename, s.enabled, s.restrictedusers, t.validuntil, t.lastaccess
                FROM
                    {external_tokens} t, {user} u, {external_services} s
                WHERE
                    t.userid=? AND t.tokentype = ".EXTERNAL_TOKEN_PERMANENT." AND s.id = t.externalserviceid AND t.userid = u.id";
        $tokens = $DB->get_records_sql($sql, array( $userid));
        return $tokens;
    }

    /**
     * Return a token that has been created by the user (i.e. to created by an admin)
     * If no tokens exist an exception is thrown
     *
     * The returned value is a stdClass:
     * ->id token id
     * ->token
     * ->tokenname
     * ->firstname user firstname
     * ->lastname
     * ->externalserviceid
     * ->name service name
     *
     * @param int $userid user id
     * @param int $tokenid token id
     * @return stdClass
     */
    public function get_created_by_user_ws_token($userid, $tokenid) {
        global $DB;
        $sql = "SELECT
                        t.id, t.token, t.name AS tokenname, u.firstname, u.lastname, t.externalserviceid, s.name
                    FROM
                        {external_tokens} t, {user} u, {external_services} s
                    WHERE
                        t.creatorid=? AND t.id=? AND t.tokentype = "
                . EXTERNAL_TOKEN_PERMANENT
                . " AND s.id = t.externalserviceid AND t.userid = u.id";
        //must be the token creator
        $token = $DB->get_record_sql($sql, array($userid, $tokenid), MUST_EXIST);
        return $token;
    }

    /**
     * Return a token of an arbitrary user by tokenid, including details of the associated user and the service name.
     * If no tokens exist an exception is thrown
     *
     * The returned value is a stdClass:
     * ->id token id
     * ->token
     * ->firstname user firstname
     * ->lastname
     * ->name service name
     *
     * @param int $tokenid token id
     * @return stdClass
     */
    public function get_token_by_id_with_details($tokenid) {
        global $DB;

        $sql = "SELECT t.id, t.token, u.id AS userid, u.firstname, u.lastname, s.name, t.creatorid
                  FROM {external_tokens} t
             LEFT JOIN {user} u ON u.id = t.userid
             LEFT JOIN {external_services} s ON s.id = t.externalserviceid
                 WHERE t.id = ? AND t.tokentype = ?";

        return $DB->get_record_sql($sql, [$tokenid, EXTERNAL_TOKEN_PERMANENT], MUST_EXIST);
    }

    /**
     * Return a database token record for a token id
     *
     * @param int $tokenid token id
     * @return object token
     */
    public function get_token_by_id($tokenid) {
        global $DB;
        return $DB->get_record('external_tokens', array('id' => $tokenid));
    }

    /**
     * Delete a token
     *
     * @param int $tokenid token id
     */
    public function delete_user_ws_token($tokenid) {
        global $DB;
        $DB->delete_records('external_tokens', array('id'=>$tokenid));
    }

    /**
     * Delete all the tokens belonging to a user.
     *
     * @param int $userid the user id whose tokens must be deleted
     */
    public static function delete_user_ws_tokens($userid) {
        global $DB;
        $DB->delete_records('external_tokens', array('userid' => $userid));
    }

    /**
     * Delete a service
     * Also delete function references and authorised user references.
     *
     * @param int $serviceid service id
     */
    public function delete_service($serviceid) {
        global $DB;
        $DB->delete_records('external_services_users', array('externalserviceid' => $serviceid));
        $DB->delete_records('external_services_functions', array('externalserviceid' => $serviceid));
        $DB->delete_records('external_tokens', array('externalserviceid' => $serviceid));
        $DB->delete_records('external_services', array('id' => $serviceid));
    }

    /**
     * Get a full database token record for a given token value
     *
     * @param string $token
     * @throws moodle_exception if there is multiple result
     */
    public function get_user_ws_token($token) {
        global $DB;
        return $DB->get_record('external_tokens', array('token'=>$token), '*', MUST_EXIST);
    }

    /**
     * Get the functions list of a service list (by id)
     *
     * @param array $serviceids service ids
     * @return array of functions
     */
    public function get_external_functions($serviceids) {
        global $DB;
        if (!empty($serviceids)) {
            list($serviceids, $params) = $DB->get_in_or_equal($serviceids);
            $sql = "SELECT f.*
                      FROM {external_functions} f
                     WHERE f.name IN (SELECT sf.functionname
                                        FROM {external_services_functions} sf
                                       WHERE sf.externalserviceid $serviceids)
                     ORDER BY f.name ASC";
            $functions = $DB->get_records_sql($sql, $params);
        } else {
            $functions = array();
        }
        return $functions;
    }

    /**
     * Get the functions of a service list (by shortname). It can return only enabled functions if required.
     *
     * @param array $serviceshortnames service shortnames
     * @param bool $enabledonly if true then only return functions for services that have been enabled
     * @return array functions
     */
    public function get_external_functions_by_enabled_services($serviceshortnames, $enabledonly = true) {
        global $DB;
        if (!empty($serviceshortnames)) {
            $enabledonlysql = $enabledonly?' AND s.enabled = 1 ':'';
            list($serviceshortnames, $params) = $DB->get_in_or_equal($serviceshortnames);
            $sql = "SELECT f.*
                      FROM {external_functions} f
                     WHERE f.name IN (SELECT sf.functionname
                                        FROM {external_services_functions} sf, {external_services} s
                                       WHERE s.shortname $serviceshortnames
                                             AND sf.externalserviceid = s.id
                                             " . $enabledonlysql . ")";
            $functions = $DB->get_records_sql($sql, $params);
        } else {
            $functions = array();
        }
        return $functions;
    }

    /**
     * Get functions not included in a service
     *
     * @param int $serviceid service id
     * @return array functions
     */
    public function get_not_associated_external_functions($serviceid) {
        global $DB;
        $select = "name NOT IN (SELECT s.functionname
                                  FROM {external_services_functions} s
                                 WHERE s.externalserviceid = :sid
                               )";

        $functions = $DB->get_records_select('external_functions',
                        $select, array('sid' => $serviceid), 'name');

        return $functions;
    }

    /**
     * Get list of required capabilities of a service, sorted by functions
     * Example of returned value:
     *  Array
     *  (
     *    [core_group_create_groups] => Array
     *    (
     *       [0] => moodle/course:managegroups
     *    )
     *
     *    [core_enrol_get_enrolled_users] => Array
     *    (
     *       [0] => moodle/user:viewdetails
     *       [1] => moodle/user:viewhiddendetails
     *       [2] => moodle/course:useremail
     *       [3] => moodle/user:update
     *       [4] => moodle/site:accessallgroups
     *    )
     *  )
     * @param int $serviceid service id
     * @return array
     */
    public function get_service_required_capabilities($serviceid) {
        $functions = $this->get_external_functions(array($serviceid));
        $requiredusercaps = array();
        foreach ($functions as $function) {
            $functioncaps = explode(',', $function->capabilities);
            if (!empty($functioncaps) and !empty($functioncaps[0])) {
                foreach ($functioncaps as $functioncap) {
                    $requiredusercaps[$function->name][] = trim($functioncap);
                }
            }
        }
        return $requiredusercaps;
    }

    /**
     * Get user capabilities (with context)
     * Only useful for documentation purpose
     * WARNING: do not use this "broken" function. It was created in the goal to display some capabilities
     * required by users. In theory we should not need to display this kind of information
     * as the front end does not display it itself. In pratice,
     * admins would like the info, for more info you can follow: MDL-29962
     *
     * @deprecated since Moodle 3.11 in MDL-67748 without a replacement.
     * @todo MDL-70187 Please delete this method completely in Moodle 4.3, thank you.
     * @param int $userid user id
     * @return array
     */
    public function get_user_capabilities($userid) {
        global $DB;

        debugging('webservice::get_user_capabilities() has been deprecated.', DEBUG_DEVELOPER);

        //retrieve the user capabilities
        $sql = "SELECT DISTINCT rc.id, rc.capability FROM {role_capabilities} rc, {role_assignments} ra
            WHERE rc.roleid=ra.roleid AND ra.userid= ? AND rc.permission = ?";
        $dbusercaps = $DB->get_records_sql($sql, array($userid, CAP_ALLOW));
        $usercaps = array();
        foreach ($dbusercaps as $usercap) {
            $usercaps[$usercap->capability] = true;
        }
        return $usercaps;
    }

    /**
     * Get missing user capabilities for the given service's functions.
     *
     * Every external function can declare some required capabilities to allow for easier setup of the web services.
     * However, that is supposed to be used for informational admin report only. There is no automatic evaluation of
     * the declared capabilities and the context of the capability evaluation is ignored. Also, actual capability
     * evaluation is much more complex as it allows for overrides etc.
     *
     * Returned are capabilities that the given users do not seem to have assigned anywhere at the site and that should
     * be checked by the admin.
     *
     * Do not use this method for anything else, particularly not for any security related checks. See MDL-29962 for the
     * background of why we have this - there are arguments for dropping this feature completely.
     *
     * @param array $users List of users to check, consisting of objects, arrays or integer ids.
     * @param int $serviceid The id of the external service to check.
     * @return array List of missing capabilities: (int)userid => array of (string)capabilitynames
     */
    public function get_missing_capabilities_by_users(array $users, int $serviceid): array {
        global $DB;

        // The following are default capabilities for all authenticated users and we will assume them granted.
        $commoncaps = get_default_capabilities('user');

        // Get the list of additional capabilities required by the service.
        $servicecaps = [];
        foreach ($this->get_service_required_capabilities($serviceid) as $service => $caps) {
            foreach ($caps as $cap) {
                if (empty($commoncaps[$cap])) {
                    $servicecaps[$cap] = true;
                }
            }
        }

        // Bail out early if there's nothing to process.
        if (empty($users) || empty($servicecaps)) {
            return [];
        }

        // Prepare a list of user ids we want to check.
        $userids = [];
        foreach ($users as $user) {
            if (is_object($user) && isset($user->id)) {
                $userids[$user->id] = true;
            } else if (is_array($user) && isset($user['id'])) {
                $userids[$user['id']] = true;
            } else {
                throw new coding_exception('Unexpected format of users list in webservice::get_missing_capabilities_by_users().');
            }
        }

        // Prepare a matrix of missing capabilities x users - consider them all missing by default.
        foreach (array_keys($userids) as $userid) {
            foreach (array_keys($servicecaps) as $capname) {
                $matrix[$userid][$capname] = true;
            }
        }

        list($capsql, $capparams) = $DB->get_in_or_equal(array_keys($servicecaps), SQL_PARAMS_NAMED, 'paramcap');
        list($usersql, $userparams) = $DB->get_in_or_equal(array_keys($userids), SQL_PARAMS_NAMED, 'paramuser');

        $sql = "SELECT c.name AS capability, u.id AS userid
                  FROM {capabilities} c
                  JOIN {role_capabilities} rc ON c.name = rc.capability
                  JOIN {role_assignments} ra ON ra.roleid = rc.roleid
                  JOIN {user} u ON ra.userid = u.id
                 WHERE rc.permission = :capallow
                   AND c.name {$capsql}
                   AND u.id {$usersql}";

        $params = $capparams + $userparams + [
            'capallow' => CAP_ALLOW,
        ];

        $rs = $DB->get_recordset_sql($sql, $params);

        foreach ($rs as $record) {
            // If there was a potential role assignment found that might grant the user the given capability,
            // remove it from the matrix. Again, we ignore all the contexts, prohibits, prevents and other details
            // of the permissions evaluations. See the function docblock for details.
            unset($matrix[$record->userid][$record->capability]);
        }

        $rs->close();

        foreach ($matrix as $userid => $caps) {
            $matrix[$userid] = array_keys($caps);
            if (empty($matrix[$userid])) {
                unset($matrix[$userid]);
            }
        }

        return $matrix;
    }

    /**
     * Get an external service for a given service id
     *
     * @param int $serviceid service id
     * @param int $strictness IGNORE_MISSING, MUST_EXIST...
     * @return stdClass external service
     */
    public function get_external_service_by_id($serviceid, $strictness=IGNORE_MISSING) {
        global $DB;
        $service = $DB->get_record('external_services',
                        array('id' => $serviceid), '*', $strictness);
        return $service;
    }

    /**
     * Get an external service for a given shortname
     *
     * @param string $shortname service shortname
     * @param int $strictness IGNORE_MISSING, MUST_EXIST...
     * @return stdClass external service
     */
    public function get_external_service_by_shortname($shortname, $strictness=IGNORE_MISSING) {
        global $DB;
        $service = $DB->get_record('external_services',
                        array('shortname' => $shortname), '*', $strictness);
        return $service;
    }

    /**
     * Get an external function for a given function id
     *
     * @param int $functionid function id
     * @param int $strictness IGNORE_MISSING, MUST_EXIST...
     * @return stdClass external function
     */
    public function get_external_function_by_id($functionid, $strictness=IGNORE_MISSING) {
        global $DB;
        $function = $DB->get_record('external_functions',
                            array('id' => $functionid), '*', $strictness);
        return $function;
    }

    /**
     * Add a function to a service
     *
     * @param string $functionname function name
     * @param int $serviceid service id
     */
    public function add_external_function_to_service($functionname, $serviceid) {
        global $DB;
        $addedfunction = new stdClass();
        $addedfunction->externalserviceid = $serviceid;
        $addedfunction->functionname = $functionname;
        $DB->insert_record('external_services_functions', $addedfunction);
    }

    /**
     * Add a service
     * It generates the timecreated field automatically.
     *
     * @param stdClass $service
     * @return serviceid integer
     */
    public function add_external_service($service) {
        global $DB;
        $service->timecreated = time();
        $serviceid = $DB->insert_record('external_services', $service);
        return $serviceid;
    }

    /**
     * Update a service
     * It modifies the timemodified automatically.
     *
     * @param stdClass $service
     */
    public function update_external_service($service) {
        global $DB;
        $service->timemodified = time();
        $DB->update_record('external_services', $service);
    }

    /**
     * Test whether an external function is already linked to a service
     *
     * @param string $functionname function name
     * @param int $serviceid service id
     * @return bool true if a matching function exists for the service, else false.
     * @throws dml_exception if error
     */
    public function service_function_exists($functionname, $serviceid) {
        global $DB;
        return $DB->record_exists('external_services_functions',
                            array('externalserviceid' => $serviceid,
                                'functionname' => $functionname));
    }

    /**
     * Remove a function from a service
     *
     * @param string $functionname function name
     * @param int $serviceid service id
     */
    public function remove_external_function_from_service($functionname, $serviceid) {
        global $DB;
        $DB->delete_records('external_services_functions',
                    array('externalserviceid' => $serviceid, 'functionname' => $functionname));

    }

    /**
     * Return a list with all the valid user tokens for the given user, it only excludes expired tokens.
     *
     * @param  string $userid user id to retrieve tokens from
     * @return array array of token entries
     * @since Moodle 3.2
     */
    public static function get_active_tokens($userid) {
        global $DB;

        $sql = 'SELECT t.id, t.creatorid, t.externalserviceid, t.name AS tokenname, t.validuntil, s.name AS servicename
                  FROM {external_tokens} t
                  JOIN {external_services} s ON t.externalserviceid = s.id
                 WHERE t.userid = :userid AND (COALESCE(t.validuntil, 0) = 0 OR t.validuntil > :now)';
        $params = array('userid' => $userid, 'now' => time());
        return $DB->get_records_sql($sql, $params);
    }
}

/**
 * Exception indicating access control problem in web service call
 * This exception should return general errors about web service setup.
 * Errors related to the user like wrong username/password should not use it,
 * you should not use this exception if you want to let the client implement
 * some code logic against an access error.
 *
 * @package    core_webservice
 * @copyright  2009 Petr Skodak
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class webservice_access_exception extends moodle_exception {

    /**
     * Constructor
     *
     * @param string $debuginfo the debug info
     */
    function __construct($debuginfo) {
        parent::__construct('accessexception', 'webservice', '', null, $debuginfo);
    }
}

/**
 * Check if a protocol is enabled
 *
 * @param string $protocol name of WS protocol ('rest', 'soap', ...)
 * @return bool true if the protocol is enabled
 */
function webservice_protocol_is_enabled($protocol) {
    global $CFG;

    if (empty($CFG->enablewebservices) || empty($CFG->webserviceprotocols)) {
        return false;
    }

    $active = explode(',', $CFG->webserviceprotocols);

    return(in_array($protocol, $active));
}

/**
 * Mandatory interface for all test client classes.
 *
 * @package    core_webservice
 * @copyright  2009 Petr Skodak
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
interface webservice_test_client_interface {

    /**
     * Execute test client WS request
     *
     * @param string $serverurl server url (including the token param)
     * @param string $function web service function name
     * @param array $params parameters of the web service function
     * @return mixed
     */
    public function simpletest($serverurl, $function, $params);
}

/**
 * Mandatory interface for all web service protocol classes
 *
 * @package    core_webservice
 * @copyright  2009 Petr Skodak
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
interface webservice_server_interface {

    /**
     * Process request from client.
     */
    public function run();
}

/**
 * Abstract web service base class.
 *
 * @package    core_webservice
 * @copyright  2009 Petr Skodak
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
abstract class webservice_server implements webservice_server_interface {

    /** @var string Name of the web server plugin */
    protected $wsname = null;

    /** @var string Name of local user */
    protected $username = null;

    /** @var string Password of the local user */
    protected $password = null;

    /** @var int The local user */
    protected $userid = null;

    /** @var integer Authentication method one of WEBSERVICE_AUTHMETHOD_* */
    protected $authmethod;

    /** @var string Authentication token*/
    protected $token = null;

    /** @var stdClass Restricted context */
    protected $restricted_context;

    /** @var int Restrict call to one service id*/
    protected $restricted_serviceid = null;

    /**
     * Constructor
     *
     * @param integer $authmethod authentication method one of WEBSERVICE_AUTHMETHOD_*
     */
    public function __construct($authmethod) {
        $this->authmethod = $authmethod;
    }


    /**
     * Authenticate user using username+password or token.
     * This function sets up $USER global.
     * It is safe to use has_capability() after this.
     * This method also verifies user is allowed to use this
     * server.
     */
    protected function authenticate_user() {
        global $CFG, $DB;

        if (!NO_MOODLE_COOKIES) {
            throw new coding_exception('Cookies must be disabled in WS servers!');
        }

        $loginfaileddefaultparams = array(
            'other' => array(
                'method' => $this->authmethod,
                'reason' => null
            )
        );

        if ($this->authmethod == WEBSERVICE_AUTHMETHOD_USERNAME) {

            //we check that authentication plugin is enabled
            //it is only required by simple authentication
            if (!is_enabled_auth('webservice')) {
                throw new webservice_access_exception('The web service authentication plugin is disabled.');
            }

            if (!$auth = get_auth_plugin('webservice')) {
                throw new webservice_access_exception('The web service authentication plugin is missing.');
            }

            $this->restricted_context = context_system::instance();

            if (!$this->username) {
                throw new moodle_exception('missingusername', 'webservice');
            }

            if (!$this->password) {
                throw new moodle_exception('missingpassword', 'webservice');
            }

            if (!$auth->user_login_webservice($this->username, $this->password)) {

                // Log failed login attempts.
                $params = $loginfaileddefaultparams;
                $params['other']['reason'] = 'password';
                $params['other']['username'] = $this->username;
                $event = \core\event\webservice_login_failed::create($params);
                $event->trigger();

                throw new moodle_exception('wrongusernamepassword', 'webservice');
            }

            $user = $DB->get_record('user', array('username'=>$this->username, 'mnethostid'=>$CFG->mnet_localhost_id), '*', MUST_EXIST);

        } else if ($this->authmethod == WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN){
            $user = $this->authenticate_by_token(EXTERNAL_TOKEN_PERMANENT);
        } else {
            $user = $this->authenticate_by_token(EXTERNAL_TOKEN_EMBEDDED);
        }

        // Cannot authenticate unless maintenance access is granted.
        $hasmaintenanceaccess = has_capability('moodle/site:maintenanceaccess', context_system::instance(), $user);
        if (!empty($CFG->maintenance_enabled) and !$hasmaintenanceaccess) {
            throw new moodle_exception('sitemaintenance', 'admin');
        }

        //only confirmed user should be able to call web service
        if (!empty($user->deleted)) {
            $params = $loginfaileddefaultparams;
            $params['other']['reason'] = 'user_deleted';
            $params['other']['username'] = $user->username;
            $event = \core\event\webservice_login_failed::create($params);
            $event->trigger();
            throw new moodle_exception('wsaccessuserdeleted', 'webservice', '', $user->username);
        }

        //only confirmed user should be able to call web service
        if (empty($user->confirmed)) {
            $params = $loginfaileddefaultparams;
            $params['other']['reason'] = 'user_unconfirmed';
            $params['other']['username'] = $user->username;
            $event = \core\event\webservice_login_failed::create($params);
            $event->trigger();
            throw new moodle_exception('wsaccessuserunconfirmed', 'webservice', '', $user->username);
        }

        //check the user is suspended
        if (!empty($user->suspended)) {
            $params = $loginfaileddefaultparams;
            $params['other']['reason'] = 'user_unconfirmed';
            $params['other']['username'] = $user->username;
            $event = \core\event\webservice_login_failed::create($params);
            $event->trigger();
            throw new moodle_exception('wsaccessusersuspended', 'webservice', '', $user->username);
        }

        //retrieve the authentication plugin if no previously done
        if (empty($auth)) {
          $auth  = get_auth_plugin($user->auth);
        }

        // check if credentials have expired
        if (!empty($auth->config->expiration) and $auth->config->expiration == 1) {
            $days2expire = $auth->password_expire($user->username);
            if (intval($days2expire) < 0 ) {
                $params = $loginfaileddefaultparams;
                $params['other']['reason'] = 'password_expired';
                $params['other']['username'] = $user->username;
                $event = \core\event\webservice_login_failed::create($params);
                $event->trigger();
                throw new moodle_exception('wsaccessuserexpired', 'webservice', '', $user->username);
            }
        }

        //check if the auth method is nologin (in this case refuse connection)
        if ($user->auth=='nologin') {
            $params = $loginfaileddefaultparams;
            $params['other']['reason'] = 'login';
            $params['other']['username'] = $user->username;
            $event = \core\event\webservice_login_failed::create($params);
            $event->trigger();
            throw new moodle_exception('wsaccessusernologin', 'webservice', '', $user->username);
        }

        // now fake user login, the session is completely empty too
        enrol_check_plugins($user, false);
        \core\session\manager::set_user($user);
        set_login_session_preferences();
        $this->userid = $user->id;

        if ($this->authmethod != WEBSERVICE_AUTHMETHOD_SESSION_TOKEN && !has_capability("webservice/$this->wsname:use", $this->restricted_context)) {
            throw new webservice_access_exception("You are not allowed to use the {$this->wsname} protocol " .
                "(missing capability: webservice/{$this->wsname}:use)");
        }

        external_api::set_context_restriction($this->restricted_context);
    }

    /**
     * User authentication by token
     *
     * @param string $tokentype token type (EXTERNAL_TOKEN_EMBEDDED or EXTERNAL_TOKEN_PERMANENT)
     * @return stdClass the authenticated user
     * @throws webservice_access_exception
     */
    protected function authenticate_by_token($tokentype){
        global $DB;

        $loginfaileddefaultparams = array(
            'other' => array(
                'method' => $this->authmethod,
                'reason' => null
            )
        );

        if (!$token = $DB->get_record('external_tokens', array('token'=>$this->token, 'tokentype'=>$tokentype))) {
            // Log failed login attempts.
            $params = $loginfaileddefaultparams;
            $params['other']['reason'] = 'invalid_token';
            $event = \core\event\webservice_login_failed::create($params);
            $event->trigger();
            throw new moodle_exception('invalidtoken', 'webservice');
        }

        if ($token->validuntil and $token->validuntil < time()) {
            $DB->delete_records('external_tokens', array('token'=>$this->token, 'tokentype'=>$tokentype));
            throw new webservice_access_exception('Invalid token - token expired - check validuntil time for the token');
        }

        if ($token->sid){//assumes that if sid is set then there must be a valid associated session no matter the token type
            if (!\core\session\manager::session_exists($token->sid)){
                $DB->delete_records('external_tokens', array('sid'=>$token->sid));
                throw new webservice_access_exception('Invalid session based token - session not found or expired');
            }
        }

        if ($token->iprestriction and !address_in_subnet(getremoteaddr(), $token->iprestriction)) {
            $params = $loginfaileddefaultparams;
            $params['other']['reason'] = 'ip_restricted';
            $params['other']['tokenid'] = $token->id;
            $event = \core\event\webservice_login_failed::create($params);
            $event->add_record_snapshot('external_tokens', $token);
            $event->trigger();
            throw new webservice_access_exception('Invalid service - IP:' . getremoteaddr()
                    . ' is not supported - check this allowed user');
        }

        $this->restricted_context = context::instance_by_id($token->contextid);
        $this->restricted_serviceid = $token->externalserviceid;

        $user = $DB->get_record('user', array('id'=>$token->userid), '*', MUST_EXIST);

        // log token access
        webservice::update_token_lastaccess($token);

        return $user;

    }

    /**
     * Intercept some moodlewssettingXXX $_GET and $_POST parameter
     * that are related to the web service call and are not the function parameters
     */
    protected function set_web_service_call_settings() {
        global $CFG;

        // Default web service settings.
        // Must be the same XXX key name as the external_settings::set_XXX function.
        // Must be the same XXX ws parameter name as 'moodlewssettingXXX'.
        $externalsettings = array(
            'raw' => array('default' => false, 'type' => PARAM_BOOL),
            'fileurl' => array('default' => true, 'type' => PARAM_BOOL),
            'filter' => array('default' => false, 'type' => PARAM_BOOL),
            'lang' => array('default' => '', 'type' => PARAM_LANG),
            'timezone' => array('default' => '', 'type' => PARAM_TIMEZONE),
        );

        // Load the external settings with the web service settings.
        $settings = external_settings::get_instance();
        foreach ($externalsettings as $name => $settingdata) {

            $wsparamname = 'moodlewssetting' . $name;

            // Retrieve and remove the setting parameter from the request.
            $value = optional_param($wsparamname, $settingdata['default'], $settingdata['type']);
            unset($_GET[$wsparamname]);
            unset($_POST[$wsparamname]);

            $functioname = 'set_' . $name;
            $settings->$functioname($value);
        }

    }
}

/**
 * Web Service server base class.
 *
 * This class handles both simple and token authentication.
 *
 * @package    core_webservice
 * @copyright  2009 Petr Skodak
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
abstract class webservice_base_server extends webservice_server {

    /** @var array The function parameters - the real values submitted in the request */
    protected $parameters = null;

    /** @var string The name of the function that is executed */
    protected $functionname = null;

    /** @var stdClass Full function description */
    protected $function = null;

    /** @var mixed Function return value */
    protected $returns = null;

    /** @var array List of methods and their information provided by the web service. */
    protected $servicemethods;

    /** @var  array List of struct classes generated for the web service methods. */
    protected $servicestructs;

    /** @var string service class name. */
    protected $serviceclass;

    /**
     * This method parses the request input, it needs to get:
     *  1/ user authentication - username+password or token
     *  2/ function name
     *  3/ function parameters
     */
    abstract protected function parse_request();

    /**
     * Send the result of function call to the WS client.
     */
    abstract protected function send_response();

    /**
     * Send the error information to the WS client.
     *
     * @param exception $ex
     */
    abstract protected function send_error($ex=null);

    /**
     * Process request from client.
     *
     * @uses die
     */
    public function run() {
        global $CFG, $USER, $SESSION;

        // we will probably need a lot of memory in some functions
        raise_memory_limit(MEMORY_EXTRA);

        // set some longer timeout, this script is not sending any output,
        // this means we need to manually extend the timeout operations
        // that need longer time to finish
        external_api::set_timeout();

        // set up exception handler first, we want to sent them back in correct format that
        // the other system understands
        // we do not need to call the original default handler because this ws handler does everything
        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();

        // find all needed function info and make sure user may actually execute the function
        $this->load_function_info();

        // Log the web service request.
        $params = array(
            'other' => array(
                'function' => $this->functionname
            )
        );
        $event = \core\event\webservice_function_called::create($params);
        $event->trigger();

        // Do additional setup stuff.
        $settings = external_settings::get_instance();
        $sessionlang = $settings->get_lang();
        if (!empty($sessionlang)) {
            $SESSION->lang = $sessionlang;
        }

        setup_lang_from_browser();

        if (empty($CFG->lang)) {
            if (empty($SESSION->lang)) {
                $CFG->lang = 'en';
            } else {
                $CFG->lang = $SESSION->lang;
            }
        }

        // Change timezone only in sites where it isn't forced.
        $newtimezone = $settings->get_timezone();
        if (!empty($newtimezone) && (!isset($CFG->forcetimezone) || $CFG->forcetimezone == 99)) {
            $USER->timezone = $newtimezone;
        }

        // finally, execute the function - any errors are catched by the default exception handler
        $this->execute();

        // send the results back in correct format
        $this->send_response();

        // session cleanup
        $this->session_cleanup();

        die;
    }

    /**
     * Specialised exception handler, we can not use the standard one because
     * it can not just print html to output.
     *
     * @param exception $ex
     * $uses exit
     */
    public function exception_handler($ex) {
        // detect active db transactions, rollback and log as error
        abort_all_db_transactions();

        // some hacks might need a cleanup hook
        $this->session_cleanup($ex);

        // now let the plugin send the exception to client
        $this->send_error($ex);

        // not much else we can do now, add some logging later
        exit(1);
    }

    /**
     * Future hook needed for emulated sessions.
     *
     * @param exception $exception null means normal termination, $exception received when WS call failed
     */
    protected function session_cleanup($exception=null) {
        if ($this->authmethod == WEBSERVICE_AUTHMETHOD_USERNAME) {
            // nothing needs to be done, there is no persistent session
        } else {
            // close emulated session if used
        }
    }

    /**
     * Fetches the function description from database,
     * verifies user is allowed to use this function and
     * loads all paremeters and return descriptions.
     */
    protected function load_function_info() {
        global $DB, $USER, $CFG;

        if (empty($this->functionname)) {
            throw new invalid_parameter_exception('Missing function name');
        }

        // function must exist
        $function = external_api::external_function_info($this->functionname);

        if ($this->restricted_serviceid) {
            $params = array('sid1'=>$this->restricted_serviceid, 'sid2'=>$this->restricted_serviceid);
            $wscond1 = 'AND s.id = :sid1';
            $wscond2 = 'AND s.id = :sid2';
        } else {
            $params = array();
            $wscond1 = '';
            $wscond2 = '';
        }

        // now let's verify access control

        // now make sure the function is listed in at least one service user is allowed to use
        // allow access only if:
        //  1/ entry in the external_services_users table if required
        //  2/ validuntil not reached
        //  3/ has capability if specified in service desc
        //  4/ iprestriction

        $sql = "SELECT s.*, NULL AS iprestriction
                  FROM {external_services} s
                  JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 0 AND sf.functionname = :name1)
                 WHERE s.enabled = 1 $wscond1

                 UNION

                SELECT s.*, su.iprestriction
                  FROM {external_services} s
                  JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 1 AND sf.functionname = :name2)
                  JOIN {external_services_users} su ON (su.externalserviceid = s.id AND su.userid = :userid)
                 WHERE s.enabled = 1 AND (su.validuntil IS NULL OR su.validuntil < :now) $wscond2";
        $params = array_merge($params, array('userid'=>$USER->id, 'name1'=>$function->name, 'name2'=>$function->name, 'now'=>time()));

        $rs = $DB->get_recordset_sql($sql, $params);
        // now make sure user may access at least one service
        $remoteaddr = getremoteaddr();
        $allowed = false;
        foreach ($rs as $service) {
            if ($service->requiredcapability and !has_capability($service->requiredcapability, $this->restricted_context)) {
                continue; // cap required, sorry
            }
            if ($service->iprestriction and !address_in_subnet($remoteaddr, $service->iprestriction)) {
                continue; // wrong request source ip, sorry
            }
            $allowed = true;
            break; // one service is enough, no need to continue
        }
        $rs->close();
        if (!$allowed) {
            throw new webservice_access_exception(
                    'Access to the function '.$this->functionname.'() is not allowed.
                     There could be multiple reasons for this:
                     1. The service linked to the user token does not contain the function.
                     2. The service is user-restricted and the user is not listed.
                     3. The service is IP-restricted and the user IP is not listed.
                     4. The service is time-restricted and the time has expired.
                     5. The token is time-restricted and the time has expired.
                     6. The service requires a specific capability which the user does not have.
                     7. The function is called with username/password (no user token is sent)
                     and none of the services has the function to allow the user.
                     These settings can be found in Administration > Site administration
                     > Server > Web services > External services and Manage tokens.');
        }

        // we have all we need now
        $this->function = $function;
    }

    /**
     * Execute previously loaded function using parameters parsed from the request data.
     */
    protected function execute() {
        // validate params, this also sorts the params properly, we need the correct order in the next part
        $params = call_user_func(array($this->function->classname, 'validate_parameters'), $this->function->parameters_desc, $this->parameters);
        $params = array_values($params);

        // Allow any Moodle plugin a chance to override this call. This is a convenient spot to
        // make arbitrary behaviour customisations, for example to affect the mobile app behaviour.
        // The overriding plugin could call the 'real' function first and then modify the results,
        // or it could do a completely separate thing.
        $callbacks = get_plugins_with_function('override_webservice_execution');
        foreach ($callbacks as $plugintype => $plugins) {
            foreach ($plugins as $plugin => $callback) {
                $result = $callback($this->function, $params);
                if ($result !== false) {
                    // If the callback returns anything other than false, we assume it replaces the
                    // real function.
                    $this->returns = $result;
                    return;
                }
            }
        }

        // execute - yay!
        $this->returns = call_user_func_array(array($this->function->classname, $this->function->methodname), $params);
    }

    /**
     * Load the virtual class needed for the web service.
     *
     * Initialises the virtual class that contains the web service functions that the user is allowed to use.
     * The web service function will be available if the user:
     * - is validly registered in the external_services_users table.
     * - has the required capability.
     * - meets the IP restriction requirement.
     * This virtual class can be used by web service protocols such as SOAP, especially when generating WSDL.
     */
    protected function init_service_class() {
        global $USER, $DB;

        // Initialise service methods and struct classes.
        $this->servicemethods = array();
        $this->servicestructs = array();

        $params = array();
        $wscond1 = '';
        $wscond2 = '';
        if ($this->restricted_serviceid) {
            $params = array('sid1' => $this->restricted_serviceid, 'sid2' => $this->restricted_serviceid);
            $wscond1 = 'AND s.id = :sid1';
            $wscond2 = 'AND s.id = :sid2';
        }

        $sql = "SELECT s.*, NULL AS iprestriction
                  FROM {external_services} s
                  JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 0)
                 WHERE s.enabled = 1 $wscond1

                 UNION

                SELECT s.*, su.iprestriction
                  FROM {external_services} s
                  JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 1)
                  JOIN {external_services_users} su ON (su.externalserviceid = s.id AND su.userid = :userid)
                 WHERE s.enabled = 1 AND (su.validuntil IS NULL OR su.validuntil < :now) $wscond2";
        $params = array_merge($params, array('userid' => $USER->id, 'now' => time()));

        $serviceids = array();
        $remoteaddr = getremoteaddr();

        // Query list of external services for the user.
        $rs = $DB->get_recordset_sql($sql, $params);

        // Check which service ID to include.
        foreach ($rs as $service) {
            if (isset($serviceids[$service->id])) {
                continue; // Service already added.
            }
            if ($service->requiredcapability and !has_capability($service->requiredcapability, $this->restricted_context)) {
                continue; // Cap required, sorry.
            }
            if ($service->iprestriction and !address_in_subnet($remoteaddr, $service->iprestriction)) {
                continue; // Wrong request source ip, sorry.
            }
            $serviceids[$service->id] = $service->id;
        }
        $rs->close();

        // Generate the virtual class name.
        $classname = $this->get_unique_classname('webservices_virtual_class');
        $this->serviceclass = $classname;

        // Get the list of all available external functions.
        $wsmanager = new webservice();
        $functions = $wsmanager->get_external_functions($serviceids);

        // Generate code for the virtual methods for this web service.
        $methods = '';
        foreach ($functions as $function) {
            $methods .= $this->get_virtual_method_code($function);
        }

        $code = <<id in context {$this->restricted_context->id}.
 */
class $classname {
$methods
}
EOD;
        // Load the virtual class definition into memory.
        eval($code);
    }

    /**
     * Generates a struct class.
     *
     * @param external_single_structure $structdesc The basis of the struct class to be generated.
     * @return string The class name of the generated struct class.
     */
    protected function generate_simple_struct_class(external_single_structure $structdesc) {
        global $USER;

        $propeties = array();
        $fields = array();
        foreach ($structdesc->keys as $name => $fieldsdesc) {
            $type = $this->get_phpdoc_type($fieldsdesc);
            $propertytype = array('type' => $type);
            if (empty($fieldsdesc->allownull) || $fieldsdesc->allownull == NULL_ALLOWED) {
                $propertytype['nillable'] = true;
            }
            $propeties[$name] = $propertytype;
            $fields[] = '    /** @var ' . $type . ' $' . $name . '*/';
            $fields[] = '    public $' . $name .';';
        }
        $fieldsstr = implode("\n", $fields);

        // We do this after the call to get_phpdoc_type() to avoid duplicate class creation.
        $classname = $this->get_unique_classname('webservices_struct_class');
        $code = <<id in context {$this->restricted_context->id}.
 */
class $classname {
$fieldsstr
}
EOD;
        // Load into memory.
        eval($code);

        // Prepare struct info.
        $structinfo = new stdClass();
        $structinfo->classname = $classname;
        $structinfo->properties = $propeties;
        // Add the struct info the the list of service struct classes.
        $this->servicestructs[] = $structinfo;

        return $classname;
    }

    /**
     * Returns a virtual method code for a web service function.
     *
     * @param stdClass $function a record from external_function
     * @return string The PHP code of the virtual method.
     * @throws coding_exception
     * @throws moodle_exception
     */
    protected function get_virtual_method_code($function) {
        $function = external_api::external_function_info($function);

        // Parameters and their defaults for the method signature.
        $paramanddefaults = array();
        // Parameters for external lib call.
        $params = array();
        $paramdesc = array();
        // The method's input parameters and their respective types.
        $inputparams = array();
        // The method's output parameters and their respective types.
        $outputparams = array();

        foreach ($function->parameters_desc->keys as $name => $keydesc) {
            $param = '$' . $name;
            $paramanddefault = $param;
            if ($keydesc->required == VALUE_OPTIONAL) {
                // It does not make sense to declare a parameter VALUE_OPTIONAL. VALUE_OPTIONAL is used only for array/object key.
                throw new moodle_exception('erroroptionalparamarray', 'webservice', '', $name);
            } else if ($keydesc->required == VALUE_DEFAULT) {
                // Need to generate the default, if there is any.
                if ($keydesc instanceof external_value) {
                    if ($keydesc->default === null) {
                        $paramanddefault .= ' = null';
                    } else {
                        switch ($keydesc->type) {
                            case PARAM_BOOL:
                                $default = (int)$keydesc->default;
                                break;
                            case PARAM_INT:
                                $default = $keydesc->default;
                                break;
                            case PARAM_FLOAT;
                                $default = $keydesc->default;
                                break;
                            default:
                                $default = "'$keydesc->default'";
                        }
                        $paramanddefault .= " = $default";
                    }
                } else {
                    // Accept empty array as default.
                    if (isset($keydesc->default) && is_array($keydesc->default) && empty($keydesc->default)) {
                        $paramanddefault .= ' = array()';
                    } else {
                        // For the moment we do not support default for other structure types.
                        throw new moodle_exception('errornotemptydefaultparamarray', 'webservice', '', $name);
                    }
                }
            }

            $params[] = $param;
            $paramanddefaults[] = $paramanddefault;
            $type = $this->get_phpdoc_type($keydesc);
            $inputparams[$name]['type'] = $type;

            $paramdesc[] = '* @param ' . $type . ' $' . $name . ' ' . $keydesc->desc;
        }
        $paramanddefaults = implode(', ', $paramanddefaults);
        $paramdescstr = implode("\n ", $paramdesc);

        $serviceclassmethodbody = $this->service_class_method_body($function, $params);

        if (empty($function->returns_desc)) {
            $return = '* @return void';
        } else {
            $type = $this->get_phpdoc_type($function->returns_desc);
            $outputparams['return']['type'] = $type;
            $return = '* @return ' . $type . ' ' . $function->returns_desc->desc;
        }

        // Now create the virtual method that calls the ext implementation.
        $code = <<description.
 *
 $paramdescstr
 $return
 */
public function $function->name($paramanddefaults) {
$serviceclassmethodbody
}
EOD;

        // Prepare the method information.
        $methodinfo = new stdClass();
        $methodinfo->name = $function->name;
        $methodinfo->inputparams = $inputparams;
        $methodinfo->outputparams = $outputparams;
        $methodinfo->description = $function->description;
        // Add the method information into the list of service methods.
        $this->servicemethods[] = $methodinfo;

        return $code;
    }

    /**
     * Get the phpdoc type for an external_description object.
     * external_value => int, double or string
     * external_single_structure => object|struct, on-fly generated stdClass name.
     * external_multiple_structure => array
     *
     * @param mixed $keydesc The type description.
     * @return string The PHP doc type of the external_description object.
     */
    protected function get_phpdoc_type($keydesc) {
        $type = null;
        if ($keydesc instanceof external_value) {
            switch ($keydesc->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';
            }
        } else if ($keydesc instanceof external_single_structure) {
            $type = $this->generate_simple_struct_class($keydesc);
        } else if ($keydesc instanceof external_multiple_structure) {
            $type = 'array';
        }

        return $type;
    }

    /**
     * Get a unique integer-suffixed classname for dynamic code creation.
     *
     * @param string $prefix The class name prefix to use.
     * @return string The unused class name
     */
    protected function get_unique_classname(string $prefix): string {
        $suffix = 0;
        do {
            $classname = sprintf(
                "%s_%06d",
                $prefix,
                $suffix,
            );
            $suffix++;
        } while (class_exists($classname));

        return $classname;
    }

    /**
     * Generates the method body of the virtual external function.
     *
     * @param stdClass $function a record from external_function.
     * @param array $params web service function parameters.
     * @return string body of the method for $function ie. everything within the {} of the method declaration.
     */
    protected function service_class_method_body($function, $params) {
        // Cast the param from object to array (validate_parameters except array only).
        $castingcode = '';
        $paramsstr = '';
        if (!empty($params)) {
            foreach ($params as $paramtocast) {
                // Clean the parameter from any white space.
                $paramtocast = trim($paramtocast);
                $castingcode .= "    $paramtocast = json_decode(json_encode($paramtocast), true);\n";
            }
            $paramsstr = implode(', ', $params);
        }

        $descriptionmethod = $function->methodname . '_returns()';
        $callforreturnvaluedesc = $function->classname . '::' . $descriptionmethod;

        $methodbody = <<classname::$function->methodname($paramsstr);
        return null;
    }
    return \\core_external\\external_api::clean_returnvalue($callforreturnvaluedesc, $function->classname::$function->methodname($paramsstr));
EOD;
        return $methodbody;
    }
}

/**
 * Early WS exception handler.
 * It handles exceptions during setup and returns the Exception text in the WS format.
 * If a raise function is found nothing is returned. Throws Exception otherwise.
 *
 * @param  Exception $ex Raised exception.
 * @throws Exception
 */
function early_ws_exception_handler(Exception $ex): void {
    if (function_exists('raise_early_ws_exception')) {
        raise_early_ws_exception($ex);
        die;
    }

    throw $ex;
}
PK09\I.zff
upload.phpnu[.


/**
 * 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 ,
 *              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 
 * @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.
    $avscanstarttime = microtime(true);
    \core\antivirus\manager::scan_file($_FILES[$fieldname]['tmp_name'], $_FILES[$fieldname]['name'], true);

    $file = new stdClass();
    $file->avscantime = microtime(true) - $avscanstarttime;
    $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(),
                        'avscantime' => $file->avscantime,
                ],
        ]);
        $logevent->trigger();
    }
}
echo json_encode($results);
PK09\S	wsdoc.phpnu[.


/**
 * Web services auto-generated documentation
 *
 * @package    core_webservice
 * @copyright  2009 Jerome Mouneyrac 
 * @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();
PK09\&2
recaptcha.phpnu[.


/**
 * 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\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 = <<
    
        
    
    
        $content
    

OET;
echo $output;
PK09\2C0classes/reportbuilder/local/entities/service.phpnu[.

declare(strict_types=1);

namespace core_webservice\reportbuilder\local\entities;

use core_collator;
use lang_string;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\filters\autocomplete;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;

/**
 * External service report builder entity
 *
 * @package    core_webservice
 * @copyright  2023 Mikel Martín 
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class service extends base {

    /**
     * Database tables that this entity uses and their default aliases
     *
     * @return array
     */
    protected function get_default_tables(): array {
        return [
            'external_services',
        ];
    }

    /**
     * The default title for this entity in the list of columns/conditions/filters in the report builder
     *
     * @return lang_string
     */
    protected function get_default_entity_title(): lang_string {
        return new lang_string('service', 'core_webservice');
    }

    /**
     * Initialise the entity
     *
     * @return base
     */
    public function initialise(): base {
        $columns = $this->get_all_columns();
        foreach ($columns as $column) {
            $this->add_column($column);
        }
        // All the filters defined by the entity can also be used as conditions.
        $filters = $this->get_all_filters();
        foreach ($filters as $filter) {
            $this
                ->add_filter($filter)
                ->add_condition($filter);
        }

        return $this;
    }

    /**
     * Returns list of all available columns
     *
     * @return column[]
     */
    protected function get_all_columns(): array {
        $tokenalias = $this->get_table_alias('external_services');

        // Service name column.
        $columnns[] = (new column(
            'name',
            new lang_string('name'),
            $this->get_entity_name()
        ))
            ->add_joins($this->get_joins())
            ->set_type(column::TYPE_TEXT)
            ->add_fields("{$tokenalias}.name, {$tokenalias}.shortname")
            ->set_is_sortable(true)
            ->add_callback(static function(?string $value, \stdClass $row): string {
                $output = $value;
                $output .= \html_writer::tag('div', format_text($row->shortname), [
                    'class' => 'small text-muted',
                ]);
                return $output;
            });

        return $columnns;
    }

    /**
     * Return list of all available filters
     *
     * @return filter[]
     */
    protected function get_all_filters(): array {
        global $DB;

        $tablealias = $this->get_table_alias('external_services');

        // Service Name filter.
        $filters[] = (new filter(
            autocomplete::class,
            'name',
            new lang_string('name'),
            $this->get_entity_name(),
            "{$tablealias}.name"
        ))
            ->add_joins($this->get_joins())
            ->set_options_callback(static function(): array {
                global $DB;
                $names = $DB->get_fieldset_sql('SELECT DISTINCT name FROM {external_services} ORDER BY name ASC');

                $options = [];
                foreach ($names as $name) {
                    $options[$name] = $name;
                }

                core_collator::asort($options);
                return $options;
            });

        return $filters;
    }
}
PK09\i::.classes/reportbuilder/local/entities/token.phpnu[.

declare(strict_types=1);

namespace core_webservice\reportbuilder\local\entities;

use lang_string;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\filters\{text, date};
use core_reportbuilder\local\helpers\format;
use core_reportbuilder\local\report\{column, filter};

/**
 * External token report builder entity
 *
 * @package    core_webservice
 * @copyright  2023 Mikel Martín 
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class token extends base {

    /**
     * Database tables that this entity uses and their default aliases
     *
     * @return array
     */
    protected function get_default_tables(): array {
        return [
            'external_tokens',
        ];
    }

    /**
     * The default title for this entity in the list of columns/conditions/filters in the report builder
     *
     * @return lang_string
     */
    protected function get_default_entity_title(): lang_string {
        return new lang_string('token', 'core_webservice');
    }

    /**
     * Initialise the entity
     *
     * @return base
     */
    public function initialise(): base {
        $columns = $this->get_all_columns();
        foreach ($columns as $column) {
            $this->add_column($column);
        }

        // All the filters defined by the entity can also be used as conditions.
        $filters = $this->get_all_filters();
        foreach ($filters as $filter) {
            $this
                ->add_filter($filter)
                ->add_condition($filter);
        }

        return $this;
    }

    /**
     * Returns list of all available columns
     *
     * @return column[]
     */
    protected function get_all_columns(): array {
        $tokenalias = $this->get_table_alias('external_tokens');

        // Token name column.
        $columnns[] = (new column(
            'name',
            new lang_string('name'),
            $this->get_entity_name()
        ))
            ->add_joins($this->get_joins())
            ->set_type(column::TYPE_TEXT)
            ->add_field("{$tokenalias}.name")
            ->set_is_sortable(true);

        // IP restriction column.
        $columnns[] = (new column(
            'iprestriction',
            new lang_string('iprestriction', 'core_webservice'),
            $this->get_entity_name()
        ))
            ->add_joins($this->get_joins())
            ->set_type(column::TYPE_TEXT)
            ->add_field("{$tokenalias}.iprestriction");

        // Valid until column.
        $columnns[] = (new column(
            'validuntil',
            new lang_string('validuntil', 'core_webservice'),
            $this->get_entity_name()
        ))
            ->add_joins($this->get_joins())
            ->set_type(column::TYPE_TIMESTAMP)
            ->add_field("{$tokenalias}.validuntil")
            ->set_is_sortable(true)
            ->add_callback([format::class, 'userdate'], get_string('strftimedatetime', 'core_langconfig'))
            ->add_callback(fn($value) => $value ?: get_string('validuntil_empty', 'core_webservice'));

        // Last access column.
        $columnns[] = (new column(
            'lastaccess',
            new lang_string('lastaccess'),
            $this->get_entity_name()
        ))
            ->add_joins($this->get_joins())
            ->set_type(column::TYPE_TIMESTAMP)
            ->add_field("{$tokenalias}.lastaccess")
            ->set_is_sortable(true)
            ->add_callback([format::class, 'userdate'])
            ->add_callback(fn($value) => $value ?: get_string('never'));

        return $columnns;
    }

    /**
     * Return list of all available filters
     *
     * @return filter[]
     */
    protected function get_all_filters(): array {
        global $DB;

        $tokenalias = $this->get_table_alias('external_tokens');

        // Name filter.
        $filters[] = (new filter(
            text::class,
            'name',
            new lang_string('tokenname', 'core_webservice'),
            $this->get_entity_name(),
            "{$tokenalias}.name"
        ))
            ->add_joins($this->get_joins());

        // Valid until filter.
        $filters[] = (new filter(
            date::class,
            'validuntil',
            new lang_string('validuntil', 'core_webservice'),
            $this->get_entity_name(),
            "{$tokenalias}.validuntil"
        ))
            ->add_joins($this->get_joins());

        return $filters;
    }
}
PK09\	>!>!4classes/reportbuilder/local/systemreports/tokens.phpnu[.

declare(strict_types=1);

namespace core_webservice\reportbuilder\local\systemreports;

use context_system;
use core_reportbuilder\local\entities\user;
use core_reportbuilder\local\report\{action, column};
use core_reportbuilder\system_report;
use core_webservice\reportbuilder\local\entities\{token, service};
use lang_string;
use moodle_url;
use pix_icon;

/**
 * Tokens system report
 *
 * @package    core_webservice
 * @copyright  2023 Mikel Martín 
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class tokens extends system_report {

    /**
     * Initialise report, we need to set the main table, load our entities and set columns/filters
     */
    protected function initialise(): void {
        global $USER;

        $entitytoken = new token();
        $entitytokenalias = $entitytoken->get_table_alias('external_tokens');

        $this->set_main_table('external_tokens', $entitytokenalias);
        $this->add_entity($entitytoken);

        $entityservice = new service();
        $entityservicealias = $entityservice->get_table_alias('external_services');
        $this->add_entity($entityservice->add_join(
            "LEFT JOIN {external_services} {$entityservicealias}
                    ON {$entityservicealias}.id = {$entitytokenalias}.externalserviceid"
        ));

        $entityuser = new user();
        $entityuseralias = $entityuser->get_table_alias('user');
        $this->add_entity($entityuser->add_join(
            "LEFT JOIN {user} {$entityuseralias} ON {$entityuseralias}.id = {$entitytokenalias}.userid"
        ));

        $entitycreator = new user();
        $entitycreator->set_entity_name('creator');
        $entitycreatoralias = $entitycreator->get_table_alias('user');
        $this->add_entity($entitycreator->add_join(
            "LEFT JOIN {user} {$entitycreatoralias} ON {$entitycreatoralias}.id = {$entitytokenalias}.creatorid"
        ));

        // Any columns required by actions should be defined here to ensure they're always available.
        $this->add_base_fields("{$entitytokenalias}.id");

        // Only show tokens created by the current user for non-manager users.
        if (!has_capability('moodle/webservice:managealltokens', context_system::instance())) {
            $this->add_base_condition_simple("{$entitycreatoralias}.userid", $USER->id);
        }

        $this->add_columns($entityuseralias, $entityservicealias);
        $this->add_filters();
        $this->add_actions();

        $this->set_initial_sort_column('token:validuntil', SORT_ASC);
    }

    /**
     * Validates access to view this report
     *
     * @return bool
     */
    protected function can_view(): bool {
        return has_capability('moodle/site:config', context_system::instance());
    }

    /**
     * Adds the columns we want to display in the report
     *
     * They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
     * unique identifier
     *
     * @param string $entityuseralias
     * @param string $entityservicealias
     */
    public function add_columns(string $entityuseralias, string $entityservicealias): void {
        $this->add_columns_from_entities([
            'token:name',
            'user:fullnamewithlink',
        ]);

        // Include all identity field columns.
        $identitycolumns = $this->get_entity('user')->get_identity_columns($this->get_context());
        foreach ($identitycolumns as $identitycolumn) {
            $this->add_column($identitycolumn);
        }

        $this->add_columns_from_entities([
            'service:name',
            'token:iprestriction',
            'token:validuntil',
            'token:lastaccess',
            'creator:fullnamewithlink',
        ]);

        $this->get_column('user:fullnamewithlink')
            ->set_title(new lang_string('user'));
        $this->get_column('service:name')
            ->set_title(new lang_string('service', 'core_webservice'));
        $this->get_column('creator:fullnamewithlink')
            ->set_title(new lang_string('tokencreator', 'core_webservice'))
            ->set_is_available(has_capability('moodle/webservice:managealltokens', context_system::instance()));

        $this->add_column((new column(
            'missingcapabilities',
            new lang_string('missingcaps', 'webservice'),
            'user'
        ))
            ->add_joins($this->get_joins())
            ->set_type(column::TYPE_TEXT)
            ->add_field("{$entityuseralias}.id", 'userid')
            ->add_fields(implode(', ', [
                "{$entityservicealias}.id",
                "{$entityservicealias}.shortname",
            ]))
            ->add_callback(static function($value, \stdClass $row): string {
                global $OUTPUT;

                $missingcapabilities = self::get_missing_capabilities((int) $row->userid, (int) $row->id, (string) $row->shortname);
                if (empty($missingcapabilities)) {
                    return '';
                }
                $missingcapabilities = array_map(function($missingcapability) {
                    return (object)[
                        'name' => $missingcapability,
                        'link' => get_capability_docs_link((object)['name' => $missingcapability]),
                    ];
                }, $missingcapabilities);
                return $OUTPUT->render_from_template('core_webservice/missing_capabilities', [
                    'missingcapabilities' => $missingcapabilities,
                    'helpicon' => $OUTPUT->help_icon('missingcaps', 'webservice'),
                ]);
            })
        );
    }

    /**
     * Adds the filters we want to display in the report
     *
     * They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
     * unique identifier
     */
    protected function add_filters(): void {
        $filters = [
            'token:name',
            'user:fullname',
            'service:name',
            'token:validuntil',
        ];

        $this->add_filters_from_entities($filters);

        $this->get_filter('user:fullname')
            ->set_header(new lang_string('user'));
        $this->get_filter('service:name')
            ->set_header(new lang_string('service', 'core_webservice'));
    }

    /**
     * Add the system report actions. An extra column will be appended to each row, containing all actions added here
     *
     * Note the use of ":id" placeholder which will be substituted according to actual values in the row
     */
    protected function add_actions(): void {

        // Action to delete token.
        $this->add_action((new action(
            new moodle_url('/admin/webservice/tokens.php', [
                'action' => 'delete',
                'tokenid' => ':id',
            ]),
            new pix_icon('t/delete', '', 'core'),
            ['class' => 'text-danger'],
            false,
            new lang_string('delete', 'core')
        )));
    }

    /**
     * Get the missing capabilities for a user
     *
     * @param int $userid
     * @param int $serviceid
     * @param string $serviceshortname
     * @return array
     */
    protected static function get_missing_capabilities(int $userid, int $serviceid, string $serviceshortname): array {
        global $CFG;
        require_once($CFG->dirroot . '/webservice/lib.php');

        $webservicemanager = new \webservice();
        $usermissingcaps = $webservicemanager->get_missing_capabilities_by_users([['id' => $userid]], $serviceid);
        if ($serviceshortname != MOODLE_OFFICIAL_MOBILE_SERVICE && !is_siteadmin($userid)) {
            return $usermissingcaps[$userid] ?? [];
        }
        return [];
    }
}
PK09\hn„00classes/token_table.phpnu[.

/**
 * Contains the class used for the displaying the tokens table.
 *
 * @package    core_webservice
 * @copyright  2017 John Okely 
 * @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 
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 *
 * @deprecated since 4.5 MDL-79496. Table replaced with a report builder system report.
 * @todo MDL-79909 This will be deleted in Moodle 6.0.
 */
#[\core\attribute\deprecated(
    replacement: null,
    since: '4.5',
    reason: 'Table replaced with a report builder system report',
    mdl: 'MDL-79496',
)]
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('' . implode(', ', $identity) . '', '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 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_' 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());
    }
}
PK09\&classes/privacy/provider.phpnu[.

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 
 * @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';
    }
}
PK09\.Uwclasses/token_filter.phpnu[.

/**
 * Provides the {@see core_webservice\token_filter} class.
 *
 * @package     core_webservice
 * @copyright   2020 David Mudrák 
 * @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 
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 *
 * @deprecated since 4.5 MDL-79496. Table replaced with a report builder system report.
 * @todo MDL-79909 This will be deleted in Moodle 6.0.
 */
#[\core\attribute\deprecated(
    replacement: null,
    since: '4.5',
    reason: 'Filters replaced with a report builder system report',
    mdl: 'MDL-79496',
)]
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);
    }
}
PK09\/classes/token_form.phpnu[.

/**
 * Provides the {@see \core_webservice\token_form} class.
 *
 * @package     core_webservice
 * @category    admin
 * @copyright   2020 David Mudrák 
 * @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 
 * @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;
    }
}
PK09\L'templates/missing_capabilities.mustachenu[{{!
    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 .
}}
{{!
    @template core_webservice/missing_capabilities

    Display missing capabilities in Manage tokens system report

    Example context (json):
    {
        "missingcapabilities": [
            {
                "capabilityname": "capabilityname",
                "capabilitylink": "capabilityname"
            }
        ],
        "helpicon": ""
    }
}}
    {{#missingcapabilities}}
  • {{{link}}}
    {{name}}
  • {{/missingcapabilities}}
{{{helpicon}}}
PK09\kF  upgrade.txtnu[=== 4.5 Onwards === This file has been replaced by UPGRADING.md. See MDL-81125 for further information. === 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. PK09\B UPGRADING.mdnu[# core_webservice (subsystem / plugintype) Upgrade notes ## 4.5rc1 ### Deprecated - The `token_table` and `token_filter` classes have been deprecated, in favour of new report builder implementation. For more information see [MDL-79496](https://tracker.moodle.org/browse/MDL-79496) PK09\zcAC C pluginfile.phpnu[. /** * A script to serve files from web service client * * @package core_webservice * @copyright 2011 Dongsheng Cai * @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: *'); header('Access-Control-Allow-Headers: range'); header('Access-Control-Expose-Headers: Content-Range'); // 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); PK09\r)tests/helpers.phpnu[. /** * 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 */ // Alias this class to the old name. // In future all uses of this class will be corrected and the legacy references will be removed. class_alias( \core_external\tests\externallib_testcase::class, \externallib_advanced_testcase::class, ); PK09\6;tests/generator/lib.phpnu[. 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 * @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'] ); } } PK09\Je443tests/generator/behat_core_webservice_generator.phpnu[. /** * Behat data generator for core_webservice. * * @package core_webservice * @category test * @copyright 2021 Andrew Nicols * @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', ], ], ]; } } PK09\SWٝDDtests/lib_test.phpnu[. /** * Unit tests for the webservice component. * * @package core_webservice * @category test * @copyright 2016 Jun Pataleta * @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 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final 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 static 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 * @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; } } PK09\55tests/externallib_test.phpnu[. namespace core_webservice; use core_external\external_api; use externallib_advanced_testcase; use core\tests\session\mock_handler; 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 * @covers \core_webservice_external::get_site_info * @category external * @copyright 2012 Paul Charsley * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class externallib_test extends externallib_advanced_testcase { #[\Override] public function setUp(): void { // Calling parent is good, always parent::setUp(); // We always need enabled WS for this testcase set_config('enablewebservices', '1'); } /** * Test get_site_info. */ 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(); $mockhandler = new mock_handler(); $mockhandler->add_test_session($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']); } /** * Test get_site_info returns the default home page URL when needed. */ public function test_get_site_info_default_home_page(): void { global $CFG; $this->resetAfterTest(); $this->setAdminUser(); // Site configuration. $CFG->defaulthomepage = HOMEPAGE_MY; $result = \core_webservice_external::get_site_info(); $result = external_api::clean_returnvalue(\core_webservice_external::get_site_info_returns(), $result); $this->assertEquals(HOMEPAGE_MY, $result['userhomepage']); $this->assertArrayNotHasKey('userhomepageurl', $result); $CFG->defaulthomepage = "/home"; $result = \core_webservice_external::get_site_info(); $result = external_api::clean_returnvalue(\core_webservice_external::get_site_info_returns(), $result); $this->assertEquals(HOMEPAGE_URL, $result['userhomepage']); $this->assertEquals("{$CFG->wwwroot}/home", $result['userhomepageurl']); // User preference. $CFG->defaulthomepage = HOMEPAGE_USER; $userpreference = "/about"; set_user_preference('user_home_page_preference', $userpreference); $result = \core_webservice_external::get_site_info(); $result = external_api::clean_returnvalue(\core_webservice_external::get_site_info_returns(), $result); $this->assertEquals(HOMEPAGE_URL, $result['userhomepage']); $this->assertEquals("{$CFG->wwwroot}/about", $result['userhomepageurl']); } } PK09\H..tests/event/events_test.phpnu[. /** * 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 */ final class events_test extends \advanced_testcase { public function setUp(): void { parent::setUp(); $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); } } PK09\̰s$tests/behat/webservice_users.featurenu[@core @core_admin @core_webservice Feature: Web service user settings In order to configure authorised users for a web service As an admin I need to use the page that lets you do that Background: # Include a custom profile field so we can check it gets displayed Given the following "custom profile fields" exist: | datatype | shortname | name | param2 | | text | frog | Favourite frog | 100 | And the following config values are set as admin: | showuseridentity | email,profile_field_frog | And the following "users" exist: | username | firstname | lastname | email | profile_field_frog | | user1 | User | One | 1@example.org | Kermit | And the following "core_webservice > Service" exists: | name | Silly service | | shortname | silly | | restrictedusers | 1 | | enabled | 1 | Scenario: Add a user to a web service When I log in as "admin" And I navigate to "Server > Web services > External services" in site administration And I click on "Authorised users" "link" in the "Silly service" "table_row" And I set the field "Not authorised users" to "User One" And I press "Add" Then I should see "User One" in the ".alloweduserlist" "css_element" And I should see "1@example.org" in the ".alloweduserlist" "css_element" And I should see "Kermit" in the ".alloweduserlist" "css_element" @javascript Scenario: Add a function to a web service When I log in as "admin" And I navigate to "Server > Web services > External services" in site administration And I click on "Functions" "link" in the "Silly service" "table_row" And I follow "Add functions" And I set the field "Name" to "core_blog_get_entries" And I click on "Add functions" "button" in the "#fgroup_id_buttonar" "css_element" Then I should see "core_blog_get_entries" in the "generaltable" "table" PK09\Ybb!tests/behat/manage_tokens.featurenu[@core @core_admin @core_webservice Feature: Manage external services tokens In order to manage external service usage As an admin I need to be able to create, filter and delete tokens Background: Given the following "users" exist: | username | password | firstname | lastname | | user1 | user1 | Firstname1 | Lastname1 | | user2 | user2 | Firstname2 | Lastname2 | | user3 | user3 | Firstname3 | Lastname3 | | user4 | user4 | Firstname4 | Lastname4 | @javascript Scenario: Add a token to user identified by name and then delete that token Given I log in as "admin" And I am on site homepage And I navigate to "Server > Web services > Manage tokens" in site administration And I press "Create token" And I set the field "Name" to "Webservice1" And I set the field "User" to "Firstname1 Lastname1" And I set the field "Service" to "Moodle mobile web service" And I set the field "IP restriction" to "127.0.0.1" When I press "Save changes" Then the following should exist in the "generaltable" table: | Name | User | Service | IP restriction | Last access | | Webservice1 | Firstname1 Lastname1 | Moodle mobile web service | 127.0.0.1 | Never | # Verify the message and the "Copy to clipboard" button. And I should see "Copy the token now. It won't be shown again once you leave this page." And "Copy to clipboard" "button" should exist # New token can only read once. And I reload the page And I should not see "Copy the token now. It won't be shown again once you leave this page." And "Copy to clipboard" "button" should not exist # Delete token. And I change the window size to "large" And I press "Delete" action in the "Webservice1" report row And I should see "Do you really want to delete this web service token for Firstname1 Lastname1 on the service Moodle mobile web service?" And I press "Delete" And "Webservice1" "table_row" should not exist @javascript @skip_chrome_zerosize Scenario: Tokens can be filtered by name (case-insensitive), by user and by service Given the following "core_webservice > Service" exists: | name | Site information | | shortname | siteinfo | | enabled | 1 | And the following "core_webservice > Service function" exists: | service | siteinfo | | functions | core_webservice_get_site_info | And the following "core_webservice > Tokens" exist: | user | service | name | validuntil | | user2 | siteinfo | WEBservice1 | ## yesterday ## | | user3 | moodle_mobile_app | webservicE3 | ## +1 year ## | | user4 | siteinfo | New service2 | ## +1 year ## | When I log in as "admin" And I navigate to "Server > Web services > Manage tokens" in site administration # All created tokens are shown by default. And "Firstname1 Lastname1" "table_row" should not exist And I should see "Site information" in the "Firstname2 Lastname2" "table_row" And I should see "Moodle mobile web service" in the "Firstname3 Lastname3" "table_row" And I should see "Site information" in the "Firstname4 Lastname4" "table_row" # Filter tokens by by name. And I click on "Filters" "button" And I set the following fields in the "Name" "core_reportbuilder > Filter" to these values: | Name operator | Contains | | Name value | webservice | And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element" And I should see "Site information" in the "Firstname2 Lastname2" "table_row" And I should see "Moodle mobile web service" in the "Firstname3 Lastname3" "table_row" And "Firstname4 Lastname4" "table_row" should not exist And I click on "Reset all" "button" in the "[data-region='report-filters']" "css_element" # Filter tokens by user. And I set the following fields in the "User" "core_reportbuilder > Filter" to these values: | User operator | Contains | | User value | Firstname2 | And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element" Then "Firstname3 Lastname3" "table_row" should not exist And "Firstname4 Lastname4" "table_row" should not exist And I should see "Site information" in the "Firstname2 Lastname2" "table_row" And I click on "Reset all" "button" in the "[data-region='report-filters']" "css_element" # Filter tokens by service. And I set the following fields in the "Service" "core_reportbuilder > Filter" to these values: | Service value | Site information | And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element" And I should see "Site information" in the "Firstname2 Lastname2" "table_row" And I should see "Site information" in the "Firstname4 Lastname4" "table_row" And "Firstname3 Lastname3" "table_row" should not exist And I click on "Reset all" "button" in the "[data-region='report-filters']" "css_element" # Filter tokens by valid date. And I set the following fields in the "Valid until" "core_reportbuilder > Filter" to these values: | Valid until operator | Last | | Valid until value | 2 | | Valid until unit | day(s) | And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element" Then "Firstname3 Lastname3" "table_row" should not exist And "Firstname4 Lastname4" "table_row" should not exist And I should see "Site information" in the "Firstname2 Lastname2" "table_row" # Reset the filters. And I click on "Reset all" "button" in the "[data-region='report-filters']" "css_element" And I should see "Site information" in the "Firstname2 Lastname2" "table_row" And I should see "Moodle mobile web service" in the "Firstname3 Lastname3" "table_row" And I should see "Site information" in the "Firstname4 Lastname4" "table_row" @javascript Scenario: Tokens table should display missing capabilities Given the following "core_webservice > Services" exist: | name | shortname | enabled | | Test Service 1 | testservice1 | 1 | | Test Service 2 | testservice2 | 1 | And the following "core_webservice > Service functions" exist: | service | functions | | testservice1 | block_accessreview_get_module_data | | testservice2 | core_block_fetch_addable_blocks | And the following "core_webservice > Tokens" exist: | user | service | name | | user1 | testservice1 | Token 01 | | user2 | testservice2 | Token 02 | When I log in as "admin" And I navigate to "Server > Web services > Manage tokens" in site administration # Check the missing capabilities. Then I should see "View the accessibility review" in the "Token 01" "table_row" And I should see "block/accessreview:view" in the "Token 01" "table_row" Then I should see "Manage blocks on a page" in the "Token 02" "table_row" And I should see "moodle/site:manageblocks" in the "Token 02" "table_row" PK09\M AA:tests/behat/siteadmin_webservice_token_breadcrumbs.featurenu[@core @core_admin @core_webservice @javascript Feature: Verify the breadcrumbs in webservice tokens site administration pages Whenever I navigate to manage tokens page in site administration As an admin The breadcrumbs should be visible Background: Given I log in as "admin" Scenario: Verify the breadcrumbs in manage tokens page as an admin Given the following "users" exist: | username | firstname | lastname | email | | student1 | John | Doe | s1@example.com | And I log in as "admin" And I navigate to "Server > Web services > Manage tokens" in site administration And I click on "Create token" "button" And "Create token" "text" should exist in the ".breadcrumb" "css_element" And "Manage tokens" "link" should exist in the ".breadcrumb" "css_element" And "Web services" "link" should exist in the ".breadcrumb" "css_element" And I set the field "User" to "John Doe" And I set the field "Service" to "Moodle mobile web service" And I press "Save changes" When I press "Delete" action in the "John Doe" report row Then "Delete token" "text" should exist in the ".breadcrumb" "css_element" And "Manage tokens" "link" should exist in the ".breadcrumb" "css_element" And "Web services" "link" should exist in the ".breadcrumb" "css_element" PK09\#o  4tests/behat/siteadmin_webservice_breadcrumbs.featurenu[@core @core_admin @core_webservice @javascript Feature: Verify the breadcrumbs in external webservice site administration pages Whenever I navigate to external webservice page in site administration As an admin The breadcrumbs should be visible Background: Given I log in as "admin" Scenario: Verify the breadcrumbs in external services page Given I navigate to "Server > Web services > External services" in site administration And "External services" "text" should exist in the ".breadcrumb" "css_element" And "Web services" "link" should exist in the ".breadcrumb" "css_element" When I click on "Edit" "link" Then "Edit external service" "text" should exist in the ".breadcrumb" "css_element" And "External services" "link" should exist in the ".breadcrumb" "css_element" And "Web services" "link" should exist in the ".breadcrumb" "css_element" And I press "Cancel" And I click on "Add" "link" And I set the field "Name" to "function to test" And "Add external service" "text" should exist in the ".breadcrumb" "css_element" And "External services" "link" should exist in the ".breadcrumb" "css_element" And "Web services" "link" should exist in the ".breadcrumb" "css_element" And I press "Add service" And "Functions" "text" should exist in the ".breadcrumb" "css_element" And "External services" "link" should exist in the ".breadcrumb" "css_element" And "Web services" "link" should exist in the ".breadcrumb" "css_element" And I navigate to "Server > Web services > External services" in site administration And I click on "Delete" "link" And "External services" "text" should exist in the ".breadcrumb" "css_element" And "Web services" "link" should exist in the ".breadcrumb" "css_element" PK09\?#rest/db/access.phpnu[. /** * 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( ), ), ); PK09\p^aa rest/lang/en/webservice_rest.phpnu[. /** * 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'; PK09\ӏLg rest/lib.phpnu[. /** * 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; } }PK09\rest/version.phpnu[. /** * 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 = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'webservice_rest'; // Full name of the plugin (used for diagnostics) PK09\PD!rest/classes/privacy/provider.phpnu[. /** * Privacy provider implementation for webservice_rest. * * @package webservice_rest * @copyright 2018 Mihail Geshoski * @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 * @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'; } } PK09\V9**rest/locallib.phpnu[. /** * 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 = ''."\n"; $response .= ''."\n"; $response .= self::xmlize_result($validatedvalues, $this->function->returns_desc); $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 = ''."\n"; $error .= ''."\n"; if (isset($ex->errorcode)) { $error .= '' . htmlspecialchars($ex->errorcode, ENT_COMPAT, 'UTF-8') . '' . "\n"; } $error .= ''.htmlspecialchars($ex->getMessage(), ENT_COMPAT, 'UTF-8').''."\n"; if (debugging() and isset($ex->debuginfo)) { $error .= ''.htmlspecialchars($ex->debuginfo, ENT_COMPAT, 'UTF-8').''."\n"; } $error .= ''."\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 ''."\n"; } else { return ''.htmlspecialchars($returns, ENT_COMPAT, 'UTF-8').''."\n"; } } else if ($desc instanceof external_multiple_structure) { $mult = ''."\n"; if (!empty($returns)) { foreach ($returns as $val) { $mult .= self::xmlize_result($val, $desc->content); } } $mult .= ''."\n"; return $mult; } else if ($desc instanceof external_single_structure) { $single = ''."\n"; foreach ($desc->keys as $key=>$subdesc) { $value = isset($returns[$key]) ? $returns[$key] : null; $single .= ''.self::xmlize_result($value, $subdesc).''."\n"; } $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); } } PK09\4lKKrest/server.phpnu[. /** * 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); } PK09\?ī""rest/tests/server_test.phpnu[. 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 */ final class server_test extends \advanced_testcase { /** * Data provider for test_xmlize. * @return array */ public static function xmlize_provider(): array { $data = []; $data[] = [null, null, '']; $data[] = [new external_value(PARAM_BOOL), false, "0\n"]; $data[] = [new external_value(PARAM_BOOL), true, "1\n"]; $data[] = [new external_value(PARAM_ALPHA), null, "\n"]; $data[] = [new external_value(PARAM_ALPHA), 'a', "a\n"]; $data[] = [new external_value(PARAM_INT), 123, "123\n"]; $data[] = [ new external_multiple_structure(new external_value(PARAM_INT)), [1, 2, 3], "\n" . "1\n" . "2\n" . "3\n" . "\n" ]; $data[] = [ // Multiple structure with null value. new external_multiple_structure(new external_value(PARAM_ALPHA)), ['A', null, 'C'], "\n" . "A\n" . "\n" . "C\n" . "\n" ]; $data[] = [ // Multiple structure without values. new external_multiple_structure(new external_value(PARAM_ALPHA)), [], "\n" . "\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], "\n" . "1\n\n" . "2\n\n" . "3\n\n" . "\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], "\n" . "1\n\n" . "\n\n" . "3\n\n" . "\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], "\n" . "\n\n" . "\n\n" . "3\n\n" . "\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'], ], "\n" . "\n". "2\n" . "3\n" . "4\n" . "\n\n" . "\n". "\n" . "Louis\n\n" . "Armstrong\n\n" . "\n" . "\n" . "Neil\n\n" . "Armstrong\n\n" . "\n" . "\n\n" . "\n" . "Neil\n\n" . "Armstrong\n\n" . "\n\n" . "\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'], ], "\n" . "\n\n\n" . "\n". "\n" . "Louis\n\n" . "\n\n" . "\n" . "\n" . "\n\n" . "Armstrong\n\n" . "\n" . "\n\n" . "\n" . "\n\n" . "Armstrong\n\n" . "\n\n" . "\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)); } } PK09\k--rest/simpleserver.phpnu[. /** * 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; PK09\ draftfile.phpnu[PK09\5 i=i=externallib.phpnu[PK09\ZDsoap/db/access.phpnu[PK09\rzaa Isoap/lang/en/webservice_soap.phpnu[PK09\\ \ ;Nsoap/lib.phpnu[PK09\@3b||Ysoap/version.phpnu[PK09\ ]7!^soap/classes/privacy/provider.phpnu[PK09\))dsoap/classes/wsdl.phpnu[PK09\as--Ǝsoap/locallib.phpnu[PK09\gGKsoap/server.phpnu[PK09\Pr88soap/tests/wsdl_test.phpnu[PK09\(( soap/simpleserver.phpnu[PK09\6M. renderer.phpnu[PK09\^xlib.phpnu[PK09\I.zff upload.phpnu[PK09\ S _wsdoc.phpnu[PK09\&2 $recaptcha.phpnu[PK09\2C0Eclasses/reportbuilder/local/entities/service.phpnu[PK09\i::.*classes/reportbuilder/local/entities/token.phpnu[PK09\ >!>!4classes/reportbuilder/local/systemreports/tokens.phpnu[PK09\hn„00d4classes/token_table.phpnu[PK09\&/eclasses/privacy/provider.phpnu[PK09\.Uwjclasses/token_filter.phpnu[PK09\/zclasses/token_form.phpnu[PK09\L'templates/missing_capabilities.mustachenu[PK09\kF  upgrade.txtnu[PK09\B <UPGRADING.mdnu[PK09\zcAC C pluginfile.phpnu[PK09\r)tests/helpers.phpnu[PK09\6;tests/generator/lib.phpnu[PK09\Je443 tests/generator/behat_core_webservice_generator.phpnu[PK09\SWٝDDtests/lib_test.phpnu[PK09\55!tests/externallib_test.phpnu[PK09\H..Wtests/event/events_test.phpnu[PK09\̰s$tests/behat/webservice_users.featurenu[PK09\Ybb!5tests/behat/manage_tokens.featurenu[PK09\M AA:tests/behat/siteadmin_webservice_token_breadcrumbs.featurenu[PK09\#o  4tests/behat/siteadmin_webservice_breadcrumbs.featurenu[PK09\?#rest/db/access.phpnu[PK09\p^aa rest/lang/en/webservice_rest.phpnu[PK09\ӏLg rest/lib.phpnu[PK09\rest/version.phpnu[PK09\PD!rest/classes/privacy/provider.phpnu[PK09\V9**rest/locallib.phpnu[PK09\4lKKrest/server.phpnu[PK09\?ī"" rest/tests/server_test.phpnu[PK09\k--x0rest/simpleserver.phpnu[PK//25