Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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/>.
16
 
17
/**
18
 * LTI enrolment plugin helper.
19
 *
20
 * @package enrol_lti
21
 * @copyright 2016 Mark Nelson <markn@moodle.com>
22
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
namespace enrol_lti;
26
 
27
defined('MOODLE_INTERNAL') || die();
28
 
29
/**
30
 * LTI enrolment plugin helper class.
31
 *
32
 * @package enrol_lti
33
 * @copyright 2016 Mark Nelson <markn@moodle.com>
34
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35
 */
36
class helper {
37
    /*
38
     * The value used when we want to enrol new members and unenrol old ones.
39
     */
40
    const MEMBER_SYNC_ENROL_AND_UNENROL = 1;
41
 
42
    /*
43
     * The value used when we want to enrol new members only.
44
     */
45
    const MEMBER_SYNC_ENROL_NEW = 2;
46
 
47
    /*
48
     * The value used when we want to unenrol missing users.
49
     */
50
    const MEMBER_SYNC_UNENROL_MISSING = 3;
51
 
52
    /**
53
     * Code for when an enrolment was successful.
54
     */
55
    const ENROLMENT_SUCCESSFUL = true;
56
 
57
    /**
58
     * Error code for enrolment when max enrolled reached.
59
     */
60
    const ENROLMENT_MAX_ENROLLED = 'maxenrolledreached';
61
 
62
    /**
63
     * Error code for enrolment has not started.
64
     */
65
    const ENROLMENT_NOT_STARTED = 'enrolmentnotstarted';
66
 
67
    /**
68
     * Error code for enrolment when enrolment has finished.
69
     */
70
    const ENROLMENT_FINISHED = 'enrolmentfinished';
71
 
72
    /**
73
     * Error code for when an image file fails to upload.
74
     */
75
    const PROFILE_IMAGE_UPDATE_SUCCESSFUL = true;
76
 
77
    /**
78
     * Error code for when an image file fails to upload.
79
     */
80
    const PROFILE_IMAGE_UPDATE_FAILED = 'profileimagefailed';
81
 
82
    /**
83
     * Creates a unique username.
84
     *
85
     * @param string $consumerkey Consumer key
86
     * @param string $ltiuserid External tool user id
87
     * @return string The new username
88
     */
89
    public static function create_username($consumerkey, $ltiuserid) {
90
        if (!empty($ltiuserid) && !empty($consumerkey)) {
91
            $userkey = $consumerkey . ':' . $ltiuserid;
92
        } else {
93
            $userkey = false;
94
        }
95
 
96
        return 'enrol_lti' . sha1($consumerkey . '::' . $userkey);
97
    }
98
 
99
    /**
100
     * Adds default values for the user object based on the tool provided.
101
     *
102
     * @param \stdClass $tool
103
     * @param \stdClass $user
104
     * @return \stdClass The $user class with added default values
105
     */
106
    public static function assign_user_tool_data($tool, $user) {
107
        global $CFG;
108
 
109
        $user->city = (!empty($tool->city)) ? $tool->city : "";
110
        $user->country = (!empty($tool->country)) ? $tool->country : "";
111
        $user->institution = (!empty($tool->institution)) ? $tool->institution : "";
112
        $user->timezone = (!empty($tool->timezone)) ? $tool->timezone : "";
113
        if (isset($tool->maildisplay)) {
114
            $user->maildisplay = $tool->maildisplay;
115
        } else if (isset($CFG->defaultpreference_maildisplay)) {
116
            $user->maildisplay = $CFG->defaultpreference_maildisplay;
117
        } else {
118
            $user->maildisplay = 2;
119
        }
120
        $user->mnethostid = $CFG->mnet_localhost_id;
121
        $user->confirmed = 1;
122
        $user->lang = $tool->lang;
123
 
124
        return $user;
125
    }
126
 
127
    /**
128
     * Compares two users.
129
     *
130
     * @param \stdClass $newuser The new user
131
     * @param \stdClass $olduser The old user
132
     * @return bool True if both users are the same
133
     */
134
    public static function user_match($newuser, $olduser) {
135
        if ($newuser->firstname != $olduser->firstname) {
136
            return false;
137
        }
138
        if ($newuser->lastname != $olduser->lastname) {
139
            return false;
140
        }
141
        if ($newuser->email != $olduser->email) {
142
            return false;
143
        }
144
        if ($newuser->city != $olduser->city) {
145
            return false;
146
        }
147
        if ($newuser->country != $olduser->country) {
148
            return false;
149
        }
150
        if ($newuser->institution != $olduser->institution) {
151
            return false;
152
        }
153
        if ($newuser->timezone != $olduser->timezone) {
154
            return false;
155
        }
156
        if ($newuser->maildisplay != $olduser->maildisplay) {
157
            return false;
158
        }
159
        if ($newuser->mnethostid != $olduser->mnethostid) {
160
            return false;
161
        }
162
        if ($newuser->confirmed != $olduser->confirmed) {
163
            return false;
164
        }
165
        if ($newuser->lang != $olduser->lang) {
166
            return false;
167
        }
168
 
169
        return true;
170
    }
171
 
172
    /**
173
     * Updates the users profile image.
174
     *
175
     * @param int $userid the id of the user
176
     * @param string $url the url of the image
177
     * @return bool|string true if successful, else a string explaining why it failed
178
     */
179
    public static function update_user_profile_image($userid, $url) {
180
        global $CFG, $DB;
181
 
182
        require_once($CFG->libdir . '/filelib.php');
183
        require_once($CFG->libdir . '/gdlib.php');
184
 
185
        $fs = get_file_storage();
186
 
187
        $context = \context_user::instance($userid, MUST_EXIST);
188
        $fs->delete_area_files($context->id, 'user', 'newicon');
189
 
190
        $filerecord = array(
191
            'contextid' => $context->id,
192
            'component' => 'user',
193
            'filearea' => 'newicon',
194
            'itemid' => 0,
195
            'filepath' => '/'
196
        );
197
 
198
        $urlparams = array(
199
            'calctimeout' => false,
200
            'timeout' => 5,
201
            'skipcertverify' => true,
202
            'connecttimeout' => 5
203
        );
204
 
205
        try {
206
            $fs->create_file_from_url($filerecord, $url, $urlparams);
207
        } catch (\file_exception $e) {
208
            return get_string($e->errorcode, $e->module, $e->a);
209
        }
210
 
211
        $iconfile = $fs->get_area_files($context->id, 'user', 'newicon', false, 'itemid', false);
212
 
213
        // There should only be one.
214
        $iconfile = reset($iconfile);
215
 
216
        // Something went wrong while creating temp file - remove the uploaded file.
217
        if (!$iconfile = $iconfile->copy_content_to_temp()) {
218
            $fs->delete_area_files($context->id, 'user', 'newicon');
219
            return self::PROFILE_IMAGE_UPDATE_FAILED;
220
        }
221
 
222
        // Copy file to temporary location and the send it for processing icon.
223
        $newpicture = (int) process_new_icon($context, 'user', 'icon', 0, $iconfile);
224
        // Delete temporary file.
225
        @unlink($iconfile);
226
        // Remove uploaded file.
227
        $fs->delete_area_files($context->id, 'user', 'newicon');
228
        // Set the user's picture.
229
        $DB->set_field('user', 'picture', $newpicture, array('id' => $userid));
230
        return self::PROFILE_IMAGE_UPDATE_SUCCESSFUL;
231
    }
232
 
233
    /**
234
     * Enrol a user in a course.
235
     *
236
     * @param \stdclass $tool The tool object (retrieved using self::get_lti_tool() or self::get_lti_tools())
237
     * @param int $userid The user id
238
     * @return bool|string returns true if successful, else an error code
239
     */
240
    public static function enrol_user($tool, $userid) {
241
        global $DB;
242
 
243
        // Check if the user enrolment exists.
244
        if (!$DB->record_exists('user_enrolments', array('enrolid' => $tool->enrolid, 'userid' => $userid))) {
245
            // Check if the maximum enrolled limit has been met.
246
            if ($tool->maxenrolled) {
247
                if ($DB->count_records('user_enrolments', array('enrolid' => $tool->enrolid)) >= $tool->maxenrolled) {
248
                    return self::ENROLMENT_MAX_ENROLLED;
249
                }
250
            }
251
            // Check if the enrolment has not started.
252
            if ($tool->enrolstartdate && time() < $tool->enrolstartdate) {
253
                return self::ENROLMENT_NOT_STARTED;
254
            }
255
            // Check if the enrolment has finished.
256
            if ($tool->enrolenddate && time() > $tool->enrolenddate) {
257
                return self::ENROLMENT_FINISHED;
258
            }
259
 
260
            $timeend = 0;
261
            if ($tool->enrolperiod) {
262
                $timeend = time() + $tool->enrolperiod;
263
            }
264
 
265
            // Finally, enrol the user.
266
            $instance = new \stdClass();
267
            $instance->id = $tool->enrolid;
268
            $instance->courseid = $tool->courseid;
269
            $instance->enrol = 'lti';
270
            $instance->status = $tool->status;
271
            $ltienrol = enrol_get_plugin('lti');
272
 
273
            // Hack - need to do this to workaround DB caching hack. See MDL-53977.
274
            $timestart = intval(substr(time(), 0, 8) . '00') - 1;
275
            $ltienrol->enrol_user($instance, $userid, null, $timestart, $timeend);
276
        }
277
 
278
        return self::ENROLMENT_SUCCESSFUL;
279
    }
280
 
281
    /**
282
     * Returns the LTI tool.
283
     *
284
     * @param int $toolid
285
     * @return \stdClass the tool
286
     */
287
    public static function get_lti_tool($toolid) {
288
        global $DB;
289
 
290
        $sql = "SELECT elt.*, e.name, e.courseid, e.status, e.enrolstartdate, e.enrolenddate, e.enrolperiod
291
                  FROM {enrol_lti_tools} elt
292
                  JOIN {enrol} e
293
                    ON elt.enrolid = e.id
294
                 WHERE elt.id = :tid";
295
 
296
        return $DB->get_record_sql($sql, array('tid' => $toolid), MUST_EXIST);
297
    }
298
 
299
    /**
300
     * Returns the LTI tools requested.
301
     *
302
     * @param array $params The list of SQL params (eg. array('columnname' => value, 'columnname2' => value)).
303
     * @param int $limitfrom return a subset of records, starting at this point (optional).
304
     * @param int $limitnum return a subset comprising this many records in total
305
     * @return array of tools
306
     */
307
    public static function get_lti_tools($params = array(), $limitfrom = 0, $limitnum = 0) {
308
        global $DB;
309
 
310
        $sql = "SELECT elt.*, e.name, e.courseid, e.status, e.enrolstartdate, e.enrolenddate, e.enrolperiod
311
                  FROM {enrol_lti_tools} elt
312
                  JOIN {enrol} e
313
                    ON elt.enrolid = e.id";
314
        if ($params) {
315
            $where = "WHERE";
316
            foreach ($params as $colname => $value) {
317
                $sql .= " $where $colname = :$colname";
318
                $where = "AND";
319
            }
320
        }
321
        $sql .= " ORDER BY elt.timecreated";
322
 
323
        return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
324
    }
325
 
326
    /**
327
     * Returns the number of LTI tools.
328
     *
329
     * @param array $params The list of SQL params (eg. array('columnname' => value, 'columnname2' => value)).
330
     * @return int The number of tools
331
     */
332
    public static function count_lti_tools($params = array()) {
333
        global $DB;
334
 
335
        $sql = "SELECT COUNT(*)
336
                  FROM {enrol_lti_tools} elt
337
                  JOIN {enrol} e
338
                    ON elt.enrolid = e.id";
339
        if ($params) {
340
            $where = "WHERE";
341
            foreach ($params as $colname => $value) {
342
                $sql .= " $where $colname = :$colname";
343
                $where = "AND";
344
            }
345
        }
346
 
347
        return $DB->count_records_sql($sql, $params);
348
    }
349
 
350
    /**
351
     * Create a IMS POX body request for sync grades.
352
     *
353
     * @param string $source Sourceid required for the request
354
     * @param float $grade User final grade
355
     * @return string
356
     */
357
    public static function create_service_body($source, $grade) {
358
        return '<?xml version="1.0" encoding="UTF-8"?>
359
            <imsx_POXEnvelopeRequest xmlns="http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
360
              <imsx_POXHeader>
361
                <imsx_POXRequestHeaderInfo>
362
                  <imsx_version>V1.0</imsx_version>
363
                  <imsx_messageIdentifier>' . (time()) . '</imsx_messageIdentifier>
364
                </imsx_POXRequestHeaderInfo>
365
              </imsx_POXHeader>
366
              <imsx_POXBody>
367
                <replaceResultRequest>
368
                  <resultRecord>
369
                    <sourcedGUID>
370
                      <sourcedId>' . $source . '</sourcedId>
371
                    </sourcedGUID>
372
                    <result>
373
                      <resultScore>
374
                        <language>en-us</language>
375
                        <textString>' . $grade . '</textString>
376
                      </resultScore>
377
                    </result>
378
                  </resultRecord>
379
                </replaceResultRequest>
380
              </imsx_POXBody>
381
            </imsx_POXEnvelopeRequest>';
382
    }
383
 
384
    /**
385
     * Returns the url to launch the lti tool.
386
     *
387
     * @param int $toolid the id of the shared tool
388
     * @return \moodle_url the url to launch the tool
389
     * @since Moodle 3.2
390
     */
391
    public static function get_launch_url($toolid) {
392
        return new \moodle_url('/enrol/lti/tool.php', array('id' => $toolid));
393
    }
394
 
395
    /**
396
     * Returns the name of the lti enrolment instance, or the name of the course/module being shared.
397
     *
398
     * @param \stdClass $tool The lti tool
399
     * @return string The name of the tool
400
     * @since Moodle 3.2
401
     */
402
    public static function get_name($tool) {
403
        $name = null;
404
 
405
        if (empty($tool->name)) {
406
            $toolcontext = \context::instance_by_id($tool->contextid);
407
            $name = $toolcontext->get_context_name();
408
        } else {
409
            $name = $tool->name;
410
        };
411
 
412
        return $name;
413
    }
414
 
415
    /**
416
     * Returns a description of the course or module that this lti instance points to.
417
     *
418
     * @param \stdClass $tool The lti tool
419
     * @return string A description of the tool
420
     * @since Moodle 3.2
421
     */
422
    public static function get_description($tool) {
423
        global $DB;
424
        $description = '';
425
        $context = \context::instance_by_id($tool->contextid);
426
        if ($context->contextlevel == CONTEXT_COURSE) {
427
            $course = $DB->get_record('course', array('id' => $context->instanceid));
428
            $description = $course->summary;
429
        } else if ($context->contextlevel == CONTEXT_MODULE) {
430
            $cmid = $context->instanceid;
431
            $cm = get_coursemodule_from_id(false, $context->instanceid, 0, false, MUST_EXIST);
432
            $module = $DB->get_record($cm->modname, array('id' => $cm->instance));
433
            $description = $module->intro;
434
        }
435
        return trim(html_to_text($description));
436
    }
437
 
438
    /**
439
     * Returns the icon of the tool.
440
     *
441
     * @param \stdClass $tool The lti tool
442
     * @return \moodle_url A url to the icon of the tool
443
     * @since Moodle 3.2
444
     */
445
    public static function get_icon($tool) {
446
        global $OUTPUT;
447
        return $OUTPUT->favicon();
448
    }
449
 
450
    /**
451
     * Returns the url to the cartridge representing the tool.
452
     *
453
     * If you have slash arguments enabled, this will be a nice url ending in cartridge.xml.
454
     * If not it will be a php page with some parameters passed.
455
     *
456
     * @param \stdClass $tool The lti tool
457
     * @return string The url to the cartridge representing the tool
458
     * @since Moodle 3.2
459
     */
460
    public static function get_cartridge_url($tool) {
461
        global $CFG;
462
        $url = null;
463
 
464
        $id = $tool->id;
465
        $token = self::generate_cartridge_token($tool->id);
466
        if ($CFG->slasharguments) {
467
            $url = new \moodle_url('/enrol/lti/cartridge.php/' . $id . '/' . $token . '/cartridge.xml');
468
        } else {
469
            $url = new \moodle_url('/enrol/lti/cartridge.php',
470
                    array(
471
                        'id' => $id,
472
                        'token' => $token
473
                    )
474
                );
475
        }
476
        return $url;
477
    }
478
 
479
    /**
480
     * Returns the url to the tool proxy registration url.
481
     *
482
     * If you have slash arguments enabled, this will be a nice url ending in cartridge.xml.
483
     * If not it will be a php page with some parameters passed.
484
     *
485
     * @param \stdClass $tool The lti tool
486
     * @return string The url to the cartridge representing the tool
487
     */
488
    public static function get_proxy_url($tool) {
489
        global $CFG;
490
        $url = null;
491
 
492
        $id = $tool->id;
493
        $token = self::generate_proxy_token($tool->id);
494
        if ($CFG->slasharguments) {
495
            $url = new \moodle_url('/enrol/lti/proxy.php/' . $id . '/' . $token . '/');
496
        } else {
497
            $url = new \moodle_url('/enrol/lti/proxy.php',
498
                    array(
499
                        'id' => $id,
500
                        'token' => $token
501
                    )
502
                );
503
        }
504
        return $url;
505
    }
506
 
507
    /**
508
     * Returns a unique hash for this site and this enrolment instance.
509
     *
510
     * Used to verify that the link to the cartridge has not just been guessed.
511
     *
512
     * @param int $toolid The id of the shared tool
513
     * @return string MD5 hash of combined site ID and enrolment instance ID.
514
     * @since Moodle 3.2
515
     */
516
    public static function generate_cartridge_token($toolid) {
517
        $siteidentifier = get_site_identifier();
518
        $checkhash = md5($siteidentifier . '_enrol_lti_cartridge_' . $toolid);
519
        return $checkhash;
520
    }
521
 
522
    /**
523
     * Returns a unique hash for this site and this enrolment instance.
524
     *
525
     * Used to verify that the link to the proxy has not just been guessed.
526
     *
527
     * @param int $toolid The id of the shared tool
528
     * @return string MD5 hash of combined site ID and enrolment instance ID.
529
     * @since Moodle 3.2
530
     */
531
    public static function generate_proxy_token($toolid) {
532
        $siteidentifier = get_site_identifier();
533
        $checkhash = md5($siteidentifier . '_enrol_lti_proxy_' . $toolid);
534
        return $checkhash;
535
    }
536
 
537
    /**
538
     * Verifies that the given token matches the cartridge token of the given shared tool.
539
     *
540
     * @param int $toolid The id of the shared tool
541
     * @param string $token hash for this site and this enrolment instance
542
     * @return boolean True if the token matches, false if it does not
543
     * @since Moodle 3.2
544
     */
545
    public static function verify_cartridge_token($toolid, $token) {
546
        return $token == self::generate_cartridge_token($toolid);
547
    }
548
 
549
    /**
550
     * Verifies that the given token matches the proxy token of the given shared tool.
551
     *
552
     * @param int $toolid The id of the shared tool
553
     * @param string $token hash for this site and this enrolment instance
554
     * @return boolean True if the token matches, false if it does not
555
     * @since Moodle 3.2
556
     */
557
    public static function verify_proxy_token($toolid, $token) {
558
        return $token == self::generate_proxy_token($toolid);
559
    }
560
 
561
    /**
562
     * Returns the parameters of the cartridge as an associative array of partial xpath.
563
     *
564
     * @param int $toolid The id of the shared tool
565
     * @return array Recursive associative array with partial xpath to be concatenated into an xpath expression
566
     *     before setting the value.
567
     * @since Moodle 3.2
568
     */
569
    protected static function get_cartridge_parameters($toolid) {
570
        global $PAGE, $SITE;
571
        $PAGE->set_context(\context_system::instance());
572
 
573
        // Get the tool.
574
        $tool = self::get_lti_tool($toolid);
575
 
576
        // Work out the name of the tool.
577
        $title = self::get_name($tool);
578
        $launchurl = self::get_launch_url($toolid);
579
        $launchurl = $launchurl->out(false);
580
        $iconurl = self::get_icon($tool);
581
        $iconurl = $iconurl->out(false);
582
        $securelaunchurl = null;
583
        $secureiconurl = null;
584
        $vendorurl = new \moodle_url('/');
585
        $vendorurl = $vendorurl->out(false);
586
        $description = self::get_description($tool);
587
 
588
        // If we are a https site, we can add the launch url and icon urls as secure equivalents.
589
        if (\is_https()) {
590
            $securelaunchurl = $launchurl;
591
            $secureiconurl = $iconurl;
592
        }
593
 
594
        return array(
595
                "/cc:cartridge_basiclti_link" => array(
596
                    "/blti:title" => $title,
597
                    "/blti:description" => $description,
598
                    "/blti:extensions" => array(
599
                            "/lticm:property[@name='icon_url']" => $iconurl,
600
                            "/lticm:property[@name='secure_icon_url']" => $secureiconurl
601
                        ),
602
                    "/blti:launch_url" => $launchurl,
603
                    "/blti:secure_launch_url" => $securelaunchurl,
604
                    "/blti:icon" => $iconurl,
605
                    "/blti:secure_icon" => $secureiconurl,
606
                    "/blti:vendor" => array(
607
                            "/lticp:code" => $SITE->shortname,
608
                            "/lticp:name" => $SITE->fullname,
609
                            "/lticp:description" => trim(html_to_text($SITE->summary)),
610
                            "/lticp:url" => $vendorurl
611
                        )
612
                )
613
            );
614
    }
615
 
616
    /**
617
     * Traverses a recursive associative array, setting the properties of the corresponding
618
     * xpath element.
619
     *
620
     * @param \DOMXPath $xpath The xpath with the xml to modify
621
     * @param array $parameters The array of xpaths to search through
622
     * @param string $prefix The current xpath prefix (gets longer the deeper into the array you go)
623
     * @return void
624
     * @since Moodle 3.2
625
     */
626
    protected static function set_xpath($xpath, $parameters, $prefix = '') {
627
        foreach ($parameters as $key => $value) {
628
            if (is_array($value)) {
629
                self::set_xpath($xpath, $value, $prefix . $key);
630
            } else {
631
                $result = @$xpath->query($prefix . $key);
632
                if ($result) {
633
                    $node = $result->item(0);
634
                    if ($node) {
635
                        if (is_null($value)) {
636
                            $node->parentNode->removeChild($node);
637
                        } else {
638
                            $node->nodeValue = s($value);
639
                        }
640
                    }
641
                } else {
642
                    throw new \coding_exception('Please check your XPATH and try again.');
643
                }
644
            }
645
        }
646
    }
647
 
648
    /**
649
     * Create an IMS cartridge for the tool.
650
     *
651
     * @param int $toolid The id of the shared tool
652
     * @return string representing the generated cartridge
653
     * @since Moodle 3.2
654
     */
655
    public static function create_cartridge($toolid) {
656
        $cartridge = new \DOMDocument();
657
        $cartridge->load(realpath(__DIR__ . '/../xml/imslticc.xml'));
658
        $xpath = new \DOMXpath($cartridge);
659
        $xpath->registerNamespace('cc', 'http://www.imsglobal.org/xsd/imslticc_v1p0');
660
        $parameters = self::get_cartridge_parameters($toolid);
661
        self::set_xpath($xpath, $parameters);
662
 
663
        return $cartridge->saveXML();
664
    }
665
}