MDL-59495 moodlenet: move all moodlenet code into new namespace
[moodle.git] / lib / classes / hub / api.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Class communication
19  *
20  * @package    core
21  * @copyright  2017 Marina Glancy
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 namespace core\hub;
26 defined('MOODLE_INTERNAL') || die();
28 use webservice_xmlrpc_client;
29 use moodle_exception;
30 use curl;
31 use stdClass;
32 use coding_exception;
33 use moodle_url;
35 /**
36  * Methods to communicate with moodle.net web services
37  *
38  * @package    core
39  * @copyright  2017 Marina Glancy
40  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41  */
42 class api {
44     /** @var File type: Course screenshot */
45     const HUB_SCREENSHOT_FILE_TYPE = 'screenshot';
47     /** @var File type: Hub screenshot */
48     const HUB_HUBSCREENSHOT_FILE_TYPE = 'hubscreenshot';
50     /** @var File type: Backup */
51     const HUB_BACKUP_FILE_TYPE = 'backup';
53     /**
54      * Calls moodle.net WS
55      *
56      * @param string $function name of WS function
57      * @param array $data parameters of WS function
58      * @param bool $allowpublic allow request without moodle.net registration
59      * @return mixed depends on the function
60      * @throws moodle_exception
61      */
62     protected static function call($function, array $data = [], $allowpublic = false) {
64         $token = registration::get_token() ?: 'publichub';
65         if (!$allowpublic && $token === 'publichub') {
66             // This will throw an exception.
67             registration::require_registration();
68         }
70         if (extension_loaded('xmlrpc')) {
71             // Use XMLRPC protocol.
72             return self::call_xmlrpc($token, $function, $data);
73         } else {
74             // Use REST.
75             return self::call_rest($token, $function, $data);
76         }
77     }
79     /**
80      * Performs REST request to moodle.net (using GET method)
81      *
82      * @param string $token
83      * @param string $function
84      * @param array $data
85      * @return mixed
86      * @throws moodle_exception
87      */
88     protected static function call_xmlrpc($token, $function, array $data) {
89         global $CFG;
90         require_once($CFG->dirroot . "/webservice/xmlrpc/lib.php");
92         $serverurl = HUB_MOODLEORGHUBURL . "/local/hub/webservice/webservices.php";
93         $xmlrpcclient = new webservice_xmlrpc_client($serverurl, $token);
94         try {
95             return $xmlrpcclient->call($function, $data);
96         } catch (\Exception $e) {
97             // Function webservice_xmlrpc_client::call() can throw Exception, wrap it into moodle_exception.
98             throw new moodle_exception('errorws', 'hub', '', $e->getMessage());
99         }
100     }
102     /**
103      * Performs REST request to moodle.net (using GET method)
104      *
105      * @param string $token
106      * @param string $function
107      * @param array $data
108      * @return mixed
109      * @throws moodle_exception
110      */
111     protected static function call_rest($token, $function, array $data) {
112         $params = [
113                 'wstoken' => $token,
114                 'wsfunction' => $function,
115                 'moodlewsrestformat' => 'json'
116             ] + $data;
118         $curl = new curl();
119         $serverurl = HUB_MOODLEORGHUBURL . "/local/hub/webservice/webservices.php";
120         $curloutput = @json_decode($curl->get($serverurl, $params), true);
121         $info = $curl->get_info();
122         if ($curl->get_errno()) {
123             throw new moodle_exception('errorconnect', 'hub', '', $curl->error);
124         } else if (isset($curloutput['exception'])) {
125             // Error message returned by web service.
126             throw new moodle_exception('errorws', 'hub', '', $curloutput['message']);
127         } else if ($info['http_code'] != 200) {
128             throw new moodle_exception('errorconnect', 'hub', '', $info['http_code']);
129         } else {
130             return $curloutput;
131         }
132     }
134     /**
135      * Update site registration on moodle.net
136      *
137      * @param array $siteinfo
138      * @throws moodle_exception
139      */
140     public static function update_registration(array $siteinfo) {
141         $params = array('siteinfo' => $siteinfo);
142         self::call('hub_update_site_info', $params);
143     }
145     /**
146      * Returns information about moodle.net
147      *
148      * Example of the return array:
149      * {
150      *     "courses": 384,
151      *     "description": "Moodle.net connects you with free content and courses shared by Moodle ...",
152      *     "downloadablecourses": 190,
153      *     "enrollablecourses": 194,
154      *     "hublogo": 1,
155      *     "language": "en",
156      *     "name": "Moodle.net",
157      *     "sites": 274175,
158      *     "url": "https://moodle.net",
159      *     "imgurl": "https://moodle.net/local/hub/webservice/download.php?filetype=hubscreenshot"
160      * }
161      *
162      * @return array
163      * @throws moodle_exception
164      */
165     public static function get_hub_info() {
166         $info = self::call('hub_get_info', [], true);
167         $info['imgurl'] = new moodle_url(HUB_MOODLEORGHUBURL . '/local/hub/webservice/download.php',
168             ['filetype' => self::HUB_HUBSCREENSHOT_FILE_TYPE]);
169         return $info;
170     }
172     /**
173      * Calls WS function hub_get_courses
174      *
175      * Parameter $options may have any of these fields:
176      * [
177      *     'ids' => new external_multiple_structure(new external_value(PARAM_INTEGER, 'id of a course in the hub course
178      *          directory'), 'ids of course', VALUE_OPTIONAL),
179      *     'sitecourseids' => new external_multiple_structure(new external_value(PARAM_INTEGER, 'id of a course in the
180      *          site'), 'ids of course in the site', VALUE_OPTIONAL),
181      *     'coverage' => new external_value(PARAM_TEXT, 'coverage', VALUE_OPTIONAL),
182      *     'licenceshortname' => new external_value(PARAM_ALPHANUMEXT, 'licence short name', VALUE_OPTIONAL),
183      *     'subject' => new external_value(PARAM_ALPHANUM, 'subject', VALUE_OPTIONAL),
184      *     'audience' => new external_value(PARAM_ALPHA, 'audience', VALUE_OPTIONAL),
185      *     'educationallevel' => new external_value(PARAM_ALPHA, 'educational level', VALUE_OPTIONAL),
186      *     'language' => new external_value(PARAM_ALPHANUMEXT, 'language', VALUE_OPTIONAL),
187      *     'orderby' => new external_value(PARAM_ALPHA, 'orderby method: newest, eldest, publisher, fullname,
188      *          ratingaverage', VALUE_OPTIONAL),
189      *     'givememore' => new external_value(PARAM_INT, 'next range of result - range size being set by the hub
190      *          server ', VALUE_OPTIONAL),
191      *     'allsitecourses' => new external_value(PARAM_INTEGER,
192      *          'if 1 return all not visible and visible courses whose siteid is the site
193      *          matching token. Only courses of this site are returned.
194      *          givememore parameter is ignored if this param = 1.
195      *          In case of public token access, this param option is ignored', VALUE_DEFAULT, 0),
196      * ]
197      *
198      * Each course in the returned array of courses will have fields:
199      * [
200      *     'id' => new external_value(PARAM_INTEGER, 'id'),
201      *     'fullname' => new external_value(PARAM_TEXT, 'course name'),
202      *     'shortname' => new external_value(PARAM_TEXT, 'course short name'),
203      *     'description' => new external_value(PARAM_TEXT, 'course description'),
204      *     'language' => new external_value(PARAM_ALPHANUMEXT, 'course language'),
205      *     'publishername' => new external_value(PARAM_TEXT, 'publisher name'),
206      *     'publisheremail' => new external_value(PARAM_EMAIL, 'publisher email', VALUE_OPTIONAL),
207      *     'privacy' => new external_value(PARAM_INT, 'privacy: published or not', VALUE_OPTIONAL),
208      *     'sitecourseid' => new external_value(PARAM_INT, 'course id on the site', VALUE_OPTIONAL),
209      *     'contributornames' => new external_value(PARAM_TEXT, 'contributor names', VALUE_OPTIONAL),
210      *     'coverage' => new external_value(PARAM_TEXT, 'coverage', VALUE_OPTIONAL),
211      *     'creatorname' => new external_value(PARAM_TEXT, 'creator name'),
212      *     'licenceshortname' => new external_value(PARAM_ALPHANUMEXT, 'licence short name'),
213      *     'subject' => new external_value(PARAM_ALPHANUM, 'subject'),
214      *     'audience' => new external_value(PARAM_ALPHA, 'audience'),
215      *     'educationallevel' => new external_value(PARAM_ALPHA, 'educational level'),
216      *     'creatornotes' => new external_value(PARAM_RAW, 'creator notes'),
217      *     'creatornotesformat' => new external_value(PARAM_INTEGER, 'notes format'),
218      *     'demourl' => new external_value(PARAM_URL, 'demo URL', VALUE_OPTIONAL),
219      *     'courseurl' => new external_value(PARAM_URL, 'course URL', VALUE_OPTIONAL),
220      *     'backupsize' => new external_value(PARAM_INT, 'course backup size in bytes', VALUE_OPTIONAL),
221      *     'enrollable' => new external_value(PARAM_BOOL, 'is the course enrollable'),
222      *     'screenshots' => new external_value(PARAM_INT, 'total number of screenshots'),
223      *     'timemodified' => new external_value(PARAM_INT, 'time of last modification - timestamp'),
224      *     'contents' => new external_multiple_structure(new external_single_structure(
225      *         array(
226      *             'moduletype' => new external_value(PARAM_ALPHA, 'the type of module (activity/block)'),
227      *             'modulename' => new external_value(PARAM_TEXT, 'the name of the module (forum, resource etc)'),
228      *             'contentcount' => new external_value(PARAM_INT, 'how many time the module is used in the course'),
229      *         )), 'contents', VALUE_OPTIONAL),
230      *     'rating' => new external_single_structure (
231      *         array(
232      *              'aggregate' =>  new external_value(PARAM_FLOAT, 'Rating average', VALUE_OPTIONAL),
233      *              'scaleid' => new external_value(PARAM_INT, 'Rating scale'),
234      *              'count' => new external_value(PARAM_INT, 'Rating count'),
235      *         ), 'rating', VALUE_OPTIONAL),
236      *     'comments' => new external_multiple_structure(new external_single_structure (
237      *          array(
238      *              'comment' => new external_value(PARAM_TEXT, 'the comment'),
239      *              'commentator' => new external_value(PARAM_TEXT, 'the name of commentator'),
240      *              'date' => new external_value(PARAM_INT, 'date of the comment'),
241      *         )), 'contents', VALUE_OPTIONAL),
242      *     'outcomes' => new external_multiple_structure(new external_single_structure(
243      *          array(
244      *              'fullname' => new external_value(PARAM_TEXT, 'the outcome fullname')
245      *          )), 'outcomes', VALUE_OPTIONAL)
246      * ]
247      *
248      * Additional fields for each course:
249      *      'screenshotbaseurl' (moodle_url) URL of the first screenshot, only set if $course['screenshots']>0
250      *      'commenturl' (moodle_url) URL for comments
251      *
252      * @param string $search search string
253      * @param bool $downloadable return downloadable courses
254      * @param bool $enrollable return enrollable courses
255      * @param array|\stdClass $options other options from the list of allowed options:
256      *              'ids', 'sitecourseids', 'coverage', 'licenceshortname', 'subject', 'audience',
257      *              'educationallevel', 'language', 'orderby', 'givememore', 'allsitecourses'
258      * @return array of two elements: [$courses, $coursetotal]
259      * @throws \coding_exception
260      * @throws moodle_exception
261      */
262     public static function get_courses($search, $downloadable, $enrollable, $options) {
263         static $availableoptions = ['ids', 'sitecourseids', 'coverage', 'licenceshortname', 'subject', 'audience',
264             'educationallevel', 'language', 'orderby', 'givememore', 'allsitecourses'];
266         if (empty($options)) {
267             $options = [];
268         } else if (is_object($options)) {
269             $options = (array)$options;
270         } else if (!is_array($options)) {
271             throw new \coding_exception('Parameter $options is invalid');
272         }
274         if ($unknownkeys = array_diff(array_keys($options), $availableoptions)) {
275             throw new \coding_exception('Unknown option(s): ' . join(', ', $unknownkeys));
276         }
278         $params = [
279             'search' => $search,
280             'downloadable' => (int)(bool)$downloadable,
281             'enrollable' => (int)(bool)$enrollable,
282             'options' => $options
283         ];
284         $result = self::call('hub_get_courses', $params, true);
285         $courses = $result['courses'];
286         $coursetotal = $result['coursetotal'];
288         foreach ($courses as $idx => $course) {
289             $courses[$idx]['screenshotbaseurl'] = null;
290             if (!empty($course['screenshots'])) {
291                 $courses[$idx]['screenshotbaseurl'] = new moodle_url(HUB_MOODLEORGHUBURL . '/local/hub/webservice/download.php',
292                     array('courseid' => $course['id'],
293                         'filetype' => self::HUB_SCREENSHOT_FILE_TYPE));
294             }
295             $courses[$idx]['commenturl'] = new moodle_url(HUB_MOODLEORGHUBURL,
296                 array('courseid' => $course['id'], 'mustbelogged' => true));
297         }
299         return [$courses, $coursetotal];
300     }
302     /**
303      * Unregister the site
304      *
305      * @throws moodle_exception
306      */
307     public static function unregister_site() {
308         self::call('hub_unregister_site');
309     }
311     /**
312      * Unpublish courses
313      *
314      * @param int[]|int $courseids
315      * @throws moodle_exception
316      */
317     public static function unregister_courses($courseids) {
318         $courseids = (array)$courseids;
319         $params = array('courseids' => $courseids);
320         self::call('hub_unregister_courses', $params);
321     }
323     /**
324      * Publish one course
325      *
326      * Expected contents of $courseinfo:
327      * [
328      *     'sitecourseid' => new external_value(PARAM_INT, 'the id of the course on the publishing site'),
329      *     'fullname' => new external_value(PARAM_TEXT, 'course name'),
330      *     'shortname' => new external_value(PARAM_TEXT, 'course short name'),
331      *     'description' => new external_value(PARAM_TEXT, 'course description'),
332      *     'language' => new external_value(PARAM_ALPHANUMEXT, 'course language'),
333      *     'publishername' => new external_value(PARAM_TEXT, 'publisher name'),
334      *     'publisheremail' => new external_value(PARAM_EMAIL, 'publisher email'),
335      *     'contributornames' => new external_value(PARAM_TEXT, 'contributor names'),
336      *     'coverage' => new external_value(PARAM_TEXT, 'coverage'),
337      *     'creatorname' => new external_value(PARAM_TEXT, 'creator name'),
338      *     'licenceshortname' => new external_value(PARAM_ALPHANUMEXT, 'licence short name'),
339      *     'subject' => new external_value(PARAM_ALPHANUM, 'subject'),
340      *     'audience' => new external_value(PARAM_ALPHA, 'audience'),
341      *     'educationallevel' => new external_value(PARAM_ALPHA, 'educational level'),
342      *     'creatornotes' => new external_value(PARAM_RAW, 'creator notes'),
343      *     'creatornotesformat' => new external_value(PARAM_INTEGER, 'notes format'),
344      *     'demourl' => new external_value(PARAM_URL, 'demo URL', VALUE_OPTIONAL),
345      *     'courseurl' => new external_value(PARAM_URL, 'course URL', VALUE_OPTIONAL),
346      *     'enrollable' => new external_value(PARAM_BOOL, 'is the course enrollable', VALUE_DEFAULT, 0),
347      *     'screenshots' => new external_value(PARAM_INT, 'the number of screenhots', VALUE_OPTIONAL),
348      *     'deletescreenshots' => new external_value(PARAM_INT, 'ask to delete all the existing screenshot files
349      *          (it does not reset the screenshot number)', VALUE_DEFAULT, 0),
350      *     'contents' => new external_multiple_structure(new external_single_structure(
351      *          array(
352      *              'moduletype' => new external_value(PARAM_ALPHA, 'the type of module (activity/block)'),
353      *              'modulename' => new external_value(PARAM_TEXT, 'the name of the module (forum, resource etc)'),
354      *              'contentcount' => new external_value(PARAM_INT, 'how many time the module is used in the course'),
355      *          )), 'contents', VALUE_OPTIONAL),
356      *     'outcomes' => new external_multiple_structure(new external_single_structure(
357      *         array(
358      *              'fullname' => new external_value(PARAM_TEXT, 'the outcome fullname')
359      *          )), 'outcomes', VALUE_OPTIONAL)
360      * ]
361      *
362      * @param array|\stdClass $courseinfo
363      * @return int id of the published course on the hub
364      * @throws moodle_exception if communication to moodle.net failed or course could not be published
365      */
366     public static function register_course($courseinfo) {
367         $params = array('courses' => array($courseinfo));
368         $hubcourseids = self::call('hub_register_courses', $params);
369         if (count($hubcourseids) != 1) {
370             throw new moodle_exception('errorcoursewronglypublished', 'hub');
371         }
372         return $hubcourseids[0];
373     }
375     /**
376      * Uploads a screenshot for the published course
377      *
378      * @param int $hubcourseid id of the published course on moodle.net, it must be published from this site
379      * @param \stored_file $file
380      * @param int $screenshotnumber ordinal number of the screenshot
381      */
382     public static function add_screenshot($hubcourseid, \stored_file $file, $screenshotnumber) {
383         $curl = new \curl();
384         $params = array();
385         $params['filetype'] = self::HUB_SCREENSHOT_FILE_TYPE;
386         $params['file'] = $file;
387         $params['courseid'] = $hubcourseid;
388         $params['filename'] = $file->get_filename();
389         $params['screenshotnumber'] = $screenshotnumber;
390         $params['token'] = registration::get_token(MUST_EXIST);
391         $curl->post(HUB_MOODLEORGHUBURL . "/local/hub/webservice/upload.php", $params);
392     }
394     /**
395      * Downloads course backup
396      *
397      * @param int $hubcourseid id of the course on moodle.net
398      * @param string $path local path (in tempdir) to save the downloaded backup to.
399      */
400     public static function download_course_backup($hubcourseid, $path) {
401         $fp = fopen($path, 'w');
403         $curlurl = new \moodle_url(HUB_MOODLEORGHUBURL . '/local/hub/webservice/download.php',
404             ['filetype' => self::HUB_BACKUP_FILE_TYPE, 'courseid' => $hubcourseid]);
406         // Send an identification token if the site is registered.
407         if ($token = registration::get_token()) {
408             $curlurl->param('token', $token);
409         }
411         $ch = curl_init($curlurl->out(false));
412         curl_setopt($ch, CURLOPT_FILE, $fp);
413         curl_exec($ch);
414         curl_close($ch);
415         fclose($fp);
416     }
418     /**
419      * Uploads a course backup
420      *
421      * @param int $hubcourseid id of the published course on moodle.net, it must be published from this site
422      * @param \stored_file $backupfile
423      */
424     public static function upload_course_backup($hubcourseid, \stored_file $backupfile) {
425         $curl = new \curl();
426         $params = array();
427         $params['filetype'] = self::HUB_BACKUP_FILE_TYPE;
428         $params['courseid'] = $hubcourseid;
429         $params['file'] = $backupfile;
430         $params['token'] = registration::get_token();
431         $curl->post(HUB_MOODLEORGHUBURL . '/local/hub/webservice/upload.php', $params);
432     }